Browse Source

feat: add markdown editor

node 6 years ago
parent
commit
dda6aea022
100 changed files with 29566 additions and 51 deletions
  1. 93
    0
      config/env.js
  2. 14
    0
      config/jest/cssTransform.js
  3. 12
    0
      config/jest/fileTransform.js
  4. 55
    0
      config/paths.js
  5. 22
    0
      config/polyfills.js
  6. 262
    0
      config/webpack.config.dev.js
  7. 342
    0
      config/webpack.config.prod.js
  8. 95
    0
      config/webpackDevServer.config.js
  9. 137
    46
      package-lock.json
  10. 84
    5
      package.json
  11. 150
    0
      scripts/build.js
  12. 107
    0
      scripts/start.js
  13. 27
    0
      scripts/test.js
  14. 1
    0
      source/braft-editor/dist/index.css
  15. 4459
    0
      source/braft-editor/dist/index.js
  16. 1
    0
      source/braft-editor/dist/index.js.map
  17. 1
    0
      source/react-mde/.dockerignore
  18. 8
    0
      source/react-mde/.gitignore
  19. 19
    0
      source/react-mde/.npmignore
  20. 1
    0
      source/react-mde/.storybook/addons.js
  21. 16
    0
      source/react-mde/.storybook/config.js
  22. 1
    0
      source/react-mde/.storybook/preview-head.html
  23. 73
    0
      source/react-mde/.storybook/stories/customization/commandButton/ButtonComponent.tsx
  24. 51
    0
      source/react-mde/.storybook/stories/customization/commandButton/CustomButtonStory.tsx
  25. 36
    0
      source/react-mde/.storybook/stories/customization/commandButton/UploadImageCommand.tsx
  26. 66
    0
      source/react-mde/.storybook/stories/customization/commandIcon/EmojiIconsStory.tsx
  27. 47
    0
      source/react-mde/.storybook/stories/layouts/HorizontalLayoutStory.tsx
  28. 47
    0
      source/react-mde/.storybook/stories/layouts/NoPreviewLayoutStory.tsx
  29. 47
    0
      source/react-mde/.storybook/stories/layouts/TabbedLayoutStory.tsx
  30. 47
    0
      source/react-mde/.storybook/stories/layouts/VerticalLayoutStory.tsx
  31. 6
    0
      source/react-mde/.storybook/styles/demo.scss
  32. 1
    0
      source/react-mde/.storybook/styles/variables.scss
  33. 40
    0
      source/react-mde/.storybook/webpack.config.js
  34. 8
    0
      source/react-mde/Dockerfile
  35. 21
    0
      source/react-mde/LICENSE
  36. BIN
      source/react-mde/assets/architecture.png
  37. BIN
      source/react-mde/assets/react-mde-5.png
  38. BIN
      source/react-mde/assets/react-mde.png
  39. 43
    0
      source/react-mde/demo/App.tsx
  40. 15
    0
      source/react-mde/demo/client.tsx
  41. 29
    0
      source/react-mde/demo/index.html
  42. 27
    0
      source/react-mde/demo/index.prod.html
  43. 26
    0
      source/react-mde/demo/server.ts
  44. 16
    0
      source/react-mde/demo/styles/demo.scss
  45. 1
    0
      source/react-mde/demo/styles/variables.scss
  46. 70
    0
      source/react-mde/docs-md/ChangeLogMigrating.md
  47. 103
    0
      source/react-mde/docs-md/customButton.md
  48. 289
    0
      source/react-mde/docs-md/readme-old.md
  49. BIN
      source/react-mde/docs/favicon.ico
  50. 89
    0
      source/react-mde/docs/iframe.html
  51. 23
    0
      source/react-mde/docs/index.html
  52. 98
    0
      source/react-mde/docs/static/manager.e49c8faf84bedb0a57cd.bundle.js
  53. 1
    0
      source/react-mde/docs/static/manager.e49c8faf84bedb0a57cd.bundle.js.map
  54. 2
    0
      source/react-mde/docs/static/preview.882bafd716960d2ded4d.bundle.js
  55. 1
    0
      source/react-mde/docs/static/preview.882bafd716960d2ded4d.bundle.js.map
  56. 2
    0
      source/react-mde/docs/static/runtime~manager.e92fd76fc0e1ef205464.bundle.js
  57. 1
    0
      source/react-mde/docs/static/runtime~manager.e92fd76fc0e1ef205464.bundle.js.map
  58. 2
    0
      source/react-mde/docs/static/runtime~preview.3d1c6dcf72bd957deb27.bundle.js
  59. 1
    0
      source/react-mde/docs/static/runtime~preview.3d1c6dcf72bd957deb27.bundle.js.map
  60. 58
    0
      source/react-mde/docs/static/vendors~preview.014d44b79772935ed98a.bundle.js
  61. 1
    0
      source/react-mde/docs/static/vendors~preview.014d44b79772935ed98a.bundle.js.map
  62. 46
    0
      source/react-mde/gulpfile.js
  63. 20650
    0
      source/react-mde/package-lock.json
  64. 99
    0
      source/react-mde/package.json
  65. 202
    0
      source/react-mde/readme.md
  66. 13
    0
      source/react-mde/src/LayoutMap.tsx
  67. 122
    0
      source/react-mde/src/ReactMde.tsx
  68. 16
    0
      source/react-mde/src/commands/boldCommand.tsx
  69. 16
    0
      source/react-mde/src/commands/checkListCommand.tsx
  70. 51
    0
      source/react-mde/src/commands/codeCommand.tsx
  71. 35
    0
      source/react-mde/src/commands/headerCommand.tsx
  72. 27
    0
      source/react-mde/src/commands/imageCommand.tsx
  73. 35
    0
      source/react-mde/src/commands/index.ts
  74. 16
    0
      source/react-mde/src/commands/italicCommand.tsx
  75. 28
    0
      source/react-mde/src/commands/linkCommand.tsx
  76. 16
    0
      source/react-mde/src/commands/orderedCommand.tsx
  77. 35
    0
      source/react-mde/src/commands/quoteCommand.tsx
  78. 16
    0
      source/react-mde/src/commands/strikethroughCommand.tsx
  79. 15
    0
      source/react-mde/src/commands/tabCommand.tsx
  80. 16
    0
      source/react-mde/src/commands/unorderedListCommand.tsx
  81. 60
    0
      source/react-mde/src/components-layout/HorizontalLayout.tsx
  82. 51
    0
      source/react-mde/src/components-layout/NoPreviewLayout.tsx
  83. 90
    0
      source/react-mde/src/components-layout/TabbedLayout.tsx
  84. 60
    0
      source/react-mde/src/components-layout/VerticalLayout.tsx
  85. 4
    0
      source/react-mde/src/components-layout/index.tsx
  86. 77
    0
      source/react-mde/src/components/MdeEditor.tsx
  87. 28
    0
      source/react-mde/src/components/MdePreview.tsx
  88. 56
    0
      source/react-mde/src/components/MdeToolbar.tsx
  89. 24
    0
      source/react-mde/src/components/MdeToolbarButton.tsx
  90. 12
    0
      source/react-mde/src/components/MdeToolbarButtonGroup.tsx
  91. 124
    0
      source/react-mde/src/components/MdeToolbarDropdown.tsx
  92. 9
    0
      source/react-mde/src/components/MdeToolbarIcon.tsx
  93. 7
    0
      source/react-mde/src/components/index.tsx
  94. 19
    0
      source/react-mde/src/index.tsx
  95. 8
    0
      source/react-mde/src/styles/react-mde-all.scss
  96. 10
    0
      source/react-mde/src/styles/react-mde-editor.scss
  97. 40
    0
      source/react-mde/src/styles/react-mde-horizontal-layout.scss
  98. 11
    0
      source/react-mde/src/styles/react-mde-no-preview-layout.scss
  99. 127
    0
      source/react-mde/src/styles/react-mde-preview.scss
  100. 0
    0
      source/react-mde/src/styles/react-mde-tabbed-layout.scss

+ 93
- 0
config/env.js View File

@@ -0,0 +1,93 @@
1
+'use strict';
2
+
3
+const fs = require('fs');
4
+const path = require('path');
5
+const paths = require('./paths');
6
+
7
+// Make sure that including paths.js after env.js will read .env variables.
8
+delete require.cache[require.resolve('./paths')];
9
+
10
+const NODE_ENV = process.env.NODE_ENV;
11
+if (!NODE_ENV) {
12
+  throw new Error(
13
+    'The NODE_ENV environment variable is required but was not specified.'
14
+  );
15
+}
16
+
17
+// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
18
+var dotenvFiles = [
19
+  `${paths.dotenv}.${NODE_ENV}.local`,
20
+  `${paths.dotenv}.${NODE_ENV}`,
21
+  // Don't include `.env.local` for `test` environment
22
+  // since normally you expect tests to produce the same
23
+  // results for everyone
24
+  NODE_ENV !== 'test' && `${paths.dotenv}.local`,
25
+  paths.dotenv,
26
+].filter(Boolean);
27
+
28
+// Load environment variables from .env* files. Suppress warnings using silent
29
+// if this file is missing. dotenv will never modify any environment variables
30
+// that have already been set.  Variable expansion is supported in .env files.
31
+// https://github.com/motdotla/dotenv
32
+// https://github.com/motdotla/dotenv-expand
33
+dotenvFiles.forEach(dotenvFile => {
34
+  if (fs.existsSync(dotenvFile)) {
35
+    require('dotenv-expand')(
36
+      require('dotenv').config({
37
+        path: dotenvFile,
38
+      })
39
+    );
40
+  }
41
+});
42
+
43
+// We support resolving modules according to `NODE_PATH`.
44
+// This lets you use absolute paths in imports inside large monorepos:
45
+// https://github.com/facebookincubator/create-react-app/issues/253.
46
+// It works similar to `NODE_PATH` in Node itself:
47
+// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
48
+// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
49
+// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
50
+// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421
51
+// We also resolve them to make sure all tools using them work consistently.
52
+const appDirectory = fs.realpathSync(process.cwd());
53
+process.env.NODE_PATH = (process.env.NODE_PATH || '')
54
+  .split(path.delimiter)
55
+  .filter(folder => folder && !path.isAbsolute(folder))
56
+  .map(folder => path.resolve(appDirectory, folder))
57
+  .join(path.delimiter);
58
+
59
+// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
60
+// injected into the application via DefinePlugin in Webpack configuration.
61
+const REACT_APP = /^REACT_APP_/i;
62
+
63
+function getClientEnvironment(publicUrl) {
64
+  const raw = Object.keys(process.env)
65
+    .filter(key => REACT_APP.test(key))
66
+    .reduce(
67
+      (env, key) => {
68
+        env[key] = process.env[key];
69
+        return env;
70
+      },
71
+      {
72
+        // Useful for determining whether we’re running in production mode.
73
+        // Most importantly, it switches React into the correct mode.
74
+        NODE_ENV: process.env.NODE_ENV || 'development',
75
+        // Useful for resolving the correct path to static assets in `public`.
76
+        // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
77
+        // This should only be used as an escape hatch. Normally you would put
78
+        // images into the `src` and `import` them in code to get their paths.
79
+        PUBLIC_URL: publicUrl,
80
+      }
81
+    );
82
+  // Stringify all values so we can feed into Webpack DefinePlugin
83
+  const stringified = {
84
+    'process.env': Object.keys(raw).reduce((env, key) => {
85
+      env[key] = JSON.stringify(raw[key]);
86
+      return env;
87
+    }, {}),
88
+  };
89
+
90
+  return { raw, stringified };
91
+}
92
+
93
+module.exports = getClientEnvironment;

+ 14
- 0
config/jest/cssTransform.js View File

@@ -0,0 +1,14 @@
1
+'use strict';
2
+
3
+// This is a custom Jest transformer turning style imports into empty objects.
4
+// http://facebook.github.io/jest/docs/en/webpack.html
5
+
6
+module.exports = {
7
+  process() {
8
+    return 'module.exports = {};';
9
+  },
10
+  getCacheKey() {
11
+    // The output is always the same.
12
+    return 'cssTransform';
13
+  },
14
+};

+ 12
- 0
config/jest/fileTransform.js View File

@@ -0,0 +1,12 @@
1
+'use strict';
2
+
3
+const path = require('path');
4
+
5
+// This is a custom Jest transformer turning file imports into filenames.
6
+// http://facebook.github.io/jest/docs/en/webpack.html
7
+
8
+module.exports = {
9
+  process(src, filename) {
10
+    return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11
+  },
12
+};

+ 55
- 0
config/paths.js View File

@@ -0,0 +1,55 @@
1
+'use strict';
2
+
3
+const path = require('path');
4
+const fs = require('fs');
5
+const url = require('url');
6
+
7
+// Make sure any symlinks in the project folder are resolved:
8
+// https://github.com/facebookincubator/create-react-app/issues/637
9
+const appDirectory = fs.realpathSync(process.cwd());
10
+const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11
+
12
+const envPublicUrl = process.env.PUBLIC_URL;
13
+
14
+function ensureSlash(path, needsSlash) {
15
+  const hasSlash = path.endsWith('/');
16
+  if (hasSlash && !needsSlash) {
17
+    return path.substr(path, path.length - 1);
18
+  } else if (!hasSlash && needsSlash) {
19
+    return `${path}/`;
20
+  } else {
21
+    return path;
22
+  }
23
+}
24
+
25
+const getPublicUrl = appPackageJson =>
26
+  envPublicUrl || require(appPackageJson).homepage;
27
+
28
+// We use `PUBLIC_URL` environment variable or "homepage" field to infer
29
+// "public path" at which the app is served.
30
+// Webpack needs to know it to put the right <script> hrefs into HTML even in
31
+// single-page apps that may serve index.html for nested URLs like /todos/42.
32
+// We can't use a relative path in HTML because we don't want to load something
33
+// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
34
+function getServedPath(appPackageJson) {
35
+  const publicUrl = getPublicUrl(appPackageJson);
36
+  const servedUrl =
37
+    envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
38
+  return ensureSlash(servedUrl, true);
39
+}
40
+
41
+// config after eject: we're in ./config/
42
+module.exports = {
43
+  dotenv: resolveApp('.env'),
44
+  appBuild: resolveApp('build'),
45
+  appPublic: resolveApp('public'),
46
+  appHtml: resolveApp('public/index.html'),
47
+  appIndexJs: resolveApp('src/index.js'),
48
+  appPackageJson: resolveApp('package.json'),
49
+  appSrc: resolveApp('src'),
50
+  yarnLockFile: resolveApp('yarn.lock'),
51
+  testsSetup: resolveApp('src/setupTests.js'),
52
+  appNodeModules: resolveApp('node_modules'),
53
+  publicUrl: getPublicUrl(resolveApp('package.json')),
54
+  servedPath: getServedPath(resolveApp('package.json')),
55
+};

+ 22
- 0
config/polyfills.js View File

@@ -0,0 +1,22 @@
1
+'use strict';
2
+
3
+if (typeof Promise === 'undefined') {
4
+  // Rejection tracking prevents a common issue where React gets into an
5
+  // inconsistent state due to an error, but it gets swallowed by a Promise,
6
+  // and the user has no idea what causes React's erratic future behavior.
7
+  require('promise/lib/rejection-tracking').enable();
8
+  window.Promise = require('promise/lib/es6-extensions.js');
9
+}
10
+
11
+// fetch() polyfill for making API calls.
12
+require('whatwg-fetch');
13
+
14
+// Object.assign() is commonly used with React.
15
+// It will use the native implementation if it's present and isn't buggy.
16
+Object.assign = require('object-assign');
17
+
18
+// In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19
+// We don't polyfill it in the browser--this is user's responsibility.
20
+if (process.env.NODE_ENV === 'test') {
21
+  require('raf').polyfill(global);
22
+}

+ 262
- 0
config/webpack.config.dev.js View File

@@ -0,0 +1,262 @@
1
+'use strict';
2
+
3
+const autoprefixer = require('autoprefixer');
4
+const path = require('path');
5
+const webpack = require('webpack');
6
+const HtmlWebpackPlugin = require('html-webpack-plugin');
7
+const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
8
+const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
9
+const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
10
+const eslintFormatter = require('react-dev-utils/eslintFormatter');
11
+const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
12
+const getClientEnvironment = require('./env');
13
+const paths = require('./paths');
14
+
15
+// Webpack uses `publicPath` to determine where the app is being served from.
16
+// In development, we always serve from the root. This makes config easier.
17
+const publicPath = '/';
18
+// `publicUrl` is just like `publicPath`, but we will provide it to our app
19
+// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
20
+// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
21
+const publicUrl = '';
22
+// Get environment variables to inject into our app.
23
+const env = getClientEnvironment(publicUrl);
24
+
25
+// This is the development configuration.
26
+// It is focused on developer experience and fast rebuilds.
27
+// The production configuration is different and lives in a separate file.
28
+module.exports = {
29
+  // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
30
+  // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
31
+  devtool: 'cheap-module-source-map',
32
+  // These are the "entry points" to our application.
33
+  // This means they will be the "root" imports that are included in JS bundle.
34
+  // The first two entry points enable "hot" CSS and auto-refreshes for JS.
35
+  entry: [
36
+    // We ship a few polyfills by default:
37
+    require.resolve('./polyfills'),
38
+    // Include an alternative client for WebpackDevServer. A client's job is to
39
+    // connect to WebpackDevServer by a socket and get notified about changes.
40
+    // When you save a file, the client will either apply hot updates (in case
41
+    // of CSS changes), or refresh the page (in case of JS changes). When you
42
+    // make a syntax error, this client will display a syntax error overlay.
43
+    // Note: instead of the default WebpackDevServer client, we use a custom one
44
+    // to bring better experience for Create React App users. You can replace
45
+    // the line below with these two lines if you prefer the stock client:
46
+    // require.resolve('webpack-dev-server/client') + '?/',
47
+    // require.resolve('webpack/hot/dev-server'),
48
+    require.resolve('react-dev-utils/webpackHotDevClient'),
49
+    // Finally, this is your app's code:
50
+    paths.appIndexJs,
51
+    // We include the app code last so that if there is a runtime error during
52
+    // initialization, it doesn't blow up the WebpackDevServer client, and
53
+    // changing JS code would still trigger a refresh.
54
+  ],
55
+  output: {
56
+    // Add /* filename */ comments to generated require()s in the output.
57
+    pathinfo: true,
58
+    // This does not produce a real file. It's just the virtual path that is
59
+    // served by WebpackDevServer in development. This is the JS bundle
60
+    // containing code from all our entry points, and the Webpack runtime.
61
+    filename: 'static/js/bundle.js',
62
+    // There are also additional JS chunk files if you use code splitting.
63
+    chunkFilename: 'static/js/[name].chunk.js',
64
+    // This is the URL that app is served from. We use "/" in development.
65
+    publicPath: publicPath,
66
+    // Point sourcemap entries to original disk location (format as URL on Windows)
67
+    devtoolModuleFilenameTemplate: info =>
68
+      path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
69
+  },
70
+  resolve: {
71
+    // This allows you to set a fallback for where Webpack should look for modules.
72
+    // We placed these paths second because we want `node_modules` to "win"
73
+    // if there are any conflicts. This matches Node resolution mechanism.
74
+    // https://github.com/facebookincubator/create-react-app/issues/253
75
+    modules: ['node_modules', paths.appNodeModules].concat(
76
+      // It is guaranteed to exist because we tweak it in `env.js`
77
+      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
78
+    ),
79
+    // These are the reasonable defaults supported by the Node ecosystem.
80
+    // We also include JSX as a common component filename extension to support
81
+    // some tools, although we do not recommend using it, see:
82
+    // https://github.com/facebookincubator/create-react-app/issues/290
83
+    // `web` extension prefixes have been added for better support
84
+    // for React Native Web.
85
+    extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
86
+    alias: {
87
+      
88
+      // Support React Native Web
89
+      // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
90
+      'react-native': 'react-native-web',
91
+    },
92
+    plugins: [
93
+      // Prevents users from importing files from outside of src/ (or node_modules/).
94
+      // This often causes confusion because we only process files within src/ with babel.
95
+      // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
96
+      // please link the files into your node_modules/ and let module-resolution kick in.
97
+      // Make sure your source files are compiled, as they will not be processed in any way.
98
+      new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
99
+    ],
100
+  },
101
+  module: {
102
+    strictExportPresence: true,
103
+    rules: [
104
+      // TODO: Disable require.ensure as it's not a standard language feature.
105
+      // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
106
+      // { parser: { requireEnsure: false } },
107
+
108
+      // First, run the linter.
109
+      // It's important to do this before Babel processes the JS.
110
+      {
111
+        test: /\.(js|jsx|mjs)$/,
112
+        enforce: 'pre',
113
+        use: [
114
+          {
115
+            options: {
116
+              formatter: eslintFormatter,
117
+              eslintPath: require.resolve('eslint'),
118
+              
119
+            },
120
+            loader: require.resolve('eslint-loader'),
121
+          },
122
+        ],
123
+        include: paths.appSrc,
124
+      },
125
+      {
126
+        // "oneOf" will traverse all following loaders until one will
127
+        // match the requirements. When no loader matches it will fall
128
+        // back to the "file" loader at the end of the loader list.
129
+        oneOf: [
130
+          // "url" loader works like "file" loader except that it embeds assets
131
+          // smaller than specified limit in bytes as data URLs to avoid requests.
132
+          // A missing `test` is equivalent to a match.
133
+          {
134
+            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
135
+            loader: require.resolve('url-loader'),
136
+            options: {
137
+              limit: 10000,
138
+              name: 'static/media/[name].[hash:8].[ext]',
139
+            },
140
+          },
141
+          // Process JS with Babel.
142
+          {
143
+            test: /\.(js|jsx|mjs)$/,
144
+            include: paths.appSrc,
145
+            loader: require.resolve('babel-loader'),
146
+            options: {
147
+              
148
+              // This is a feature of `babel-loader` for webpack (not Babel itself).
149
+              // It enables caching results in ./node_modules/.cache/babel-loader/
150
+              // directory for faster rebuilds.
151
+              cacheDirectory: true,
152
+            },
153
+          },
154
+          // "postcss" loader applies autoprefixer to our CSS.
155
+          // "css" loader resolves paths in CSS and adds assets as dependencies.
156
+          // "style" loader turns CSS into JS modules that inject <style> tags.
157
+          // In production, we use a plugin to extract that CSS to a file, but
158
+          // in development "style" loader enables hot editing of CSS.
159
+          {
160
+            test: /\.css$/,
161
+            use: [
162
+              require.resolve('style-loader'),
163
+              {
164
+                loader: require.resolve('css-loader'),
165
+                options: {
166
+                  importLoaders: 1,
167
+                },
168
+              },
169
+              {
170
+                loader: require.resolve('postcss-loader'),
171
+                options: {
172
+                  // Necessary for external CSS imports to work
173
+                  // https://github.com/facebookincubator/create-react-app/issues/2677
174
+                  ident: 'postcss',
175
+                  plugins: () => [
176
+                    require('postcss-flexbugs-fixes'),
177
+                    autoprefixer({
178
+                      browsers: [
179
+                        '>1%',
180
+                        'last 4 versions',
181
+                        'Firefox ESR',
182
+                        'not ie < 9', // React doesn't support IE8 anyway
183
+                      ],
184
+                      flexbox: 'no-2009',
185
+                    }),
186
+                  ],
187
+                },
188
+              },
189
+            ],
190
+          },
191
+          // "file" loader makes sure those assets get served by WebpackDevServer.
192
+          // When you `import` an asset, you get its (virtual) filename.
193
+          // In production, they would get copied to the `build` folder.
194
+          // This loader doesn't use a "test" so it will catch all modules
195
+          // that fall through the other loaders.
196
+          {
197
+            // Exclude `js` files to keep "css" loader working as it injects
198
+            // its runtime that would otherwise processed through "file" loader.
199
+            // Also exclude `html` and `json` extensions so they get processed
200
+            // by webpacks internal loaders.
201
+            exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
202
+            loader: require.resolve('file-loader'),
203
+            options: {
204
+              name: 'static/media/[name].[hash:8].[ext]',
205
+            },
206
+          },
207
+        ],
208
+      },
209
+      // ** STOP ** Are you adding a new loader?
210
+      // Make sure to add the new loader(s) before the "file" loader.
211
+    ],
212
+  },
213
+  plugins: [
214
+    // Makes some environment variables available in index.html.
215
+    // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
216
+    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
217
+    // In development, this will be an empty string.
218
+    new InterpolateHtmlPlugin(env.raw),
219
+    // Generates an `index.html` file with the <script> injected.
220
+    new HtmlWebpackPlugin({
221
+      inject: true,
222
+      template: paths.appHtml,
223
+    }),
224
+    // Add module names to factory functions so they appear in browser profiler.
225
+    new webpack.NamedModulesPlugin(),
226
+    // Makes some environment variables available to the JS code, for example:
227
+    // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
228
+    new webpack.DefinePlugin(env.stringified),
229
+    // This is necessary to emit hot updates (currently CSS only):
230
+    new webpack.HotModuleReplacementPlugin(),
231
+    // Watcher doesn't work well if you mistype casing in a path so we use
232
+    // a plugin that prints an error when you attempt to do this.
233
+    // See https://github.com/facebookincubator/create-react-app/issues/240
234
+    new CaseSensitivePathsPlugin(),
235
+    // If you require a missing module and then `npm install` it, you still have
236
+    // to restart the development server for Webpack to discover it. This plugin
237
+    // makes the discovery automatic so you don't have to restart.
238
+    // See https://github.com/facebookincubator/create-react-app/issues/186
239
+    new WatchMissingNodeModulesPlugin(paths.appNodeModules),
240
+    // Moment.js is an extremely popular library that bundles large locale files
241
+    // by default due to how Webpack interprets its code. This is a practical
242
+    // solution that requires the user to opt into importing specific locales.
243
+    // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
244
+    // You can remove this if you don't use Moment.js:
245
+    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
246
+  ],
247
+  // Some libraries import Node modules but don't use them in the browser.
248
+  // Tell Webpack to provide empty mocks for them so importing them works.
249
+  node: {
250
+    dgram: 'empty',
251
+    fs: 'empty',
252
+    net: 'empty',
253
+    tls: 'empty',
254
+    child_process: 'empty',
255
+  },
256
+  // Turn off performance hints during development because we don't do any
257
+  // splitting or minification in interest of speed. These warnings become
258
+  // cumbersome.
259
+  performance: {
260
+    hints: false,
261
+  },
262
+};

+ 342
- 0
config/webpack.config.prod.js View File

@@ -0,0 +1,342 @@
1
+'use strict';
2
+
3
+const autoprefixer = require('autoprefixer');
4
+const path = require('path');
5
+const webpack = require('webpack');
6
+const HtmlWebpackPlugin = require('html-webpack-plugin');
7
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
8
+const ManifestPlugin = require('webpack-manifest-plugin');
9
+const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
10
+const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
11
+const eslintFormatter = require('react-dev-utils/eslintFormatter');
12
+const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
13
+const paths = require('./paths');
14
+const getClientEnvironment = require('./env');
15
+
16
+// Webpack uses `publicPath` to determine where the app is being served from.
17
+// It requires a trailing slash, or the file assets will get an incorrect path.
18
+const publicPath = paths.servedPath;
19
+// Some apps do not use client-side routing with pushState.
20
+// For these, "homepage" can be set to "." to enable relative asset paths.
21
+const shouldUseRelativeAssetPaths = publicPath === './';
22
+// Source maps are resource heavy and can cause out of memory issue for large source files.
23
+const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
24
+// `publicUrl` is just like `publicPath`, but we will provide it to our app
25
+// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
26
+// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
27
+const publicUrl = publicPath.slice(0, -1);
28
+// Get environment variables to inject into our app.
29
+const env = getClientEnvironment(publicUrl);
30
+
31
+// Assert this just to be safe.
32
+// Development builds of React are slow and not intended for production.
33
+if (env.stringified['process.env'].NODE_ENV !== '"production"') {
34
+  throw new Error('Production builds must have NODE_ENV=production.');
35
+}
36
+
37
+// Note: defined here because it will be used more than once.
38
+const cssFilename = 'static/css/[name].[contenthash:8].css';
39
+
40
+// ExtractTextPlugin expects the build output to be flat.
41
+// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
42
+// However, our output is structured with css, js and media folders.
43
+// To have this structure working with relative paths, we have to use custom options.
44
+const extractTextPluginOptions = shouldUseRelativeAssetPaths
45
+  ? // Making sure that the publicPath goes back to to build folder.
46
+    { publicPath: Array(cssFilename.split('/').length).join('../') }
47
+  : {};
48
+
49
+// This is the production configuration.
50
+// It compiles slowly and is focused on producing a fast and minimal bundle.
51
+// The development configuration is different and lives in a separate file.
52
+module.exports = {
53
+  // Don't attempt to continue if there are any errors.
54
+  bail: true,
55
+  // We generate sourcemaps in production. This is slow but gives good results.
56
+  // You can exclude the *.map files from the build during deployment.
57
+  devtool: shouldUseSourceMap ? 'source-map' : false,
58
+  // In production, we only want to load the polyfills and the app code.
59
+  entry: [require.resolve('./polyfills'), paths.appIndexJs],
60
+  output: {
61
+    // The build folder.
62
+    path: paths.appBuild,
63
+    // Generated JS file names (with nested folders).
64
+    // There will be one main bundle, and one file per asynchronous chunk.
65
+    // We don't currently advertise code splitting but Webpack supports it.
66
+    filename: 'static/js/[name].[chunkhash:8].js',
67
+    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
68
+    // We inferred the "public path" (such as / or /my-project) from homepage.
69
+    publicPath: publicPath,
70
+    // Point sourcemap entries to original disk location (format as URL on Windows)
71
+    devtoolModuleFilenameTemplate: info =>
72
+      path
73
+        .relative(paths.appSrc, info.absoluteResourcePath)
74
+        .replace(/\\/g, '/'),
75
+  },
76
+  resolve: {
77
+    // This allows you to set a fallback for where Webpack should look for modules.
78
+    // We placed these paths second because we want `node_modules` to "win"
79
+    // if there are any conflicts. This matches Node resolution mechanism.
80
+    // https://github.com/facebookincubator/create-react-app/issues/253
81
+    modules: ['node_modules', paths.appNodeModules].concat(
82
+      // It is guaranteed to exist because we tweak it in `env.js`
83
+      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
84
+    ),
85
+    // These are the reasonable defaults supported by the Node ecosystem.
86
+    // We also include JSX as a common component filename extension to support
87
+    // some tools, although we do not recommend using it, see:
88
+    // https://github.com/facebookincubator/create-react-app/issues/290
89
+    // `web` extension prefixes have been added for better support
90
+    // for React Native Web.
91
+    extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
92
+    alias: {
93
+      
94
+      // Support React Native Web
95
+      // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
96
+      'react-native': 'react-native-web',
97
+    },
98
+    plugins: [
99
+      // Prevents users from importing files from outside of src/ (or node_modules/).
100
+      // This often causes confusion because we only process files within src/ with babel.
101
+      // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
102
+      // please link the files into your node_modules/ and let module-resolution kick in.
103
+      // Make sure your source files are compiled, as they will not be processed in any way.
104
+      new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
105
+    ],
106
+  },
107
+  module: {
108
+    strictExportPresence: true,
109
+    rules: [
110
+      // TODO: Disable require.ensure as it's not a standard language feature.
111
+      // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
112
+      // { parser: { requireEnsure: false } },
113
+
114
+      // First, run the linter.
115
+      // It's important to do this before Babel processes the JS.
116
+      {
117
+        test: /\.(js|jsx|mjs)$/,
118
+        enforce: 'pre',
119
+        use: [
120
+          {
121
+            options: {
122
+              formatter: eslintFormatter,
123
+              eslintPath: require.resolve('eslint'),
124
+              
125
+            },
126
+            loader: require.resolve('eslint-loader'),
127
+          },
128
+        ],
129
+        include: paths.appSrc,
130
+      },
131
+      {
132
+        // "oneOf" will traverse all following loaders until one will
133
+        // match the requirements. When no loader matches it will fall
134
+        // back to the "file" loader at the end of the loader list.
135
+        oneOf: [
136
+          // "url" loader works just like "file" loader but it also embeds
137
+          // assets smaller than specified size as data URLs to avoid requests.
138
+          {
139
+            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
140
+            loader: require.resolve('url-loader'),
141
+            options: {
142
+              limit: 10000,
143
+              name: 'static/media/[name].[hash:8].[ext]',
144
+            },
145
+          },
146
+          // Process JS with Babel.
147
+          {
148
+            test: /\.(js|jsx|mjs)$/,
149
+            include: paths.appSrc,
150
+            loader: require.resolve('babel-loader'),
151
+            options: {
152
+              
153
+              compact: true,
154
+            },
155
+          },
156
+          // The notation here is somewhat confusing.
157
+          // "postcss" loader applies autoprefixer to our CSS.
158
+          // "css" loader resolves paths in CSS and adds assets as dependencies.
159
+          // "style" loader normally turns CSS into JS modules injecting <style>,
160
+          // but unlike in development configuration, we do something different.
161
+          // `ExtractTextPlugin` first applies the "postcss" and "css" loaders
162
+          // (second argument), then grabs the result CSS and puts it into a
163
+          // separate file in our build process. This way we actually ship
164
+          // a single CSS file in production instead of JS code injecting <style>
165
+          // tags. If you use code splitting, however, any async bundles will still
166
+          // use the "style" loader inside the async code so CSS from them won't be
167
+          // in the main CSS file.
168
+          {
169
+            test: /\.css$/,
170
+            loader: ExtractTextPlugin.extract(
171
+              Object.assign(
172
+                {
173
+                  fallback: {
174
+                    loader: require.resolve('style-loader'),
175
+                    options: {
176
+                      hmr: false,
177
+                    },
178
+                  },
179
+                  use: [
180
+                    {
181
+                      loader: require.resolve('css-loader'),
182
+                      options: {
183
+                        importLoaders: 1,
184
+                        minimize: true,
185
+                        sourceMap: shouldUseSourceMap,
186
+                      },
187
+                    },
188
+                    {
189
+                      loader: require.resolve('postcss-loader'),
190
+                      options: {
191
+                        // Necessary for external CSS imports to work
192
+                        // https://github.com/facebookincubator/create-react-app/issues/2677
193
+                        ident: 'postcss',
194
+                        plugins: () => [
195
+                          require('postcss-flexbugs-fixes'),
196
+                          autoprefixer({
197
+                            browsers: [
198
+                              '>1%',
199
+                              'last 4 versions',
200
+                              'Firefox ESR',
201
+                              'not ie < 9', // React doesn't support IE8 anyway
202
+                            ],
203
+                            flexbox: 'no-2009',
204
+                          }),
205
+                        ],
206
+                      },
207
+                    },
208
+                  ],
209
+                },
210
+                extractTextPluginOptions
211
+              )
212
+            ),
213
+            // Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
214
+          },
215
+          // "file" loader makes sure assets end up in the `build` folder.
216
+          // When you `import` an asset, you get its filename.
217
+          // This loader doesn't use a "test" so it will catch all modules
218
+          // that fall through the other loaders.
219
+          {
220
+            loader: require.resolve('file-loader'),
221
+            // Exclude `js` files to keep "css" loader working as it injects
222
+            // it's runtime that would otherwise processed through "file" loader.
223
+            // Also exclude `html` and `json` extensions so they get processed
224
+            // by webpacks internal loaders.
225
+            exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
226
+            options: {
227
+              name: 'static/media/[name].[hash:8].[ext]',
228
+            },
229
+          },
230
+          // ** STOP ** Are you adding a new loader?
231
+          // Make sure to add the new loader(s) before the "file" loader.
232
+        ],
233
+      },
234
+    ],
235
+  },
236
+  plugins: [
237
+    // Makes some environment variables available in index.html.
238
+    // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
239
+    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
240
+    // In production, it will be an empty string unless you specify "homepage"
241
+    // in `package.json`, in which case it will be the pathname of that URL.
242
+    new InterpolateHtmlPlugin(env.raw),
243
+    // Generates an `index.html` file with the <script> injected.
244
+    new HtmlWebpackPlugin({
245
+      inject: true,
246
+      template: paths.appHtml,
247
+      minify: {
248
+        removeComments: true,
249
+        collapseWhitespace: true,
250
+        removeRedundantAttributes: true,
251
+        useShortDoctype: true,
252
+        removeEmptyAttributes: true,
253
+        removeStyleLinkTypeAttributes: true,
254
+        keepClosingSlash: true,
255
+        minifyJS: true,
256
+        minifyCSS: true,
257
+        minifyURLs: true,
258
+      },
259
+    }),
260
+    // Makes some environment variables available to the JS code, for example:
261
+    // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
262
+    // It is absolutely essential that NODE_ENV was set to production here.
263
+    // Otherwise React will be compiled in the very slow development mode.
264
+    new webpack.DefinePlugin(env.stringified),
265
+    // Minify the code.
266
+    new webpack.optimize.UglifyJsPlugin({
267
+      compress: {
268
+        warnings: false,
269
+        // Disabled because of an issue with Uglify breaking seemingly valid code:
270
+        // https://github.com/facebookincubator/create-react-app/issues/2376
271
+        // Pending further investigation:
272
+        // https://github.com/mishoo/UglifyJS2/issues/2011
273
+        comparisons: false,
274
+      },
275
+      mangle: {
276
+        safari10: true,
277
+      },
278
+      output: {
279
+        comments: false,
280
+        // Turned on because emoji and regex is not minified properly using default
281
+        // https://github.com/facebookincubator/create-react-app/issues/2488
282
+        ascii_only: true,
283
+      },
284
+      sourceMap: shouldUseSourceMap,
285
+    }),
286
+    // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
287
+    new ExtractTextPlugin({
288
+      filename: cssFilename,
289
+    }),
290
+    // Generate a manifest file which contains a mapping of all asset filenames
291
+    // to their corresponding output file so that tools can pick it up without
292
+    // having to parse `index.html`.
293
+    new ManifestPlugin({
294
+      fileName: 'asset-manifest.json',
295
+    }),
296
+    // Generate a service worker script that will precache, and keep up to date,
297
+    // the HTML & assets that are part of the Webpack build.
298
+    new SWPrecacheWebpackPlugin({
299
+      // By default, a cache-busting query parameter is appended to requests
300
+      // used to populate the caches, to ensure the responses are fresh.
301
+      // If a URL is already hashed by Webpack, then there is no concern
302
+      // about it being stale, and the cache-busting can be skipped.
303
+      dontCacheBustUrlsMatching: /\.\w{8}\./,
304
+      filename: 'service-worker.js',
305
+      logger(message) {
306
+        if (message.indexOf('Total precache size is') === 0) {
307
+          // This message occurs for every build and is a bit too noisy.
308
+          return;
309
+        }
310
+        if (message.indexOf('Skipping static resource') === 0) {
311
+          // This message obscures real errors so we ignore it.
312
+          // https://github.com/facebookincubator/create-react-app/issues/2612
313
+          return;
314
+        }
315
+        console.log(message);
316
+      },
317
+      minify: true,
318
+      // For unknown URLs, fallback to the index page
319
+      navigateFallback: publicUrl + '/index.html',
320
+      // Ignores URLs starting from /__ (useful for Firebase):
321
+      // https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
322
+      navigateFallbackWhitelist: [/^(?!\/__).*/],
323
+      // Don't precache sourcemaps (they're large) and build asset manifest:
324
+      staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
325
+    }),
326
+    // Moment.js is an extremely popular library that bundles large locale files
327
+    // by default due to how Webpack interprets its code. This is a practical
328
+    // solution that requires the user to opt into importing specific locales.
329
+    // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
330
+    // You can remove this if you don't use Moment.js:
331
+    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
332
+  ],
333
+  // Some libraries import Node modules but don't use them in the browser.
334
+  // Tell Webpack to provide empty mocks for them so importing them works.
335
+  node: {
336
+    dgram: 'empty',
337
+    fs: 'empty',
338
+    net: 'empty',
339
+    tls: 'empty',
340
+    child_process: 'empty',
341
+  },
342
+};

+ 95
- 0
config/webpackDevServer.config.js View File

@@ -0,0 +1,95 @@
1
+'use strict';
2
+
3
+const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
4
+const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
5
+const ignoredFiles = require('react-dev-utils/ignoredFiles');
6
+const config = require('./webpack.config.dev');
7
+const paths = require('./paths');
8
+
9
+const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
10
+const host = process.env.HOST || '0.0.0.0';
11
+
12
+module.exports = function(proxy, allowedHost) {
13
+  return {
14
+    // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
15
+    // websites from potentially accessing local content through DNS rebinding:
16
+    // https://github.com/webpack/webpack-dev-server/issues/887
17
+    // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
18
+    // However, it made several existing use cases such as development in cloud
19
+    // environment or subdomains in development significantly more complicated:
20
+    // https://github.com/facebookincubator/create-react-app/issues/2271
21
+    // https://github.com/facebookincubator/create-react-app/issues/2233
22
+    // While we're investigating better solutions, for now we will take a
23
+    // compromise. Since our WDS configuration only serves files in the `public`
24
+    // folder we won't consider accessing them a vulnerability. However, if you
25
+    // use the `proxy` feature, it gets more dangerous because it can expose
26
+    // remote code execution vulnerabilities in backends like Django and Rails.
27
+    // So we will disable the host check normally, but enable it if you have
28
+    // specified the `proxy` setting. Finally, we let you override it if you
29
+    // really know what you're doing with a special environment variable.
30
+    disableHostCheck:
31
+      !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
32
+    // Enable gzip compression of generated files.
33
+    compress: true,
34
+    // Silence WebpackDevServer's own logs since they're generally not useful.
35
+    // It will still show compile warnings and errors with this setting.
36
+    clientLogLevel: 'none',
37
+    // By default WebpackDevServer serves physical files from current directory
38
+    // in addition to all the virtual build products that it serves from memory.
39
+    // This is confusing because those files won’t automatically be available in
40
+    // production build folder unless we copy them. However, copying the whole
41
+    // project directory is dangerous because we may expose sensitive files.
42
+    // Instead, we establish a convention that only files in `public` directory
43
+    // get served. Our build script will copy `public` into the `build` folder.
44
+    // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
45
+    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
46
+    // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
47
+    // Note that we only recommend to use `public` folder as an escape hatch
48
+    // for files like `favicon.ico`, `manifest.json`, and libraries that are
49
+    // for some reason broken when imported through Webpack. If you just want to
50
+    // use an image, put it in `src` and `import` it from JavaScript instead.
51
+    contentBase: paths.appPublic,
52
+    // By default files from `contentBase` will not trigger a page reload.
53
+    watchContentBase: true,
54
+    // Enable hot reloading server. It will provide /sockjs-node/ endpoint
55
+    // for the WebpackDevServer client so it can learn when the files were
56
+    // updated. The WebpackDevServer client is included as an entry point
57
+    // in the Webpack development configuration. Note that only changes
58
+    // to CSS are currently hot reloaded. JS changes will refresh the browser.
59
+    hot: true,
60
+    // It is important to tell WebpackDevServer to use the same "root" path
61
+    // as we specified in the config. In development, we always serve from /.
62
+    publicPath: config.output.publicPath,
63
+    // WebpackDevServer is noisy by default so we emit custom message instead
64
+    // by listening to the compiler events with `compiler.plugin` calls above.
65
+    quiet: true,
66
+    // Reportedly, this avoids CPU overload on some systems.
67
+    // https://github.com/facebookincubator/create-react-app/issues/293
68
+    // src/node_modules is not ignored to support absolute imports
69
+    // https://github.com/facebookincubator/create-react-app/issues/1065
70
+    watchOptions: {
71
+      ignored: ignoredFiles(paths.appSrc),
72
+    },
73
+    // Enable HTTPS if the HTTPS environment variable is set to 'true'
74
+    https: protocol === 'https',
75
+    host: host,
76
+    overlay: false,
77
+    historyApiFallback: {
78
+      // Paths with dots should still use the history fallback.
79
+      // See https://github.com/facebookincubator/create-react-app/issues/387.
80
+      disableDotRule: true,
81
+    },
82
+    public: allowedHost,
83
+    proxy,
84
+    before(app) {
85
+      // This lets us open files from the runtime error overlay.
86
+      app.use(errorOverlayMiddleware());
87
+      // This service worker file is effectively a 'no-op' that will reset any
88
+      // previous service worker registered for the same host:port combination.
89
+      // We do this in development to avoid hitting the production cache if
90
+      // it used the same host and port.
91
+      // https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
92
+      app.use(noopServiceWorkerMiddleware());
93
+    },
94
+  };
95
+};

+ 137
- 46
package-lock.json View File

@@ -4,6 +4,21 @@
4 4
   "lockfileVersion": 1,
5 5
   "requires": true,
6 6
   "dependencies": {
7
+    "@babel/runtime": {
8
+      "version": "7.0.0",
9
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
10
+      "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
11
+      "requires": {
12
+        "regenerator-runtime": "^0.12.0"
13
+      },
14
+      "dependencies": {
15
+        "regenerator-runtime": {
16
+          "version": "0.12.1",
17
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
18
+          "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
19
+        }
20
+      }
21
+    },
7 22
     "abab": {
8 23
       "version": "1.0.4",
9 24
       "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
@@ -1368,6 +1383,39 @@
1368 1383
         "repeat-element": "^1.1.2"
1369 1384
       }
1370 1385
     },
1386
+    "braft-convert": {
1387
+      "version": "2.0.2",
1388
+      "resolved": "https://registry.npmjs.org/braft-convert/-/braft-convert-2.0.2.tgz",
1389
+      "integrity": "sha512-i3dsu210MKYXa/458q3HfarVNMz8KFxCs5smzhnocqw+D56dhArvtINDWHwdiSzo2vhiATU5D07dAiS1MZQp7g==",
1390
+      "requires": {
1391
+        "draft-convert": "^2.0.0",
1392
+        "draft-js": "^0.10.3"
1393
+      }
1394
+    },
1395
+    "braft-editor": {
1396
+      "version": "2.0.5",
1397
+      "resolved": "https://registry.npmjs.org/braft-editor/-/braft-editor-2.0.5.tgz",
1398
+      "integrity": "sha512-YAEm+KPHuQZpUK2/SE/Z1pIBTzyTlIcMRHyW1ljwVkjUroOAI/IVnn2Se+b9E+Vdwzz3Bx7npDpXsrMgKB7vgQ==",
1399
+      "requires": {
1400
+        "@babel/runtime": "^7.0.0",
1401
+        "braft-convert": "^2.0.2",
1402
+        "braft-finder": "^0.0.9",
1403
+        "braft-utils": "^0.0.16",
1404
+        "draft-convert": "^2.0.0",
1405
+        "draft-js": "^0.10.3",
1406
+        "draftjs-utils": "^0.9.4"
1407
+      }
1408
+    },
1409
+    "braft-finder": {
1410
+      "version": "0.0.9",
1411
+      "resolved": "https://registry.npmjs.org/braft-finder/-/braft-finder-0.0.9.tgz",
1412
+      "integrity": "sha512-AIXHxp3dcfw0AOdUbYcXgIdVO6+IcuySZbrDemnUukUFr01G9pX9vLbYr+ESnI5CH8nYq6giQ1feIuVFcgNu7Q=="
1413
+    },
1414
+    "braft-utils": {
1415
+      "version": "0.0.16",
1416
+      "resolved": "https://registry.npmjs.org/braft-utils/-/braft-utils-0.0.16.tgz",
1417
+      "integrity": "sha512-ccN4XoHmoQe3OS2GdwmA6RFBMobgH3v312+ks5uKGsCyJjRTVrWmYbzS0i0FduhvTYNsq04ah6E+or01K+P0dA=="
1418
+    },
1371 1419
     "brorand": {
1372 1420
       "version": "1.1.0",
1373 1421
       "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
@@ -2945,6 +2993,30 @@
2945 2993
       "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-4.2.0.tgz",
2946 2994
       "integrity": "sha1-3vHxyl1gWdJKdm5YeULCEQbOEnU="
2947 2995
     },
2996
+    "draft-convert": {
2997
+      "version": "2.1.4",
2998
+      "resolved": "https://registry.npmjs.org/draft-convert/-/draft-convert-2.1.4.tgz",
2999
+      "integrity": "sha1-uIFwAOryUelVg8mHrdoIXWclSg0=",
3000
+      "requires": {
3001
+        "immutable": "~3.7.4",
3002
+        "invariant": "^2.2.1"
3003
+      }
3004
+    },
3005
+    "draft-js": {
3006
+      "version": "0.10.5",
3007
+      "resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.10.5.tgz",
3008
+      "integrity": "sha512-LE6jSCV9nkPhfVX2ggcRLA4FKs6zWq9ceuO/88BpXdNCS7mjRTgs0NsV6piUCJX9YxMsB9An33wnkMmU2sD2Zg==",
3009
+      "requires": {
3010
+        "fbjs": "^0.8.15",
3011
+        "immutable": "~3.7.4",
3012
+        "object-assign": "^4.1.0"
3013
+      }
3014
+    },
3015
+    "draftjs-utils": {
3016
+      "version": "0.9.4",
3017
+      "resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.9.4.tgz",
3018
+      "integrity": "sha512-KYjABSbGpJrwrwmxVj5UhfV37MF/p0QRxKIyL+/+QOaJ8J9z1FBKxkblThbpR0nJi9lxPQWGg+gh+v0dAsSCCg=="
3019
+    },
2948 3020
     "duplexer": {
2949 3021
       "version": "0.1.1",
2950 3022
       "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
@@ -3004,6 +3076,14 @@
3004 3076
       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
3005 3077
       "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
3006 3078
     },
3079
+    "encoding": {
3080
+      "version": "0.1.12",
3081
+      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
3082
+      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
3083
+      "requires": {
3084
+        "iconv-lite": "~0.4.13"
3085
+      }
3086
+    },
3007 3087
     "enhanced-resolve": {
3008 3088
       "version": "3.4.1",
3009 3089
       "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz",
@@ -3729,6 +3809,35 @@
3729 3809
         "bser": "^2.0.0"
3730 3810
       }
3731 3811
     },
3812
+    "fbjs": {
3813
+      "version": "0.8.17",
3814
+      "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
3815
+      "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
3816
+      "requires": {
3817
+        "core-js": "^1.0.0",
3818
+        "isomorphic-fetch": "^2.1.1",
3819
+        "loose-envify": "^1.0.0",
3820
+        "object-assign": "^4.1.0",
3821
+        "promise": "^7.1.1",
3822
+        "setimmediate": "^1.0.5",
3823
+        "ua-parser-js": "^0.7.18"
3824
+      },
3825
+      "dependencies": {
3826
+        "core-js": {
3827
+          "version": "1.2.7",
3828
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
3829
+          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
3830
+        },
3831
+        "promise": {
3832
+          "version": "7.3.1",
3833
+          "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
3834
+          "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
3835
+          "requires": {
3836
+            "asap": "~2.0.3"
3837
+          }
3838
+        }
3839
+      }
3840
+    },
3732 3841
     "figures": {
3733 3842
       "version": "2.0.0",
3734 3843
       "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -4913,6 +5022,11 @@
4913 5022
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
4914 5023
       "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug=="
4915 5024
     },
5025
+    "immutable": {
5026
+      "version": "3.7.6",
5027
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
5028
+      "integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks="
5029
+    },
4916 5030
     "import-lazy": {
4917 5031
       "version": "2.1.0",
4918 5032
       "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
@@ -5349,6 +5463,15 @@
5349 5463
         "isarray": "1.0.0"
5350 5464
       }
5351 5465
     },
5466
+    "isomorphic-fetch": {
5467
+      "version": "2.2.1",
5468
+      "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
5469
+      "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
5470
+      "requires": {
5471
+        "node-fetch": "^1.0.1",
5472
+        "whatwg-fetch": ">=0.10.0"
5473
+      }
5474
+    },
5352 5475
     "isstream": {
5353 5476
       "version": "0.1.2",
5354 5477
       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -6427,6 +6550,15 @@
6427 6550
         "lower-case": "^1.1.1"
6428 6551
       }
6429 6552
     },
6553
+    "node-fetch": {
6554
+      "version": "1.7.3",
6555
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
6556
+      "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
6557
+      "requires": {
6558
+        "encoding": "^0.1.11",
6559
+        "is-stream": "^1.0.1"
6560
+      }
6561
+    },
6430 6562
     "node-forge": {
6431 6563
       "version": "0.7.5",
6432 6564
       "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz",
@@ -8423,52 +8555,6 @@
8423 8555
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.1.tgz",
8424 8556
       "integrity": "sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw=="
8425 8557
     },
8426
-    "react-scripts": {
8427
-      "version": "1.1.5",
8428
-      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-1.1.5.tgz",
8429
-      "integrity": "sha512-ZXqnbg+kLRaacAkjuedMFTgKu9lNltMDDsuwn37CTV7X2tuZQmDKi08eI3LYvtpjqh5vm8/6BhwHRHkRtvMyJg==",
8430
-      "requires": {
8431
-        "autoprefixer": "7.1.6",
8432
-        "babel-core": "6.26.0",
8433
-        "babel-eslint": "7.2.3",
8434
-        "babel-jest": "20.0.3",
8435
-        "babel-loader": "7.1.2",
8436
-        "babel-preset-react-app": "^3.1.2",
8437
-        "babel-runtime": "6.26.0",
8438
-        "case-sensitive-paths-webpack-plugin": "2.1.1",
8439
-        "chalk": "1.1.3",
8440
-        "css-loader": "0.28.7",
8441
-        "dotenv": "4.0.0",
8442
-        "dotenv-expand": "4.2.0",
8443
-        "eslint": "4.10.0",
8444
-        "eslint-config-react-app": "^2.1.0",
8445
-        "eslint-loader": "1.9.0",
8446
-        "eslint-plugin-flowtype": "2.39.1",
8447
-        "eslint-plugin-import": "2.8.0",
8448
-        "eslint-plugin-jsx-a11y": "5.1.1",
8449
-        "eslint-plugin-react": "7.4.0",
8450
-        "extract-text-webpack-plugin": "3.0.2",
8451
-        "file-loader": "1.1.5",
8452
-        "fs-extra": "3.0.1",
8453
-        "fsevents": "^1.1.3",
8454
-        "html-webpack-plugin": "2.29.0",
8455
-        "jest": "20.0.4",
8456
-        "object-assign": "4.1.1",
8457
-        "postcss-flexbugs-fixes": "3.2.0",
8458
-        "postcss-loader": "2.0.8",
8459
-        "promise": "8.0.1",
8460
-        "raf": "3.4.0",
8461
-        "react-dev-utils": "^5.0.2",
8462
-        "resolve": "1.6.0",
8463
-        "style-loader": "0.19.0",
8464
-        "sw-precache-webpack-plugin": "0.11.4",
8465
-        "url-loader": "0.6.2",
8466
-        "webpack": "3.8.1",
8467
-        "webpack-dev-server": "2.11.3",
8468
-        "webpack-manifest-plugin": "1.3.2",
8469
-        "whatwg-fetch": "2.0.3"
8470
-      }
8471
-    },
8472 8558
     "read-pkg": {
8473 8559
       "version": "1.1.0",
8474 8560
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
@@ -9847,6 +9933,11 @@
9847 9933
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
9848 9934
       "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
9849 9935
     },
9936
+    "ua-parser-js": {
9937
+      "version": "0.7.18",
9938
+      "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz",
9939
+      "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA=="
9940
+    },
9850 9941
     "uglify-js": {
9851 9942
       "version": "3.4.9",
9852 9943
       "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",

+ 84
- 5
package.json View File

@@ -3,14 +3,93 @@
3 3
   "version": "0.1.0",
4 4
   "private": true,
5 5
   "dependencies": {
6
+    "autoprefixer": "7.1.6",
7
+    "babel-core": "6.26.0",
8
+    "babel-eslint": "7.2.3",
9
+    "babel-jest": "20.0.3",
10
+    "babel-loader": "7.1.2",
11
+    "babel-preset-react-app": "^3.1.2",
12
+    "babel-runtime": "6.26.0",
13
+    "braft-editor": "^2.0.5",
14
+    "case-sensitive-paths-webpack-plugin": "2.1.1",
15
+    "chalk": "1.1.3",
16
+    "css-loader": "0.28.7",
17
+    "dotenv": "4.0.0",
18
+    "dotenv-expand": "4.2.0",
19
+    "eslint": "4.10.0",
20
+    "eslint-config-react-app": "^2.1.0",
21
+    "eslint-loader": "1.9.0",
22
+    "eslint-plugin-flowtype": "2.39.1",
23
+    "eslint-plugin-import": "2.8.0",
24
+    "eslint-plugin-jsx-a11y": "5.1.1",
25
+    "eslint-plugin-react": "7.4.0",
26
+    "extract-text-webpack-plugin": "3.0.2",
27
+    "file-loader": "1.1.5",
28
+    "fs-extra": "3.0.1",
29
+    "html-webpack-plugin": "2.29.0",
30
+    "jest": "20.0.4",
31
+    "object-assign": "4.1.1",
32
+    "postcss-flexbugs-fixes": "3.2.0",
33
+    "postcss-loader": "2.0.8",
34
+    "promise": "8.0.1",
35
+    "raf": "3.4.0",
6 36
     "react": "^16.5.0",
37
+    "react-dev-utils": "^5.0.2",
7 38
     "react-dom": "^16.5.0",
8
-    "react-scripts": "1.1.5"
39
+    "resolve": "1.6.0",
40
+    "style-loader": "0.19.0",
41
+    "sw-precache-webpack-plugin": "0.11.4",
42
+    "url-loader": "0.6.2",
43
+    "webpack": "3.8.1",
44
+    "webpack-dev-server": "2.11.3",
45
+    "webpack-manifest-plugin": "1.3.2",
46
+    "whatwg-fetch": "2.0.3"
9 47
   },
10 48
   "scripts": {
11
-    "start": "react-scripts start",
12
-    "build": "react-scripts build",
13
-    "test": "react-scripts test --env=jsdom",
14
-    "eject": "react-scripts eject"
49
+    "start": "node scripts/start.js",
50
+    "build": "node scripts/build.js",
51
+    "test": "node scripts/test.js --env=jsdom"
52
+  },
53
+  "jest": {
54
+    "collectCoverageFrom": [
55
+      "src/**/*.{js,jsx,mjs}"
56
+    ],
57
+    "setupFiles": [
58
+      "<rootDir>/config/polyfills.js"
59
+    ],
60
+    "testMatch": [
61
+      "<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}",
62
+      "<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}"
63
+    ],
64
+    "testEnvironment": "node",
65
+    "testURL": "http://localhost",
66
+    "transform": {
67
+      "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
68
+      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
69
+      "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
70
+    },
71
+    "transformIgnorePatterns": [
72
+      "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$"
73
+    ],
74
+    "moduleNameMapper": {
75
+      "^react-native$": "react-native-web"
76
+    },
77
+    "moduleFileExtensions": [
78
+      "web.js",
79
+      "js",
80
+      "json",
81
+      "web.jsx",
82
+      "jsx",
83
+      "node",
84
+      "mjs"
85
+    ]
86
+  },
87
+  "babel": {
88
+    "presets": [
89
+      "react-app"
90
+    ]
91
+  },
92
+  "eslintConfig": {
93
+    "extends": "react-app"
15 94
   }
16 95
 }

+ 150
- 0
scripts/build.js View File

@@ -0,0 +1,150 @@
1
+'use strict';
2
+
3
+// Do this as the first thing so that any code reading it knows the right env.
4
+process.env.BABEL_ENV = 'production';
5
+process.env.NODE_ENV = 'production';
6
+
7
+// Makes the script crash on unhandled rejections instead of silently
8
+// ignoring them. In the future, promise rejections that are not handled will
9
+// terminate the Node.js process with a non-zero exit code.
10
+process.on('unhandledRejection', err => {
11
+  throw err;
12
+});
13
+
14
+// Ensure environment variables are read.
15
+require('../config/env');
16
+
17
+const path = require('path');
18
+const chalk = require('chalk');
19
+const fs = require('fs-extra');
20
+const webpack = require('webpack');
21
+const config = require('../config/webpack.config.prod');
22
+const paths = require('../config/paths');
23
+const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
24
+const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
25
+const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
26
+const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
27
+const printBuildError = require('react-dev-utils/printBuildError');
28
+
29
+const measureFileSizesBeforeBuild =
30
+  FileSizeReporter.measureFileSizesBeforeBuild;
31
+const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
32
+const useYarn = fs.existsSync(paths.yarnLockFile);
33
+
34
+// These sizes are pretty large. We'll warn for bundles exceeding them.
35
+const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
36
+const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
37
+
38
+// Warn and crash if required files are missing
39
+if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
40
+  process.exit(1);
41
+}
42
+
43
+// First, read the current file sizes in build directory.
44
+// This lets us display how much they changed later.
45
+measureFileSizesBeforeBuild(paths.appBuild)
46
+  .then(previousFileSizes => {
47
+    // Remove all content but keep the directory so that
48
+    // if you're in it, you don't end up in Trash
49
+    fs.emptyDirSync(paths.appBuild);
50
+    // Merge with the public folder
51
+    copyPublicFolder();
52
+    // Start the webpack build
53
+    return build(previousFileSizes);
54
+  })
55
+  .then(
56
+    ({ stats, previousFileSizes, warnings }) => {
57
+      if (warnings.length) {
58
+        console.log(chalk.yellow('Compiled with warnings.\n'));
59
+        console.log(warnings.join('\n\n'));
60
+        console.log(
61
+          '\nSearch for the ' +
62
+            chalk.underline(chalk.yellow('keywords')) +
63
+            ' to learn more about each warning.'
64
+        );
65
+        console.log(
66
+          'To ignore, add ' +
67
+            chalk.cyan('// eslint-disable-next-line') +
68
+            ' to the line before.\n'
69
+        );
70
+      } else {
71
+        console.log(chalk.green('Compiled successfully.\n'));
72
+      }
73
+
74
+      console.log('File sizes after gzip:\n');
75
+      printFileSizesAfterBuild(
76
+        stats,
77
+        previousFileSizes,
78
+        paths.appBuild,
79
+        WARN_AFTER_BUNDLE_GZIP_SIZE,
80
+        WARN_AFTER_CHUNK_GZIP_SIZE
81
+      );
82
+      console.log();
83
+
84
+      const appPackage = require(paths.appPackageJson);
85
+      const publicUrl = paths.publicUrl;
86
+      const publicPath = config.output.publicPath;
87
+      const buildFolder = path.relative(process.cwd(), paths.appBuild);
88
+      printHostingInstructions(
89
+        appPackage,
90
+        publicUrl,
91
+        publicPath,
92
+        buildFolder,
93
+        useYarn
94
+      );
95
+    },
96
+    err => {
97
+      console.log(chalk.red('Failed to compile.\n'));
98
+      printBuildError(err);
99
+      process.exit(1);
100
+    }
101
+  );
102
+
103
+// Create the production build and print the deployment instructions.
104
+function build(previousFileSizes) {
105
+  console.log('Creating an optimized production build...');
106
+
107
+  let compiler = webpack(config);
108
+  return new Promise((resolve, reject) => {
109
+    compiler.run((err, stats) => {
110
+      if (err) {
111
+        return reject(err);
112
+      }
113
+      const messages = formatWebpackMessages(stats.toJson({}, true));
114
+      if (messages.errors.length) {
115
+        // Only keep the first error. Others are often indicative
116
+        // of the same problem, but confuse the reader with noise.
117
+        if (messages.errors.length > 1) {
118
+          messages.errors.length = 1;
119
+        }
120
+        return reject(new Error(messages.errors.join('\n\n')));
121
+      }
122
+      if (
123
+        process.env.CI &&
124
+        (typeof process.env.CI !== 'string' ||
125
+          process.env.CI.toLowerCase() !== 'false') &&
126
+        messages.warnings.length
127
+      ) {
128
+        console.log(
129
+          chalk.yellow(
130
+            '\nTreating warnings as errors because process.env.CI = true.\n' +
131
+              'Most CI servers set it automatically.\n'
132
+          )
133
+        );
134
+        return reject(new Error(messages.warnings.join('\n\n')));
135
+      }
136
+      return resolve({
137
+        stats,
138
+        previousFileSizes,
139
+        warnings: messages.warnings,
140
+      });
141
+    });
142
+  });
143
+}
144
+
145
+function copyPublicFolder() {
146
+  fs.copySync(paths.appPublic, paths.appBuild, {
147
+    dereference: true,
148
+    filter: file => file !== paths.appHtml,
149
+  });
150
+}

+ 107
- 0
scripts/start.js View File

@@ -0,0 +1,107 @@
1
+'use strict';
2
+
3
+// Do this as the first thing so that any code reading it knows the right env.
4
+process.env.BABEL_ENV = 'development';
5
+process.env.NODE_ENV = 'development';
6
+
7
+// Makes the script crash on unhandled rejections instead of silently
8
+// ignoring them. In the future, promise rejections that are not handled will
9
+// terminate the Node.js process with a non-zero exit code.
10
+process.on('unhandledRejection', err => {
11
+  throw err;
12
+});
13
+
14
+// Ensure environment variables are read.
15
+require('../config/env');
16
+
17
+const fs = require('fs');
18
+const chalk = require('chalk');
19
+const webpack = require('webpack');
20
+const WebpackDevServer = require('webpack-dev-server');
21
+const clearConsole = require('react-dev-utils/clearConsole');
22
+const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
23
+const {
24
+  choosePort,
25
+  createCompiler,
26
+  prepareProxy,
27
+  prepareUrls,
28
+} = require('react-dev-utils/WebpackDevServerUtils');
29
+const openBrowser = require('react-dev-utils/openBrowser');
30
+const paths = require('../config/paths');
31
+const config = require('../config/webpack.config.dev');
32
+const createDevServerConfig = require('../config/webpackDevServer.config');
33
+
34
+const useYarn = fs.existsSync(paths.yarnLockFile);
35
+const isInteractive = process.stdout.isTTY;
36
+
37
+// Warn and crash if required files are missing
38
+if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
39
+  process.exit(1);
40
+}
41
+
42
+// Tools like Cloud9 rely on this.
43
+const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
44
+const HOST = process.env.HOST || '0.0.0.0';
45
+
46
+if (process.env.HOST) {
47
+  console.log(
48
+    chalk.cyan(
49
+      `Attempting to bind to HOST environment variable: ${chalk.yellow(
50
+        chalk.bold(process.env.HOST)
51
+      )}`
52
+    )
53
+  );
54
+  console.log(
55
+    `If this was unintentional, check that you haven't mistakenly set it in your shell.`
56
+  );
57
+  console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`);
58
+  console.log();
59
+}
60
+
61
+// We attempt to use the default port but if it is busy, we offer the user to
62
+// run on a different port. `choosePort()` Promise resolves to the next free port.
63
+choosePort(HOST, DEFAULT_PORT)
64
+  .then(port => {
65
+    if (port == null) {
66
+      // We have not found a port.
67
+      return;
68
+    }
69
+    const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
70
+    const appName = require(paths.appPackageJson).name;
71
+    const urls = prepareUrls(protocol, HOST, port);
72
+    // Create a webpack compiler that is configured with custom messages.
73
+    const compiler = createCompiler(webpack, config, appName, urls, useYarn);
74
+    // Load proxy config
75
+    const proxySetting = require(paths.appPackageJson).proxy;
76
+    const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
77
+    // Serve webpack assets generated by the compiler over a web sever.
78
+    const serverConfig = createDevServerConfig(
79
+      proxyConfig,
80
+      urls.lanUrlForConfig
81
+    );
82
+    const devServer = new WebpackDevServer(compiler, serverConfig);
83
+    // Launch WebpackDevServer.
84
+    devServer.listen(port, HOST, err => {
85
+      if (err) {
86
+        return console.log(err);
87
+      }
88
+      if (isInteractive) {
89
+        clearConsole();
90
+      }
91
+      console.log(chalk.cyan('Starting the development server...\n'));
92
+      openBrowser(urls.localUrlForBrowser);
93
+    });
94
+
95
+    ['SIGINT', 'SIGTERM'].forEach(function(sig) {
96
+      process.on(sig, function() {
97
+        devServer.close();
98
+        process.exit();
99
+      });
100
+    });
101
+  })
102
+  .catch(err => {
103
+    if (err && err.message) {
104
+      console.log(err.message);
105
+    }
106
+    process.exit(1);
107
+  });

+ 27
- 0
scripts/test.js View File

@@ -0,0 +1,27 @@
1
+'use strict';
2
+
3
+// Do this as the first thing so that any code reading it knows the right env.
4
+process.env.BABEL_ENV = 'test';
5
+process.env.NODE_ENV = 'test';
6
+process.env.PUBLIC_URL = '';
7
+
8
+// Makes the script crash on unhandled rejections instead of silently
9
+// ignoring them. In the future, promise rejections that are not handled will
10
+// terminate the Node.js process with a non-zero exit code.
11
+process.on('unhandledRejection', err => {
12
+  throw err;
13
+});
14
+
15
+// Ensure environment variables are read.
16
+require('../config/env');
17
+
18
+const jest = require('jest');
19
+let argv = process.argv.slice(2);
20
+
21
+// Watch unless on CI or in coverage mode
22
+if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23
+  argv.push('--watch');
24
+}
25
+
26
+
27
+jest.run(argv);

+ 1
- 0
source/braft-editor/dist/index.css
File diff suppressed because it is too large
View File


+ 4459
- 0
source/braft-editor/dist/index.js
File diff suppressed because it is too large
View File


+ 1
- 0
source/braft-editor/dist/index.js.map
File diff suppressed because it is too large
View File


+ 1
- 0
source/react-mde/.dockerignore View File

@@ -0,0 +1 @@
1
+node_modules

+ 8
- 0
source/react-mde/.gitignore View File

@@ -0,0 +1,8 @@
1
+# Logs
2
+logs
3
+*.log*
4
+.idea
5
+/lib/
6
+node_modules
7
+/.vscode/
8
+ts-build/

+ 19
- 0
source/react-mde/.npmignore View File

@@ -0,0 +1,19 @@
1
+src
2
+.npmignore
3
+.idea
4
+demo
5
+assets
6
+npm-debug.*
7
+.debug.log
8
+webpack.*
9
+gulpfile.ts
10
+webpack*
11
+tsconfig*
12
+*.lock
13
+docs
14
+test
15
+gulpfile.js
16
+readme.md
17
+tslint.json
18
+ts-build
19
+.storybook

+ 1
- 0
source/react-mde/.storybook/addons.js View File

@@ -0,0 +1 @@
1
+import '@storybook/addon-storysource/register';

+ 16
- 0
source/react-mde/.storybook/config.js View File

@@ -0,0 +1,16 @@
1
+import {configure} from "@storybook/react";
2
+import "../node_modules/draft-js/dist/Draft.css";
3
+import "../src/styles/react-mde-all.scss";
4
+import "./styles/demo.scss";
5
+
6
+function loadStories() {
7
+    require("./stories/layouts/VerticalLayoutStory.tsx");
8
+    require("./stories/layouts/HorizontalLayoutStory.tsx");
9
+    require("./stories/layouts/TabbedLayoutStory.tsx");
10
+    require("./stories/layouts/NoPreviewLayoutStory.tsx");
11
+    require("./stories/customization/commandIcon/EmojiIconsStory.tsx");
12
+    //WIP
13
+    require("./stories/customization/commandButton/CustomButtonStory.tsx");
14
+}
15
+
16
+configure(loadStories, module);

+ 1
- 0
source/react-mde/.storybook/preview-head.html View File

@@ -0,0 +1 @@
1
+<script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>

+ 73
- 0
source/react-mde/.storybook/stories/customization/commandButton/ButtonComponent.tsx View File

@@ -0,0 +1,73 @@
1
+import * as React from "react";
2
+
3
+const containerStyle: React.CSSProperties = {
4
+    width: "200px",
5
+    border: "1px solid #c8ccd0",
6
+    height: "50px",
7
+    position: "absolute",
8
+    background: "#fff",
9
+    zIndex: 999,
10
+    padding: "5px",
11
+};
12
+
13
+const buttonStyle = {
14
+    textAlign: "center",
15
+    width: "100%",
16
+};
17
+
18
+const imagePlaceholder = "https://www.comet.ml/images/logo_comet_dark.png";
19
+
20
+export interface Props {
21
+    onTextChange: (value: string) => void;
22
+    handleSubmit: () => void;
23
+}
24
+
25
+export interface State {
26
+    isOpen: boolean;
27
+}
28
+
29
+export default class SimpleForm extends React.Component<Props, State> {
30
+    constructor(props) {
31
+        super(props);
32
+        this.state = {
33
+            isOpen: false,
34
+        };
35
+
36
+        props.setValues(imagePlaceholder);
37
+    }
38
+
39
+    handleClick = () => {
40
+        const {isOpen} = this.state;
41
+        this.setState({isOpen: !isOpen});
42
+    }
43
+
44
+    handleTextChange = (e) => {
45
+        this.props.onTextChange(e.target.value);
46
+    }
47
+
48
+    handleSubmit = () => {
49
+        this.props.handleSubmit();
50
+        this.setState({isOpen: false});
51
+    }
52
+
53
+    render() {
54
+        const {isOpen} = this.state;
55
+
56
+        return (
57
+            <div>
58
+                <button onClick={this.handleClick}> Upload Image</button>
59
+                {isOpen &&
60
+                <div style={containerStyle}>
61
+                    <input
62
+                        type="text"
63
+                        onChange={this.handleTextChange}
64
+                        style={{width: "100%"}}
65
+                        value={imagePlaceholder}
66
+                    />
67
+                    <button onClick={this.handleSubmit} style={buttonStyle}>Submit</button>
68
+                </div>
69
+                }
70
+            </div>
71
+        );
72
+    }
73
+}

+ 51
- 0
source/react-mde/.storybook/stories/customization/commandButton/CustomButtonStory.tsx View File

@@ -0,0 +1,51 @@
1
+import * as React from "react";
2
+import * as Showdown from "showdown";
3
+import ReactMde, {ReactMdeTypes} from "../../../../src/index";
4
+import {storiesOf} from "@storybook/react";
5
+import UploadImageCommand from "./UploadImageCommand";
6
+
7
+const commands = [[UploadImageCommand]];
8
+
9
+interface State {
10
+    mdeState: ReactMdeTypes.MdeState;
11
+}
12
+
13
+class VerticalLayoutStoryComponent extends React.Component<{}, State> {
14
+    converter: Showdown.Converter;
15
+
16
+    constructor(props) {
17
+        super(props);
18
+        this.state = {
19
+            mdeState: {
20
+                markdown: "Custom Button with functionality",
21
+            },
22
+        };
23
+        this.converter = new Showdown.Converter({
24
+            tables: true,
25
+            simplifiedAutoLink: true,
26
+            strikethrough: true,
27
+            tasklists: true,
28
+        });
29
+    }
30
+
31
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
32
+        this.setState({mdeState});
33
+    }
34
+
35
+    render() {
36
+        return (
37
+            <ReactMde
38
+                layout="vertical"
39
+                onChange={this.handleValueChange}
40
+                editorState={this.state.mdeState}
41
+                generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
42
+                commands={commands}
43
+            />
44
+        );
45
+    }
46
+}
47
+
48
+storiesOf("Customization", module)
49
+    .add("Command button", () => (
50
+        <VerticalLayoutStoryComponent/>
51
+    ));

+ 36
- 0
source/react-mde/.storybook/stories/customization/commandButton/UploadImageCommand.tsx View File

@@ -0,0 +1,36 @@
1
+import * as React from "react";
2
+import {Command} from "../../../../src/types";
3
+import {insertText} from "../../../../src/util/MarkdownUtil";
4
+import {getMarkdownStateFromDraftState, buildNewDraftState} from "../../../../src/util/DraftUtil";
5
+import ButtonComponent from "./ButtonComponent";
6
+
7
+let myVal = "def";
8
+const UploadImageCommand: Command = {
9
+
10
+    buttonContentBuilder: ({iconProvider}) => iconProvider("bold"),
11
+    buttonProps: {
12
+        "aria-label": "Add bold text",
13
+        "setValues": (value) => {
14
+            myVal = value;
15
+        },
16
+    },
17
+    execute: (state) => {
18
+        const {text, selection} = getMarkdownStateFromDraftState(state);
19
+        const {newText, insertionLength} = insertText(text, "![", selection.start);
20
+        const finalText = insertText(newText, `](${myVal})`, selection.end + insertionLength).newText;
21
+
22
+        return buildNewDraftState(
23
+            state,
24
+            {
25
+                text: finalText,
26
+                selection: {
27
+                    start: selection.start + insertionLength,
28
+                    end: selection.end + insertionLength,
29
+                },
30
+            },
31
+        );
32
+    },
33
+    buttonComponentClass: ButtonComponent,
34
+};
35
+
36
+export default UploadImageCommand;

+ 66
- 0
source/react-mde/.storybook/stories/customization/commandIcon/EmojiIconsStory.tsx View File

@@ -0,0 +1,66 @@
1
+import * as React from "react";
2
+import * as Showdown from "showdown";
3
+import ReactMde, {ReactMdeTypes} from "../../../../src/index";
4
+import {storiesOf} from "@storybook/react";
5
+
6
+interface State {
7
+    mdeState: ReactMdeTypes.MdeState;
8
+}
9
+
10
+const icons = {
11
+  "bold": (<strong>B</strong>),
12
+  "heading": "H",
13
+  "italic": (<em>I</em>),
14
+  "strikethrough": (<del>S</del>),
15
+  "link": "🔗",
16
+  "quote-right": (<strong>”</strong>),
17
+  "code": "🤓",
18
+  "image": "📸",
19
+  "list-ul": "⏺",
20
+  "list-ol": "#️⃣",
21
+  "tasks": "📝",
22
+};
23
+
24
+const iconProvider = (name) => {
25
+  return icons[name] || "❓";
26
+};
27
+
28
+class EmojiIconsStory extends React.Component<{}, State> {
29
+    converter: Showdown.Converter;
30
+
31
+    constructor(props) {
32
+        super(props);
33
+        this.state = {
34
+            mdeState: {
35
+                markdown: "**Hello world!**",
36
+            },
37
+        };
38
+        this.converter = new Showdown.Converter({
39
+            tables: true,
40
+            simplifiedAutoLink: true,
41
+            strikethrough: true,
42
+            tasklists: true,
43
+        });
44
+    }
45
+
46
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
47
+        this.setState({mdeState});
48
+    }
49
+
50
+    render() {
51
+        return (
52
+            <ReactMde
53
+                layout="horizontal"
54
+                buttonContentOptions={{ iconProvider }}
55
+                onChange={this.handleValueChange}
56
+                editorState={this.state.mdeState}
57
+                generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
58
+            />
59
+        );
60
+    }
61
+}
62
+
63
+storiesOf("Customization", module)
64
+    .add("Command emoji", () => (
65
+        <EmojiIconsStory/>
66
+    ));

+ 47
- 0
source/react-mde/.storybook/stories/layouts/HorizontalLayoutStory.tsx View File

@@ -0,0 +1,47 @@
1
+import * as React from "react";
2
+import * as Showdown from "showdown";
3
+import ReactMde, {ReactMdeTypes} from "../../../src/index";
4
+import {storiesOf} from "@storybook/react";
5
+
6
+interface State {
7
+    mdeState: ReactMdeTypes.MdeState;
8
+}
9
+
10
+class VerticalLayoutStoryComponent extends React.Component<{}, State> {
11
+    converter: Showdown.Converter;
12
+
13
+    constructor(props) {
14
+        super(props);
15
+        this.state = {
16
+            mdeState: {
17
+                markdown: "**Hello world!**",
18
+            },
19
+        };
20
+        this.converter = new Showdown.Converter({
21
+            tables: true,
22
+            simplifiedAutoLink: true,
23
+            strikethrough: true,
24
+            tasklists: true,
25
+        });
26
+    }
27
+
28
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
29
+        this.setState({mdeState});
30
+    }
31
+
32
+    render() {
33
+        return (
34
+            <ReactMde
35
+                layout="horizontal"
36
+                onChange={this.handleValueChange}
37
+                editorState={this.state.mdeState}
38
+                generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
39
+            />
40
+        );
41
+    }
42
+}
43
+
44
+storiesOf("Layouts", module)
45
+    .add("horizontal", () => (
46
+        <VerticalLayoutStoryComponent/>
47
+    ));

+ 47
- 0
source/react-mde/.storybook/stories/layouts/NoPreviewLayoutStory.tsx View File

@@ -0,0 +1,47 @@
1
+import * as React from "react";
2
+import * as Showdown from "showdown";
3
+import ReactMde, {ReactMdeTypes} from "../../../src/index";
4
+import {storiesOf} from "@storybook/react";
5
+
6
+interface State {
7
+    mdeState: ReactMdeTypes.MdeState;
8
+}
9
+
10
+class VerticalLayoutStoryComponent extends React.Component<{}, State> {
11
+    converter: Showdown.Converter;
12
+
13
+    constructor(props) {
14
+        super(props);
15
+        this.state = {
16
+            mdeState: {
17
+                markdown: "**Hello world!**",
18
+            },
19
+        };
20
+        this.converter = new Showdown.Converter({
21
+            tables: true,
22
+            simplifiedAutoLink: true,
23
+            strikethrough: true,
24
+            tasklists: true,
25
+        });
26
+    }
27
+
28
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
29
+        this.setState({mdeState});
30
+    }
31
+
32
+    render() {
33
+        return (
34
+            <ReactMde
35
+                layout="noPreview"
36
+                onChange={this.handleValueChange}
37
+                editorState={this.state.mdeState}
38
+                generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
39
+            />
40
+        );
41
+    }
42
+}
43
+
44
+storiesOf("Layouts", module)
45
+    .add("noPreview", () => (
46
+        <VerticalLayoutStoryComponent/>
47
+    ));

+ 47
- 0
source/react-mde/.storybook/stories/layouts/TabbedLayoutStory.tsx View File

@@ -0,0 +1,47 @@
1
+import * as React from "react";
2
+import * as Showdown from "showdown";
3
+import ReactMde, {ReactMdeTypes} from "../../../src/index";
4
+import {storiesOf} from "@storybook/react";
5
+
6
+interface State {
7
+    mdeState: ReactMdeTypes.MdeState;
8
+}
9
+
10
+class VerticalLayoutStoryComponent extends React.Component<{}, State> {
11
+    converter: Showdown.Converter;
12
+
13
+    constructor(props) {
14
+        super(props);
15
+        this.state = {
16
+            mdeState: {
17
+                markdown: "**Hello world!**",
18
+            },
19
+        };
20
+        this.converter = new Showdown.Converter({
21
+            tables: true,
22
+            simplifiedAutoLink: true,
23
+            strikethrough: true,
24
+            tasklists: true,
25
+        });
26
+    }
27
+
28
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
29
+        this.setState({mdeState});
30
+    }
31
+
32
+    render() {
33
+        return (
34
+            <ReactMde
35
+                layout="tabbed"
36
+                onChange={this.handleValueChange}
37
+                editorState={this.state.mdeState}
38
+                generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
39
+            />
40
+        );
41
+    }
42
+}
43
+
44
+storiesOf("Layouts", module)
45
+    .add("tabbed", () => (
46
+        <VerticalLayoutStoryComponent/>
47
+    ));

+ 47
- 0
source/react-mde/.storybook/stories/layouts/VerticalLayoutStory.tsx View File

@@ -0,0 +1,47 @@
1
+import * as React from "react";
2
+import * as Showdown from "showdown";
3
+import ReactMde, {ReactMdeTypes} from "../../../src/index";
4
+import {storiesOf} from "@storybook/react";
5
+
6
+interface State {
7
+    mdeState: ReactMdeTypes.MdeState;
8
+}
9
+
10
+class VerticalLayoutStoryComponent extends React.Component<{}, State> {
11
+    converter: Showdown.Converter;
12
+
13
+    constructor(props) {
14
+        super(props);
15
+        this.state = {
16
+            mdeState: {
17
+                markdown: "**Hello world!**",
18
+            },
19
+        };
20
+        this.converter = new Showdown.Converter({
21
+            tables: true,
22
+            simplifiedAutoLink: true,
23
+            strikethrough: true,
24
+            tasklists: true,
25
+        });
26
+    }
27
+
28
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
29
+        this.setState({mdeState});
30
+    }
31
+
32
+    render() {
33
+        return (
34
+            <ReactMde
35
+                layout="vertical"
36
+                onChange={this.handleValueChange}
37
+                editorState={this.state.mdeState}
38
+                generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
39
+            />
40
+        );
41
+    }
42
+}
43
+
44
+storiesOf("Layouts", module)
45
+    .add("vertical", () => (
46
+        <VerticalLayoutStoryComponent/>
47
+    ));

+ 6
- 0
source/react-mde/.storybook/styles/demo.scss View File

@@ -0,0 +1,6 @@
1
+@import 'variables.scss';
2
+
3
+body {
4
+    font-size: 14px;
5
+    font-family: sans-serif;
6
+}

+ 1
- 0
source/react-mde/.storybook/styles/variables.scss View File

@@ -0,0 +1 @@
1
+$default-border-color: rgba(0, 0, 0, .125) !default;

+ 40
- 0
source/react-mde/.storybook/webpack.config.js View File

@@ -0,0 +1,40 @@
1
+module.exports = (baseConfig, env, defaultConfig) => {
2
+    const config = {...defaultConfig, ...baseConfig};
3
+    config.module.rules.push({
4
+        test: /\.(ts|tsx)$/,
5
+        loader: require.resolve('awesome-typescript-loader')
6
+    });
7
+    config.module.rules.push({
8
+        test: /Story.tsx/,
9
+        loaders: [
10
+            {
11
+                loader: require.resolve('@storybook/addon-storysource/loader'),
12
+                options: {parser: 'typescript'}
13
+            }
14
+        ],
15
+        enforce: 'pre',
16
+    });
17
+    config.module.rules.push({
18
+        test: /\.css/, use: [
19
+            {
20
+                loader: 'style-loader'
21
+            },
22
+            {
23
+                loader: 'css-loader', options: {sourceMap: true}
24
+            }]
25
+    });
26
+    config.module.rules.push({
27
+        test: /\.scss$/, use: [
28
+            {
29
+                loader: 'style-loader'
30
+            },
31
+            {
32
+                loader: 'css-loader', options: {sourceMap: true}
33
+            },
34
+            {
35
+                loader: 'sass-loader', options: {sourceMap: true}
36
+            }]
37
+    });
38
+    config.resolve.extensions.push('.ts', '.tsx');
39
+    return config;
40
+};

+ 8
- 0
source/react-mde/Dockerfile View File

@@ -0,0 +1,8 @@
1
+FROM alpine
2
+LABEL maintainer="andrerpena@gmail.com"
3
+RUN apk add --update nodejs nodejs-npm
4
+COPY . /src
5
+WORKDIR /src
6
+RUN npm i
7
+EXPOSE 4000
8
+ENTRYPOINT ["npm", "run", "start"]

+ 21
- 0
source/react-mde/LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2016 André Pena
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.

BIN
source/react-mde/assets/architecture.png View File


BIN
source/react-mde/assets/react-mde-5.png View File


BIN
source/react-mde/assets/react-mde.png View File


+ 43
- 0
source/react-mde/demo/App.tsx View File

@@ -0,0 +1,43 @@
1
+import * as React from "react";
2
+import ReactMde, { ReactMdeTypes } from "../src";
3
+import * as Showdown from "showdown";
4
+
5
+export interface AppState {
6
+    mdeState: ReactMdeTypes.MdeState;
7
+}
8
+
9
+export class App extends React.Component<{}, AppState> {
10
+
11
+    converter: Showdown.Converter;
12
+
13
+    constructor(props) {
14
+        super(props);
15
+        this.state = {
16
+            mdeState: {
17
+                markdown: "**Hello world!!!**",
18
+            },
19
+        };
20
+        this.converter = new Showdown.Converter({
21
+            tables: true,
22
+            simplifiedAutoLink: true,
23
+            strikethrough: true,
24
+            tasklists: true,
25
+        });
26
+    }
27
+
28
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
29
+        this.setState({ mdeState });
30
+    }
31
+
32
+    render() {
33
+        return (
34
+            <div className="container">
35
+                <ReactMde
36
+                    onChange={this.handleValueChange}
37
+                    editorState={this.state.mdeState}
38
+                    generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
39
+                />
40
+            </div>
41
+        );
42
+    }
43
+}

+ 15
- 0
source/react-mde/demo/client.tsx View File

@@ -0,0 +1,15 @@
1
+import * as React from "react";
2
+import { render } from "react-dom";
3
+import { App } from "./App";
4
+
5
+// stylings
6
+import "../node_modules/normalize.css/normalize.css";
7
+import "../node_modules/draft-js/dist/Draft.css";
8
+import "../src/styles/react-mde-all.scss";
9
+import "./styles/demo.scss";
10
+import "./styles/variables.scss";
11
+
12
+render(
13
+    <App />,
14
+    document.getElementById("#app_container"),
15
+);

+ 29
- 0
source/react-mde/demo/index.html View File

@@ -0,0 +1,29 @@
1
+<html>
2
+
3
+<head>
4
+    <title>react-mde</title>
5
+    <meta http-equiv='X-UA-Compatible' content='IE=edge' />
6
+    <meta name='viewport' content='width=device-width, initial-scale=1.0' />
7
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
8
+</head>
9
+
10
+<body>
11
+    <div>
12
+        <div id="#app_container"></div>
13
+    </div>
14
+    <script src='bundle.js'></script>
15
+    <script>
16
+    (function (i, s, o, g, r, a, m) {
17
+        i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
18
+            (i[r].q = i[r].q || []).push(arguments)
19
+        }, i[r].l = 1 * new Date(); a = s.createElement(o),
20
+            m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
21
+    })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
22
+
23
+    ga('create', 'UA-89807105-1', 'auto');
24
+    ga('send', 'pageview');
25
+
26
+</script>
27
+</body>
28
+
29
+</html>

+ 27
- 0
source/react-mde/demo/index.prod.html View File

@@ -0,0 +1,27 @@
1
+<html>
2
+<head>
3
+    <title>react-mde</title>
4
+    <meta http-equiv='X-UA-Compatible' content='IE=edge' />
5
+    <meta name='viewport' content='width=device-width, initial-scale=1.0' />
6
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
7
+    <link rel='stylesheet' href='bundle.css'/>
8
+</head>
9
+<body>
10
+<div>
11
+    <div id="#app_container"></div>
12
+</div>
13
+<script src='bundle-prod.js'></script>
14
+<script>
15
+        (function (i, s, o, g, r, a, m) {
16
+        i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
17
+            (i[r].q = i[r].q || []).push(arguments)
18
+        }, i[r].l = 1 * new Date(); a = s.createElement(o),
19
+            m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
20
+        })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
21
+
22
+        ga('create', 'UA-89807105-1', 'auto');
23
+        ga('send', 'pageview');
24
+
25
+    </script>
26
+</body>
27
+</html>

+ 26
- 0
source/react-mde/demo/server.ts View File

@@ -0,0 +1,26 @@
1
+import * as fs from "fs";
2
+import * as express from "express";
3
+import * as webpackMiddleware from "webpack-dev-middleware";
4
+import * as webpackHotMiddleware from "webpack-hot-middleware";
5
+import * as webpack from "webpack";
6
+import * as webpackConfig from "../webpack.config.demo.dev";
7
+const packageJson = require("../package.json");
8
+
9
+const webpackCompiler = webpack(webpackConfig as any);
10
+const port = 4000;
11
+
12
+require.extensions[".html"] = (module, filename) => {
13
+    module.exports = fs.readFileSync(filename, "utf8");
14
+};
15
+
16
+const app = express();
17
+
18
+app.use(webpackMiddleware(webpackCompiler));
19
+app.use(webpackHotMiddleware(webpackCompiler));
20
+app.use((req, res) => res.status(200).send(require("./index.html")));
21
+
22
+app.listen(port, "0.0.0.0", () => {
23
+    const demoUrl = `http://localhost:${port}/`;
24
+    // tslint:disable-next-line
25
+    console.log(`${packageJson.name} running at ${demoUrl}`);
26
+});

+ 16
- 0
source/react-mde/demo/styles/demo.scss View File

@@ -0,0 +1,16 @@
1
+@import 'variables.scss';
2
+
3
+* {
4
+    box-sizing: border-box;
5
+}
6
+
7
+body {
8
+    font-size: 14px;
9
+    font-family: sans-serif;
10
+}
11
+
12
+.container {
13
+    width: 100%;
14
+    height: 600px;
15
+    padding: 10px;
16
+}

+ 1
- 0
source/react-mde/demo/styles/variables.scss View File

@@ -0,0 +1 @@
1
+$default-border-color: rgba(0, 0, 0, .125) !default;

+ 70
- 0
source/react-mde/docs-md/ChangeLogMigrating.md View File

@@ -0,0 +1,70 @@
1
+    
2
+# Change log / Migrating
3
+
4
+## From 4.\* to 5.\*
5
+
6
+Major differences:
7
+
8
+- `React-mde` is now built on top of `Draft.js`. This facilitates features that would otherwise be quite
9
+difficult, the best examples being history management, mentions and pasting files.
10
+- Showdown is no longer a dependency and server side markdown generation is now possible.
11
+- The Command API is now much more capable and stable. For example, it now allows commands to be 
12
+executed asynchronously.
13
+- The default styling is now self-contained and easier to integrate into existing applications.
14
+
15
+## From 3.\* to 4.\*
16
+
17
+Major differences:
18
+
19
+- Passing `commands` to `React-Mde` is now optional. If none is passed, it will automatically
20
+use the default ones.
21
+- Now the `React-mde` sub-components cannot be imported directly from the main package.
22
+This change will not affect you if you don't using sub-components. This will not affect the majority
23
+of the users.
24
+
25
+    // 3.\* and below:
26
+    import { ReactMarkdownTextArea } from "react-mde"
27
+    // 4.\* and after:
28
+    import { ReactMdeComponents } from "react-mde"
29
+    const { ReactMarkdownTextArea } = ReactMdeComponents
30
+
31
+Architectural differences:
32
+
33
+- Now the layout is decoupled from `React-Mde` in such a way that now, introducing new layouts, like
34
+horizontal and tabs, will not require breaking changes. The only layout available now is `Vertical`.
35
+
36
+## From 2.* to 3.\*
37
+
38
+Font Awesome 5 is now used, and it's not a NPM peer dependency anymore. 
39
+It's up to you how to install it, it [can be installed in different ways](https://fontawesome.com/how-to-use/svg-with-js), but the easiest is just adding this to the `<head/>`:
40
+
41
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
42
+
43
+## From 1.\* to 2.\*
44
+
45
+2.* is a major refactoring of the code to improve composability
46
+
47
+Major differences:
48
+
49
+- Now the main `react-mde` component is composed of 3 sub-components, the `ReactMdeToolbar`, the `ReactMdeTextArea` and the `ReactMdePreview`.
50
+You can import `react-mde` directly or import each of the sub-components and have more flexibility building your own layout.
51
+- Each sub-component now has its own CSS/SCSS file and we're now including a `react-mde-all.css`, or the SCSS alternative, for simplicity.
52
+- We realized that on version 1.*, it was difficult to select which components you wanted to use. So now...
53
+
54
+
55
+    import * as ReactMde from 'react-mde';
56
+    ReactMde.ReactMdeComponents // contains all components and you can select your own components
57
+    ReactMde.getDefaultComponents() // will return an array with all components
58
+    
59
+    
60
+How to upgrade an existing 1.* JavaScript project: https://github.com/andrerpena/react-mde-js-demo/commit/e62af170fa258f7f17f29d41f395f24e9eaf3b72
61
+
62
+How to upgrade an existing 1.* TypeScript project: https://github.com/andrerpena/react-mde-ts-demo/commit/d6718305c0132649cabca432e1e9415ea06cd643
63
+
64
+## From 0.* to 1.*
65
+
66
+Major differences:
67
+
68
+- Instead of using the `getDefaultCommands` function, now the default commands are exported directly.
69
+- The `textAreaId` and `textareaName` props were replaced by `textAreaProps` that allows you to pass whatever you want to the `textarea` component.
70
+- The paths of the CSS and SCSS have changed.

+ 103
- 0
source/react-mde/docs-md/customButton.md View File

@@ -0,0 +1,103 @@
1
+## Custom Buttons
2
+
3
+1. `ImageUploaderCommand.js` file:
4
+```
5
+import {Command} from "../types";
6
+import ButtonComponent from "./ButtonComponent";
7
+let myVal = "def";
8
+const customCommand: Command = {
9
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("bold"),
10
+    execute: (state) => {
11
+        /*...*/
12
+    },
13
+    CustomButtonComponent: ButtonComponent,
14
+    setValues: (value) => {
15
+        myVal = value;
16
+    },
17
+};
18
+export default customCommand;
19
+```
20
+
21
+2. Set the `commands` prop in `<ReactMde />`:
22
+
23
+```
24
+import * as React from "react";
25
+import * as Showdown from "showdown";
26
+import ReactMde, {ReactMdeTypes} from "../../../src/index";
27
+import {storiesOf} from "@storybook/react";
28
+import CustomCommand from "./path/to/ImageUploaderCommand";
29
+
30
+const commands = [
31
+    [CustomCommand],
32
+];
33
+
34
+interface State {
35
+    mdeState: ReactMdeTypes.MdeState;
36
+}
37
+
38
+class ReactMdeButtonExample extends React.Component {
39
+    converter: Showdown.Converter;
40
+
41
+    constructor(props) {
42
+        super(props);
43
+        this.state = {
44
+            mdeState: {
45
+                markdown: "Custom Button with functionality",
46
+            },
47
+        };
48
+        this.converter = new Showdown.Converter({
49
+            tables: true,
50
+            simplifiedAutoLink: true,
51
+            strikethrough: true,
52
+            tasklists: true,
53
+        });
54
+    }
55
+
56
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
57
+        this.setState({mdeState});
58
+    };
59
+
60
+    render() {
61
+        return (
62
+            <ReactMde
63
+                onChange={this.handleValueChange}
64
+                editorState={this.state.mdeState}
65
+                generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
66
+                commands={commands}
67
+            />
68
+        );
69
+    }
70
+}
71
+```
72
+
73
+3.  Invoke `this.props.setValues()` and `this.props.handleSubmit()` in `ButtonComponent.tsx`:
74
+
75
+```
76
+export default class SimpleForm extends React.Component {
77
+  constructor(props) {
78
+    super(props);
79
+    this.handleTextChange = this.handleTextChange.bind(this);
80
+    this.handleSubmit = this.handleSubmit.bind(this);
81
+    props.setValues(imagePlaceholder);
82
+  }
83
+
84
+  handleTextChange(e) {
85
+    this.props.setValues(e.target.value)
86
+  }
87
+
88
+  handleSubmit() {
89
+    this.props.handleSubmit();
90
+  }
91
+
92
+  render() {
93
+    return <div>
94
+      <button onClick={this.handleClick}> Upload Image </button>
95
+        <div>
96
+          <input  onChange={this.handleTextChange}  />
97
+          <button onClick={this.handleSubmit} >Submit </button>
98
+        </div>
99
+      }
100
+    </div>
101
+  }
102
+}
103
+```

+ 289
- 0
source/react-mde/docs-md/readme-old.md View File

@@ -0,0 +1,289 @@
1
+# react-mde
2
+
3
+A simple yet powerful and extensible Markdown Editor editor for React, inspired by GitHub. This is the editor used by http://aboutdevs.com.
4
+
5
+![image](https://github.com/andrerpena/react-mde/blob/master/assets/react-mde.png)
6
+
7
+> Warning: React-mde is not yet stable and ready for production use even though we're working very hard towards it. Its main current problem now is that Ctrl + Z doesn't work 100% (after executing a command, history gets messy). I, @andrerpena, am curretly working to replace the textarea with Facebook's Draft.js, this will solve the history problem and will make it easy to implement mentions and copying files. I'm also working on making the command infrastructure more rebust. Expect a major breaking change (5.\*) coming on the 18th of March. After this, my goal will be to provide a GitHub like layout. React-mde currently supports multiple layouts already, but just a simple vertical one is provided out of the box. I want provide 2 refined layouts: Stackoverflow and GitHub. I'm really sorry for the constant breaking change releases. It was my fault. I should have used minor versions (0.\*) until a final version was reached, but I can't fix that now. I will make sure that 5.* will be a pretty stable version. Thanks for your comprehension and support.  
8
+
9
+## Demos
10
+
11
+Demos are provided through https://codesandbox.io. Feel free to fork and play with different options :smile:.
12
+
13
+[React-mde 4.* - JavaScript - Basic Demo](https://codesandbox.io/s/qz116r505w)
14
+
15
+[React-mde 4.* - TypeScript - Basic Demo](https://codesandbox.io/s/y0xwwqo88j)
16
+
17
+[React-mde 3.* - JavaScript - Basic Demo](https://codesandbox.io/s/mz1945q5my)
18
+
19
+[React-mde 3.* - TypeScript - Basic Demo](https://codesandbox.io/s/z1zv6py3)
20
+
21
+## Installing
22
+
23
+    npm install --save react-mde
24
+
25
+## Dependencies
26
+
27
+`React-mde` currently depends on:
28
+
29
+- Font Awesome - For the icons
30
+- Showdown - For rendering the markdown preview
31
+
32
+`React-mde` used Font Awesome 4.7.* as an NPM dependency before 3.*. Now Font Awesome needs
33
+to be installed separately [using your preferred method](https://fontawesome.com/how-to-use/svg-with-js). The easiest is just add this to `<head/>`:
34
+
35
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
36
+ 
37
+You also need NPM packages.
38
+
39
+
40
+
41
+For `React-mde` 3.*+:
42
+
43
+    npm install --save showdown
44
+    
45
+For `React-mde` 2.*: (this will install Font Awesome 4.7.*)
46
+    
47
+    npm install --save showdown font-awesome
48
+
49
+## Using
50
+
51
+React-mde is a completely controlled component. You can experiment with the props below
52
+forking the [Codesandbox demos](https://github.com/andrerpena/react-mde#demos)
53
+
54
+**Props**:
55
+
56
+- **value**: The current value. An object with this structure `{text: "", selection?: {start:0, end:2}, scrollTop?: number }` 
57
+where `text` is the text and `selection` is an object containing `start` and `end` representing what should be selected.
58
+Passing null to `selection` is perfectly fine.
59
+- **onChange**: Function that should handle the value. The `value` passed as a parameter to the `onChange` function is of the same type as the `value` prop above.
60
+- **commands**: An array of array of command objects (`[[cmd1, cmd2],[cmd3, cmd4]]`). The first array represents groups,
61
+ the second represents commands inside that group. For an example, refer to how the `getDefaultCommands()` [is implemented](https://github.com/andrerpena/react-mde/blob/master/src/commands/index.ts). How to create custom commands is explained below.
62
+- **textAreaProps**: Whatever you want to pass to the `textarea` component.
63
+- **showdownFlavor**: The Markdown flavor to use. This defaults to `original`, the [original specs by John Gruber](https://daringfireball.net/projects/markdown/). Please refer to the [Showdown documentation](https://github.com/showdownjs/showdown#flavors) for the complete list of supported flavors.
64
+- **showdownOptions**: An object with options to be passed to Showdown. Please refer to the [Showdown documentation](https://github.com/showdownjs/showdown#valid-options) for the complete list of options. Note that, unlike what happens by default with Showdown, the options passed here will **override** the defined flavor.
65
+Notice that tables, for example, are disabled by default in Showdown. So, in order to enable tables, you'd pass something like `showdownOptions={{tables: true}}` to `ReactMde`.
66
+- **visibility**: Determines which sub-components are visible. `visibility` is an object optionally containing these booleans:
67
+`toolbar`, `textarea`, `preview` and `previewHelp`. For example, in order to hide the preview, you can pass `visibility:{{preview:false}}`.
68
+- **className**: Custom className.
69
+- **processHtml**: A function that receives the HTML generated by Showdown and returned a processed/sanitized version before
70
+displaying it in the preview. This is useful to avoid XSS.
71
+
72
+## Styling
73
+
74
+The following styles from React-mde should be added: (Both .scss and .css files are available. No need to use sass-loader if you don't want)
75
+
76
+Easiest way: import `react-mde-all.css`:
77
+
78
+    import 'react-mde/lib/styles/css/react-mde-all.css';
79
+    
80
+If you want to have a more granular control over the styles, you can import each individual file:
81
+
82
+    import 'react-mde/lib/styles/css/react-mde.css';
83
+    import 'react-mde/lib/styles/css/react-mde-toolbar.css';
84
+    import 'react-mde/lib/styles/css/react-mde-textarea.css';
85
+    import 'react-mde/lib/styles/css/react-mde-preview.css';
86
+    
87
+If you're using SASS, you can override these variables: https://github.com/andrerpena/react-mde/blob/master/src/styles/variables.scss
88
+
89
+You also need Font Awesome for the toolbar icons. `React-mde` 3.\*+ uses Font Awesome 5.\*. `React-mde` 2.\* uses Font Awesome 4.*.
90
+
91
+Font Awesome 5 [can be installed in different ways](https://fontawesome.com/how-to-use/svg-with-js), but the easiest is just adding this to the `<head/>`:
92
+
93
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
94
+
95
+If you're using `React-mde` 2.\*, Font Awesome 4.\* is required. After installing the NPM, 
96
+import it in your JavaScript or TypeScript like this:
97
+
98
+    import '../node_modules/font-awesome/css/font-awesome.css';
99
+    
100
+Normalize is optional but it's used in the Demo.
101
+    
102
+    import '../node_modules/normalize.css/normalize.css';
103
+    
104
+    
105
+## XSS concerns
106
+    
107
+Taken from the [Markdown repository documentation](https://github.com/showdownjs/showdown/wiki/Markdown's-XSS-Vulnerability-(and-how-to-mitigate-it)):
108
+
109
+> Cross-side scripting is a well known technique to gain access to private information of the users 
110
+of a website. The attacker injects spurious HTML content (a script) on the web page which will read 
111
+the user’s cookies and do something bad with it (like steal credentials). As a countermeasure,
112
+ you should filter any suspicious content coming from user input. Showdown doesn’t include an 
113
+ XSS filter, so you must provide your own. But be careful in how you do it…
114
+ 
115
+`React-mde` does not automatically sanitize the Showdown generated HTML for you. However,
116
+you can use your preferred anti-XSS library and use the `processHtml` prop to sanitize
117
+the HTML before it is presented in the preview.
118
+    
119
+## Commands
120
+
121
+React-mde allows you to use the build-in commands, implement your own commands, or both.
122
+
123
+There are two types of commands, the `button` commands (default if you don't say anything), and the `dropdown` commands.
124
+`button` commands appear as button and execute a single action when clicked. `dropdown` commands have `subCommands` and
125
+will display a dropdown when you click them.
126
+
127
+### Anatomy of a button command
128
+
129
+You don't have to create your own commands at all, but if you want, this is how a command looks like:
130
+
131
+    const makeLinkCommand = {
132
+        icon: 'link',
133
+        tooltip:
134
+            'Insert a link',
135
+        execute:
136
+            (text: string, selection: TextSelection) => {
137
+                const {newText, insertionLength} = insertText(text, '[', selection.start);
138
+                const finalText = insertText(newText, '](url)', selection.end + insertionLength).newText;
139
+                return {
140
+                    text: finalText,
141
+                    selection: {
142
+                        start: selection.start + insertionLength,
143
+                        end: selection.end + insertionLength,
144
+                    },
145
+                };
146
+            },
147
+    };
148
+
149
+
150
+**props:**
151
+
152
+- **type**: The type of the command.
153
+- **icon**: If this is a text, it will print a `font-awesome` `<i/>` element with the classes `fa fa-${icon}`. Passing `bold` will print `<i className="fa fa-bold"></i>`.
154
+ If the passing value is a React element, it will print the react element.
155
+- **tooltip**: If any, it will print a tooltip with the passed text.
156
+- **execute**: The function that will actually execute the command. This function accepts 2 parameters: `text`,
157
+ which is the textarea text before the command, and `selection`, an object containing `start` and `end`. 
158
+ Your function should return the new `text` and the new `selection` (after your command).
159
+
160
+
161
+### Anatomy of a dropdown command
162
+
163
+    {
164
+		type: 'dropdown',
165
+		icon: 'header',
166
+		subCommands: [
167
+			{
168
+				content: <p className="header-1">Header</p>,
169
+				execute: function (text, selection) {
170
+					return makeHeader(text, selection, '# ');
171
+				}
172
+			},
173
+			{
174
+				content: <p className="header-2">Header</p>,
175
+				execute: function (text, selection) {
176
+					return makeHeader(text, selection, '## ');
177
+				}
178
+			},
179
+			{
180
+				content: <p className="header-3">Header</p>,
181
+				execute: function (text, selection) {
182
+					return makeHeader(text, selection, '### ');
183
+				}
184
+			}
185
+		]
186
+	}
187
+
188
+
189
+**props:**
190
+
191
+
192
+ - **type**: The type of the command.
193
+ - **icon**: If this is a text, it will print a `font-awesome` `<i/>` element with the classes `fa fa-${icon}`. Passing `bold` will print `<i className="fa fa-bold"></i>`.
194
+ If the passing value is a React element, it will print the react element.
195
+ - **subCommands**: A list of commands that will dropdown when you click the button.
196
+
197
+**subCommands** is an array of objects with the following props:
198
+
199
+ - **content**: A React component that will be displayed within the `li`.
200
+ - **execute**: The function that will actually execute the command. This function accepts 2 parameters: `text`, which is the whole textarea text before your command, and `selection`, a 2 items array containing the beggining and end of the current selection.
201
+ Your function should return the current `text` (after your command) and the current `selection` (after your command).
202
+
203
+## Composition and custom layouts
204
+
205
+`ReactMde` is designed to make it easy for users to customize the layout, or to make any of it sub-components invisible,
206
+like the Preview, for instance. The `ReactMde` component, itself, just a thin layout wrapper for its 3 internal components
207
+with visibility options.
208
+
209
+If you want to create your own layout, please take a look at the source code of the `ReactMde` component:
210
+https://github.com/andrerpena/react-mde/blob/master/src/ReactMde.tsx. It's easy to simply create your own, only
211
+leveraging the internal components you'd like and laying them out in the way you prefer
212
+
213
+    import * as ReactMde from 'react-mde';
214
+    // Now you have (among other utility modules):
215
+    // The ReactMde.ReactMdeToolbar component
216
+    // The ReactMde.ReactMdeTextArea component
217
+    // The ReactMde.ReactMdePreview component
218
+    
219
+    
220
+# Migrating
221
+
222
+## From 3.\* to 4.\*
223
+
224
+Major differences:
225
+
226
+- Passing `commands` to `React-Mde` is now optional. If none is passed, it will automatically
227
+use the default ones.
228
+- Now the `React-mde` sub-components cannot be imported directly from the main package.
229
+This change will not affect you if you don't using sub-components. This will not affect the majority
230
+of the users.
231
+
232
+    // 3.\* and below:
233
+    import { ReactMarkdownTextArea } from "react-mde"
234
+    // 4.\* and after:
235
+    import { ReactMdeComponents } from "react-mde"
236
+    const { ReactMarkdownTextArea } = ReactMdeComponents
237
+
238
+Architectural differences:
239
+
240
+- Now the layout is decoupled from `React-Mde` in such a way that now, introducing new layouts, like
241
+horizontal and tabs, will not require breaking changes. The only layout available now is `Vertical`.
242
+
243
+## From 2.* to 3.\*
244
+
245
+Font Awesome 5 is now used, and it's not a NPM peer dependency anymore. 
246
+It's up to you how to install it, it [can be installed in different ways](https://fontawesome.com/how-to-use/svg-with-js), but the easiest is just adding this to the `<head/>`:
247
+
248
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
249
+
250
+## From 1.\* to 2.\*
251
+
252
+2.* is a major refactoring of the code to improve composability
253
+
254
+Major differences:
255
+
256
+- Now the main `react-mde` component is composed of 3 sub-components, the `ReactMdeToolbar`, the `ReactMdeTextArea` and the `ReactMdePreview`.
257
+You can import `react-mde` directly or import each of the sub-components and have more flexibility building your own layout.
258
+- Each sub-component now has its own CSS/SCSS file and we're now including a `react-mde-all.css`, or the SCSS alternative, for simplicity.
259
+- We realized that on version 1.*, it was difficult to select which components you wanted to use. So now...
260
+
261
+
262
+    import * as ReactMde from 'react-mde';
263
+    ReactMde.ReactMdeComponents // contains all components and you can select your own components
264
+    ReactMde.getDefaultComponents() // will return an array with all components
265
+    
266
+    
267
+How to upgrade an existing 1.* JavaScript project: https://github.com/andrerpena/react-mde-js-demo/commit/e62af170fa258f7f17f29d41f395f24e9eaf3b72
268
+
269
+How to upgrade an existing 1.* TypeScript project: https://github.com/andrerpena/react-mde-ts-demo/commit/d6718305c0132649cabca432e1e9415ea06cd643
270
+
271
+## From 0.* to 1.*
272
+
273
+Major differences:
274
+
275
+- Instead of using the `getDefaultCommands` function, now the default commands are exported directly.
276
+- The `textAreaId` and `textareaName` props were replaced by `textAreaProps` that allows you to pass whatever you want to the `textarea` component.
277
+- The paths of the CSS and SCSS have changed.
278
+
279
+## Roadmap
280
+
281
+Check the project here: https://github.com/andrerpena/react-mde/projects/1
282
+
283
+## Licence
284
+
285
+React-mde is MIT licensed
286
+
287
+## About the author
288
+
289
+Made with :heart: by André Pena. Check out my website: https://aboutdevs.com/andrerpena

BIN
source/react-mde/docs/favicon.ico View File


+ 89
- 0
source/react-mde/docs/iframe.html View File

@@ -0,0 +1,89 @@
1
+<!DOCTYPE html>
2
+<html>
3
+<head>
4
+    <meta charset="utf-8">
5
+    <meta name="viewport" content="width=device-width, initial-scale=1">
6
+    <meta content="IE=edge" http-equiv="X-UA-Compatible" />
7
+    <base target="_parent">
8
+    <title>Storybook</title>
9
+    <link rel="shortcut icon" href="favicon.ico?v=1" />
10
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
11
+    <style>
12
+        :not(.sb-show-main) > .sb-main,
13
+        :not(.sb-show-nopreview) > .sb-nopreview,
14
+        :not(.sb-show-errordisplay) > .sb-errordisplay {
15
+            display: none;
16
+        }
17
+
18
+
19
+        .sb-wrapper {
20
+            position: fixed;
21
+            top: 0;
22
+            bottom: 0;
23
+            left: 0;
24
+            right: 0;
25
+            padding: 20px;
26
+            font-family: -apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
27
+            -webkit-font-smoothing: antialiased;
28
+        }
29
+
30
+        .sb-heading {
31
+            font-size: 20px;
32
+            font-weight: 600;
33
+            letter-spacing: 0.2px;
34
+            margin: 10px 0;
35
+        }
36
+
37
+
38
+        .sb-nopreview {
39
+            display: flex;
40
+            align-content: center;
41
+            justify-content: center;
42
+        }
43
+
44
+        .sb-nopreview_main {
45
+            margin: auto;
46
+            padding: 30px;
47
+            border-radius: 10px;
48
+            background: rgba(0,0,0,0.03);
49
+        }
50
+
51
+        .sb-nopreview_heading {
52
+            text-align: center;
53
+        }
54
+
55
+
56
+        .sb-errordisplay {
57
+            background-color: rgb(187, 49, 49);
58
+            color: #FFF;
59
+        }
60
+
61
+        .sb-errordisplay_code {
62
+            font-size: 14px;
63
+            width: 100vw;
64
+            overflow: auto;
65
+        }
66
+    </style>
67
+</head>
68
+<body class="sb-show-main">
69
+    <div id="root" class="sb-main"></div>
70
+
71
+    <div class="sb-nopreview sb-wrapper">
72
+        <div class="sb-nopreview_main">
73
+            <h1 class="sb-nopreview_heading sb-heading">No Preview</h1>
74
+            <p>Sorry, but you either have no stories or none are selected somehow.</p>
75
+            <ul>
76
+                <li>Please check the storybook config.</li>
77
+                <li>Try reloading the page.</li>
78
+            </ul>
79
+        </div>
80
+    </div>
81
+
82
+    <div class="sb-errordisplay sb-wrapper">
83
+        <div id="error-message" class="sb-heading"></div>
84
+        <pre class="sb-errordisplay_code">
85
+            <code id="error-stack"></code>
86
+        </pre>
87
+    </div>
88
+<script type="text/javascript" src="static/runtime~preview.3d1c6dcf72bd957deb27.bundle.js"></script><script type="text/javascript" src="static/vendors~preview.014d44b79772935ed98a.bundle.js"></script><script type="text/javascript" src="static/preview.882bafd716960d2ded4d.bundle.js"></script></body>
89
+</html>

+ 23
- 0
source/react-mde/docs/index.html View File

@@ -0,0 +1,23 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+    <meta charset="utf-8">
5
+    <meta name="viewport" content="width=device-width, initial-scale=1">
6
+    <meta name="storybook-version" content="4.0.0-alpha.10">
7
+    <meta content="IE=edge" http-equiv="X-UA-Compatible" />
8
+    <title>Storybook</title>
9
+    <link rel="shortcut icon" href="favicon.ico?v=1" />
10
+    
11
+
12
+</head>
13
+<body style="margin: 0;">
14
+    <style>
15
+        html, body {
16
+            overflow: hidden;
17
+            height: 100%;
18
+            width: 100%;
19
+        }
20
+    </style>
21
+    <div id="root"></div>
22
+<script type="text/javascript" src="static/runtime~manager.e92fd76fc0e1ef205464.bundle.js"></script><script type="text/javascript" src="static/manager.e49c8faf84bedb0a57cd.bundle.js"></script></body>
23
+</html>

+ 98
- 0
source/react-mde/docs/static/manager.e49c8faf84bedb0a57cd.bundle.js
File diff suppressed because it is too large
View File


+ 1
- 0
source/react-mde/docs/static/manager.e49c8faf84bedb0a57cd.bundle.js.map
File diff suppressed because it is too large
View File


+ 2
- 0
source/react-mde/docs/static/preview.882bafd716960d2ded4d.bundle.js
File diff suppressed because it is too large
View File


+ 1
- 0
source/react-mde/docs/static/preview.882bafd716960d2ded4d.bundle.js.map View File

@@ -0,0 +1 @@
1
+{"version":3,"file":"static/preview.882bafd716960d2ded4d.bundle.js","sources":["webpack:///./.storybook/stories/customization/commandButton/ButtonComponent.tsx"],"sourcesContent":["import * as React from \"react\";\r\n\r\nconst containerStyle: React.CSSProperties = {\r\n    width: \"200px\",\r\n    border: \"1px solid #c8ccd0\",\r\n    height: \"50px\",\r\n    position: \"absolute\",\r\n    background: \"#fff\",\r\n    zIndex: 999,\r\n    padding: \"5px\",\r\n};\r\n\r\nconst buttonStyle = {\r\n    textAlign: \"center\",\r\n    width: \"100%\",\r\n};\r\n\r\nconst imagePlaceholder = \"https://www.comet.ml/images/logo_comet_dark.png\";\r\n\r\nexport interface Props {\r\n    onTextChange: (value: string) => void;\r\n    handleSubmit: () => void;\r\n}\r\n\r\nexport interface State {\r\n    isOpen: boolean;\r\n}\r\n\r\nexport default class SimpleForm extends React.Component<Props, State> {\r\n    constructor(props) {\r\n        super(props);\r\n        this.state = {\r\n            isOpen: false,\r\n        };\r\n\r\n        props.setValues(imagePlaceholder);\r\n    }\r\n\r\n    handleClick = () => {\r\n        const {isOpen} = this.state;\r\n        this.setState({isOpen: !isOpen});\r\n    }\r\n\r\n    handleTextChange = (e) => {\r\n        this.props.onTextChange(e.target.value);\r\n    }\r\n\r\n    handleSubmit = () => {\r\n        this.props.handleSubmit();\r\n        this.setState({isOpen: false});\r\n    }\r\n\r\n    render() {\r\n        const {isOpen} = this.state;\r\n\r\n        return (\r\n            <div>\r\n                <button onClick={this.handleClick}> Upload Image</button>\r\n                {isOpen &&\r\n                <div style={containerStyle}>\r\n                    <input\r\n                        type=\"text\"\r\n                        onChange={this.handleTextChange}\r\n                        style={{width: \"100%\"}}\r\n                        value={imagePlaceholder}\r\n                    />\r\n                    <button onClick={this.handleSubmit} style={buttonStyle}>Submit</button>\r\n                </div>\r\n                }\r\n            </div>\r\n        );\r\n    }\r\n}\r\n"],"mappings":"AAAA","sourceRoot":""}

+ 2
- 0
source/react-mde/docs/static/runtime~manager.e92fd76fc0e1ef205464.bundle.js View File

@@ -0,0 +1,2 @@
1
+!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c<i.length;c++)f=i[c],o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={0:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="";var i=window.webpackJsonp=window.webpackJsonp||[],l=i.push.bind(i);i.push=r,i=i.slice();for(var a=0;a<i.length;a++)r(i[a]);var p=l;t()}([]);
2
+//# sourceMappingURL=runtime~manager.e92fd76fc0e1ef205464.bundle.js.map

+ 1
- 0
source/react-mde/docs/static/runtime~manager.e92fd76fc0e1ef205464.bundle.js.map
File diff suppressed because it is too large
View File


+ 2
- 0
source/react-mde/docs/static/runtime~preview.3d1c6dcf72bd957deb27.bundle.js View File

@@ -0,0 +1,2 @@
1
+!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c<i.length;c++)f=i[c],o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="";var i=window.webpackJsonp=window.webpackJsonp||[],l=i.push.bind(i);i.push=r,i=i.slice();for(var a=0;a<i.length;a++)r(i[a]);var p=l;t()}([]);
2
+//# sourceMappingURL=runtime~preview.3d1c6dcf72bd957deb27.bundle.js.map

+ 1
- 0
source/react-mde/docs/static/runtime~preview.3d1c6dcf72bd957deb27.bundle.js.map
File diff suppressed because it is too large
View File


+ 58
- 0
source/react-mde/docs/static/vendors~preview.014d44b79772935ed98a.bundle.js
File diff suppressed because it is too large
View File


+ 1
- 0
source/react-mde/docs/static/vendors~preview.014d44b79772935ed98a.bundle.js.map
File diff suppressed because it is too large
View File


+ 46
- 0
source/react-mde/gulpfile.js View File

@@ -0,0 +1,46 @@
1
+const gulp = require('gulp');
2
+const rename = require('gulp-rename');
3
+const ts = require('gulp-typescript');
4
+const sass = require('gulp-sass');
5
+const merge = require('merge2');
6
+const tsProject = ts.createProject('./tsconfig.build.json');
7
+const run = require('gulp-run');
8
+
9
+gulp.task('build_styles', function () {
10
+    return gulp.src('./src/styles/**/*.scss')
11
+        .pipe(sass().on('error', sass.logError))
12
+        .pipe(gulp.dest('./lib/styles/css'));
13
+});
14
+
15
+// library
16
+gulp.task('copy_styles', function () {
17
+    return gulp.src('./src/styles/**/*.scss')
18
+        .pipe(gulp.dest('./lib/styles/scss'));
19
+});
20
+
21
+gulp.task('build-lib', ['copy_styles', 'build_styles'], function () {
22
+    const tsResult = gulp.src('src/**/*.{ts,tsx}')
23
+        .pipe(tsProject({
24
+            declaration: true
25
+        }));
26
+    return merge([
27
+        tsResult.dts.pipe(gulp.dest('lib/definitions')),
28
+        tsResult.js.pipe(gulp.dest('lib/js'))
29
+    ]);
30
+});
31
+
32
+// demo
33
+// removes the output configuration from the webpack.config.js file, otherwise it doesn't work.
34
+
35
+gulp.task('copy-index', function () {
36
+    return gulp.src('./demo/index.prod.html')
37
+        .pipe(rename('index.html'))
38
+        .pipe(gulp.dest('./docs'));
39
+});
40
+
41
+gulp.task('build-demo', ['copy-index'], function () {
42
+    return run("npm run build:storybook").exec();
43
+});
44
+
45
+// all
46
+gulp.task('build', ['build-demo', 'build-lib']);

+ 20650
- 0
source/react-mde/package-lock.json
File diff suppressed because it is too large
View File


+ 99
- 0
source/react-mde/package.json View File

@@ -0,0 +1,99 @@
1
+{
2
+  "name": "react-mde",
3
+  "version": "5.8.0",
4
+  "description": "React Markdown Editor",
5
+  "main": "./lib/js/index.js",
6
+  "types": "./lib/definitions/index.d.ts",
7
+  "scripts": {
8
+    "test": "mocha --timeout 15000 -r ts-node/register ./test/*Spec.ts",
9
+    "start": "cross-env NODE_ENV=development ts-node ./demo/server.ts",
10
+    "start:storybook": "start-storybook -p 9001 -c .storybook",
11
+    "start:storybook:prod": "npm run build:storybook && static-server docs",
12
+    "build:ts": "tsc --project ./tsconfig.build.json",
13
+    "build": "cross-env NODE_ENV=production gulp build",
14
+    "build:lib": "cross-env NODE_ENV=production gulp build-lib",
15
+    "build:storybook": "build-storybook -c .storybook -o docs",
16
+    "lint": "tslint --project \".\"",
17
+    "lint:fix": "tslint --project \".\" --fix",
18
+    "tsc": "tsc",
19
+    "webpack": "webpack --config webpack.config.demo.dev.js",
20
+    "webpack:prod": "webpack --config webpack.config.demo.prod.js"
21
+  },
22
+  "repository": {
23
+    "type": "git",
24
+    "url": "git+https://github.com/andrerpena/react-mde.git"
25
+  },
26
+  "keywords": [
27
+    "react",
28
+    "component",
29
+    "markdown",
30
+    "editor",
31
+    "text-editor",
32
+    "markdown-editor"
33
+  ],
34
+  "author": "Andre Pena",
35
+  "license": "MIT",
36
+  "bugs": {
37
+    "url": "https://github.com/andrerpena/react-mde.git/issues"
38
+  },
39
+  "devDependencies": {
40
+    "@storybook/addon-actions": "^4.0.0-alpha.4",
41
+    "@storybook/addon-storysource": "^4.0.0-alpha.4",
42
+    "@storybook/react": "^4.0.0-alpha.10",
43
+    "@types/classnames": "^2.2.3",
44
+    "@types/draft-js": "^0.10.20",
45
+    "@types/express": "^4.11.1",
46
+    "@types/mocha": "^5.2.0",
47
+    "@types/react": "^16.0.38",
48
+    "@types/react-dom": "^16.0.4",
49
+    "@types/showdown": "^1.7.2",
50
+    "@types/storybook__react": "^3.0.7",
51
+    "@types/webpack-dev-middleware": "^2.0.0",
52
+    "@types/webpack-hot-middleware": "^2.16.2",
53
+    "awesome-typescript-loader": "^5.0.0",
54
+    "babel-core": "^6.26.3",
55
+    "chai": "^4.1.2",
56
+    "cross-env": "^5.1.3",
57
+    "css-loader": "^0.28.10",
58
+    "draft-js": "^0.10.5",
59
+    "express": "^4.16.2",
60
+    "extract-text-webpack-plugin": "^4.0.0-beta.0",
61
+    "file-loader": "^1.1.9",
62
+    "gulp": "^3.9.1",
63
+    "gulp-cli": "^2.0.1",
64
+    "gulp-rename": "^1.2.2",
65
+    "gulp-run": "^1.7.1",
66
+    "gulp-sass": "^4.0.1",
67
+    "gulp-typescript": "^5.0.0-alpha.2",
68
+    "merge2": "^1.2.1",
69
+    "mocha": "^5.0.1",
70
+    "node-sass": "^4.9.0",
71
+    "normalize.css": "^8.0.0",
72
+    "opn": "^5.2.0",
73
+    "react": "^16.2.0",
74
+    "react-dom": "^16.2.0",
75
+    "react-hot-loader": "^4.1.2",
76
+    "sass-loader": "^7.0.1",
77
+    "showdown": "^1.8.6",
78
+    "static-server": "^2.2.1",
79
+    "style-loader": "^0.21.0",
80
+    "ts-node": "^7.0.0",
81
+    "tslint": "^5.9.1",
82
+    "tslint-react": "^3.5.1",
83
+    "typescript": "^2.9.2",
84
+    "webpack": "^4.14.0",
85
+    "webpack-cli": "^3.0.8",
86
+    "webpack-dev-middleware": "^3.1.3",
87
+    "webpack-hot-middleware": "^2.21.0"
88
+  },
89
+  "homepage": "https://github.com/andrerpena/react-mde.git#readme",
90
+  "peerDependencies": {
91
+    "react": "^16.0.0",
92
+    "react-dom": "^16.0.0",
93
+    "draft-js": "^0.10.5"
94
+  },
95
+  "dependencies": {
96
+    "classnames": "^2.2.5",
97
+    "npm": "^6.1.0"
98
+  }
99
+}

+ 202
- 0
source/react-mde/readme.md View File

@@ -0,0 +1,202 @@
1
+# react-mde
2
+
3
+A simple yet powerful and extensible Markdown Editor editor for React. React-mde is built on top of [Draft.js](https://draftjs.org/).
4
+
5
+## Demos
6
+
7
+- [Demos](http://andrerpena.me/react-mde/).
8
+
9
+## Installing
10
+
11
+    npm i --save react-mde
12
+
13
+## Dependencies
14
+
15
+React-mde currently depends on:
16
+
17
+- [Draft.js](https://draftjs.org/). This facilitates features that would otherwise be quite
18
+difficult. The best examples being history management, mentions and pasting files.
19
+<!-- -->
20
+
21
+    npm i --save draft-js
22
+
23
+- [Font Awesome 5.*](https://fontawesome.com/) for the icons. This is not a hard dependency and can be changed (see the **Customizing Icons** section below).
24
+To use Font Awesome icons, install [using your preferred method](https://fontawesome.com/how-to-use/svg-with-js).
25
+The easiest is just add this to `<head/>`:
26
+<!-- -->
27
+
28
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
29
+
30
+It is possible to use React-mde with Font Awesome 4 (and possibly earlier versions) – see below under 'Customizing Icons'.
31
+
32
+## Optional dependencies
33
+
34
+- [Showdown](https://github.com/showdownjs/showdown). React-mde is not opinionated as to how to transform markdown into HTML and this can be done both in client-side,
35
+like StackOverflow, or in server-side, like GitHub. The easiest way is to use Showdown and process it in client-side. If you
36
+decide to do so, install Showdown:
37
+<!-- -->
38
+
39
+    npm i --save showdown
40
+
41
+## Using
42
+
43
+React-mde is a completely controlled component.
44
+
45
+Minimal example using Showdown:
46
+```jsx
47
+import * as React from "react";
48
+import ReactMde, {ReactMdeTypes} from "../src";
49
+import * as Showdown from "showdown";
50
+
51
+export interface AppState {
52
+    mdeState: ReactMdeTypes.MdeState;
53
+}
54
+
55
+export class App extends React.Component<{}, AppState> {
56
+
57
+    converter: Showdown.Converter;
58
+
59
+    constructor(props) {
60
+        super(props);
61
+        this.state = {
62
+            mdeState: null,
63
+        };
64
+        this.converter = new Showdown.Converter({tables: true, simplifiedAutoLink: true});
65
+    }
66
+
67
+    handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
68
+        this.setState({mdeState});
69
+    }
70
+
71
+    render() {
72
+        return (
73
+            <div className="container">
74
+                <ReactMde
75
+                    onChange={this.handleValueChange}
76
+                    editorState={this.state.mdeState}
77
+                    generateMarkdownPreview={(markdown) => Promise.resolve(this.converter.makeHtml(markdown))}
78
+                />
79
+            </div>
80
+        );
81
+    }
82
+}
83
+```
84
+
85
+### Customizing Icons
86
+
87
+By default, React-mde will use Font Awesome class names to render icons (see above for how to install).
88
+The default icon provider returns icons that look like the following:
89
+
90
+```jsx
91
+<i className={`fas fa-${icon}`} aria-hidden="true"/>
92
+```
93
+
94
+This can be changed by passing a `buttonContentOptions` prop to the `ReactMde` component with an `iconProvider` option to tell React-mde how to render icons.
95
+
96
+For example, you can use your own custom icon component by changing the `iconProvider`:
97
+
98
+```jsx
99
+<ReactMde
100
+    buttonContentOptions={{
101
+        iconProvider: name => <MyCustomIcon name={name} />,
102
+    }}
103
+    onChange={this.handleValueChange}
104
+    // ...
105
+/>
106
+```
107
+
108
+In order to use Font Awesome 4 classes, you can pass the following prop:
109
+```
110
+buttonContentOptions={{
111
+    iconProvider: name => <i className={`fa fa-${name}`} />,
112
+}}
113
+```
114
+This will cause React-mde to use FA4-style classnames.
115
+
116
+## Custom props
117
+[See docs](docs/customButton.md "See docs")
118
+
119
+## React-mde Props
120
+
121
+The types are described below
122
+
123
+- **editorState: MdeState**: The [state of the editor](https://github.com/andrerpena/react-mde/blob/master/src/types/MdeState.ts). 
124
+It contains the markdown, the HTML and the underlying Draft.js state. However, only *markdown* is supposed to be passed
125
+by the user on the first call. Both *html* and *draftEditorState* will be 
126
+generated by React-mde and passed back to the user through the *onChange* callback. It is important to notice that, even
127
+though on the first call, *editorState* is supposed to be something like `{markdown:'Hello!`}`, on the subsequent calls,
128
+*editorState* should be the object returned by the *onChange* callback.
129
+- **className?: string**: Optional class name to be added to the top level element.
130
+- **commands?: Command[][]**: An array of array of commands. If no commands are specified, the default will be used. Commands are explained in more details below.
131
+- **buttonContentOptions?: { iconProvider: (iconName: string) => React.ReactNode }** An optional set of button content options, including an `iconProvider` to allow custom icon rendering.
132
+- **onChange: (value: [MdeState](https://github.com/andrerpena/react-mde/blob/master/src/types/MdeState.ts)) => void**: Event handler for the `onChange` event.
133
+- **generateMarkdownPreview: (markdown: string) => Promise<string>;**: Function that should return the generated HTML for the preview. If this `prop` is falsy, then no preview is going to be generated.
134
+- **layout?: string**: The name of the layout to be used. For now, the supported layouts are : `vertical`, `horizontal`, `tabbed` and `noPreview`.
135
+- **layoutOptions?: any**: An object with options to be passed to the `layout-component`. Each layout may or may not expect
136
+options. It is recommended to [inspect the layouts source code](https://github.com/andrerpena/react-mde/tree/master/src/components-layout) to see what options can be passed to each
137
+while the documentation is not complete. 
138
+-- **readOnly?: boolean**: Flag to render the editor in read-only mode.
139
+
140
+## Styling
141
+
142
+The following styles from React-mde should be added: (Both .scss and .css files are available. No need to use sass-loader if you don't want)
143
+
144
+Easiest way: import `react-mde-all.css`:
145
+
146
+    import 'react-mde/lib/styles/css/react-mde-all.css';
147
+    
148
+If you want to have a more granular control over the styles, you can [import each individual file](https://github.com/andrerpena/react-mde/tree/master/src/styles).
149
+    
150
+If you're using SASS, you can override these variables: https://github.com/andrerpena/react-mde/blob/master/src/styles/variables.scss
151
+
152
+You also need Font Awesome for the toolbar icons. Font Awesome 5 [can be installed in different ways](https://fontawesome.com/how-to-use/svg-with-js),
153
+but the easiest is just adding this to the `<head/>`:
154
+
155
+    <script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
156
+
157
+## XSS concerns
158
+
159
+React-mde does not automatically sanitize the HTML preview. If your using Showdown,
160
+this has been taken from [their documentation](https://github.com/showdownjs/showdown/wiki/Markdown's-XSS-Vulnerability-(and-how-to-mitigate-it)):
161
+    
162
+> Cross-side scripting is a well known technique to gain access to private information of the users
163
+of a website. The attacker injects spurious HTML content (a script) on the web page which will read 
164
+the user’s cookies and do something bad with it (like steal credentials). As a countermeasure,
165
+ you should filter any suspicious content coming from user input. Showdown doesn’t include an 
166
+ XSS filter, so you must provide your own. But be careful in how you do it…
167
+ 
168
+You might want to take a look at [showdown-xss-filter](https://github.com/VisionistInc/showdown-xss-filter).
169
+
170
+## Commands
171
+
172
+React-mde allows you to use the build-in commands, implement your own commands, or both. If you wish
173
+to implement your own commands, please refer to the [commands source code](https://github.com/andrerpena/react-mde/tree/master/src/commands) to understand how they
174
+should be implemented.
175
+
176
+## Composition and custom layouts
177
+
178
+React-mde is designed to be composable and to facilitate new layouts:
179
+
180
+![architecture](https://github.com/andrerpena/react-mde/blob/master/assets/architecture.png)
181
+
182
+## Programmatically controlling text and selection
183
+
184
+`DraftUtil.buildNewMdeState` can be used to programmatically create a new state based on the current state.
185
+[Example with a button that duplicates the text](https://codesandbox.io/s/l4jlp3p5om). For the initial state, you can just pass null to `editorState` and React-Mde will build a new `MdeState` and call `onChange` with the first `MdeState`
186
+
187
+## Change log / Migrating from older versions
188
+
189
+[Instructions here](https://github.com/andrerpena/react-mde/blob/master/docs-md/ChangeLogMigrating.md).
190
+
191
+## Roadmap
192
+
193
+Check the project here: https://github.com/andrerpena/react-mde/projects/1
194
+
195
+## Licence
196
+
197
+React-mde is [MIT licensed](https://github.com/andrerpena/react-mde/blob/master/LICENSE).
198
+
199
+## About the author
200
+
201
+Made with :heart: by André Pena and [other awesome contributors](https://github.com/andrerpena/react-mde/graphs/contributors).
202
+Check out my website: http://andrerpena.me.

+ 13
- 0
source/react-mde/src/LayoutMap.tsx View File

@@ -0,0 +1,13 @@
1
+import * as React from "react";
2
+import {VerticalLayout, NoPreviewLayout, HorizontalLayout, TabbedLayout} from "./components-layout";
3
+
4
+class LayoutMap {
5
+    vertical: typeof VerticalLayout = VerticalLayout;
6
+    noPreview: typeof NoPreviewLayout = NoPreviewLayout;
7
+    horizontal: typeof HorizontalLayout = HorizontalLayout;
8
+    tabbed: typeof TabbedLayout = TabbedLayout;
9
+}
10
+
11
+const layoutMap = new LayoutMap();
12
+
13
+export { LayoutMap, layoutMap };

+ 122
- 0
source/react-mde/src/ReactMde.tsx View File

@@ -0,0 +1,122 @@
1
+import * as React from "react";
2
+import {Command, GenerateMarkdownPreview, MdeState, ButtonContentOptions} from "./types";
3
+import {getDefaultCommands} from "./commands";
4
+import {layoutMap, LayoutMap} from "./LayoutMap";
5
+import {ContentState, EditorState} from "draft-js";
6
+import {getMdeStateFromDraftState} from "./util/DraftUtil";
7
+import {MdeToolbarIcon} from "./components";
8
+
9
+export interface ReactMdeProps {
10
+    editorState: MdeState;
11
+    className?: string;
12
+    commands?: Command[][];
13
+    buttonContentOptions?: ButtonContentOptions;
14
+    onChange: (value: MdeState) => void;
15
+    generateMarkdownPreview?: GenerateMarkdownPreview;
16
+    layout?: keyof LayoutMap;
17
+    layoutOptions?: any;
18
+    emptyPreviewHtml?: string;
19
+    readOnly?: boolean;
20
+}
21
+
22
+export class ReactMde extends React.Component<ReactMdeProps> {
23
+    static defaultProps: Partial<ReactMdeProps> = {
24
+        commands: getDefaultCommands(),
25
+        buttonContentOptions: {
26
+            iconProvider: (name) => <MdeToolbarIcon icon={name}/>,
27
+        },
28
+        layout: "vertical",
29
+        emptyPreviewHtml: "<p>&nbsp;</p>",
30
+        readOnly: false,
31
+    };
32
+
33
+    handleOnChange = ({markdown, html, draftEditorState}: MdeState) => {
34
+        const {onChange} = this.props;
35
+        onChange({markdown, html, draftEditorState});
36
+    }
37
+
38
+    handleDraftStateChange = (draftEditorState: EditorState) => {
39
+        const {generateMarkdownPreview} = this.props;
40
+        getMdeStateFromDraftState(draftEditorState, generateMarkdownPreview).then((mdeState) => {
41
+            this.handleOnChange({
42
+                html: mdeState.html,
43
+                markdown: mdeState.markdown,
44
+                draftEditorState,
45
+            });
46
+        });
47
+    }
48
+
49
+    onCommand = (command: Command) => {
50
+        if (!command.execute) return;
51
+        const {draftEditorState} = this.props.editorState;
52
+        const executedCommand = command.execute(draftEditorState);
53
+        return Promise.resolve(executedCommand).then((result) => this.handleDraftStateChange(result));
54
+    }
55
+
56
+    // The user is **only** supposed to pass the 'markdown' prop of the editorState. Both 'html' and 'draftEditorState'
57
+    // are supposed to be populated by React-Mde. If 'draftEditorState' has value here, this means that the whole 'editorState'
58
+    // has been generated by React-Mde. Otherwise, we will generate an 'initializedMdeState' and call 'handleOnChange'
59
+    // so the user has it
60
+    async ensureMdeStateIsInSync() {
61
+        const {editorState, generateMarkdownPreview} = this.props;
62
+
63
+        let initializedMdeState: MdeState;
64
+        if (editorState) {
65
+            if (editorState.draftEditorState) {
66
+                // editor states with a draftEditorState are considered to be in sync already
67
+                return;
68
+            }
69
+            const html = editorState.html || ((editorState.markdown && generateMarkdownPreview) ? await generateMarkdownPreview(editorState.markdown) : "");
70
+            initializedMdeState = {
71
+                markdown: editorState.markdown,
72
+                html,
73
+                draftEditorState: editorState.draftEditorState || EditorState.createWithContent(ContentState.createFromText(editorState.markdown)),
74
+            };
75
+        } else {
76
+            initializedMdeState = {
77
+                html: "",
78
+                markdown: "",
79
+                draftEditorState: EditorState.createEmpty(),
80
+            };
81
+        }
82
+        this.handleOnChange(initializedMdeState);
83
+    }
84
+
85
+    async componentDidMount() {
86
+        await this.ensureMdeStateIsInSync();
87
+    }
88
+
89
+    async componentDidUpdate() {
90
+        await this.ensureMdeStateIsInSync();
91
+    }
92
+
93
+    render() {
94
+        const Layout = layoutMap[this.props.layout];
95
+        const {buttonContentOptions, commands, layoutOptions, className, emptyPreviewHtml, readOnly} = this.props;
96
+        const {editorState} = this.props;
97
+        let finalEditorState: MdeState = editorState;
98
+        if (!finalEditorState || !finalEditorState.draftEditorState) {
99
+            // This is only supposed to prevent React-Mde from receiving an empty draftEditorState. In this case,
100
+            // componentDidMount or componentDidUpdate will call handleOnChange to pass a valid MdeState to the user
101
+            finalEditorState = {
102
+                html: "",
103
+                markdown: "",
104
+                draftEditorState: EditorState.createEmpty(),
105
+            };
106
+        }
107
+        return (
108
+            <div className={`react-mde ${className || ""}`}>
109
+                <Layout
110
+                    buttonContentOptions={buttonContentOptions}
111
+                    onChange={this.handleDraftStateChange}
112
+                    onCommand={this.onCommand}
113
+                    commands={commands}
114
+                    layoutOptions={layoutOptions}
115
+                    mdeEditorState={finalEditorState}
116
+                    emptyPreviewHtml={emptyPreviewHtml}
117
+                    readOnly={readOnly}
118
+                />
119
+            </div>
120
+        );
121
+    }
122
+}

+ 16
- 0
source/react-mde/src/commands/boldCommand.tsx View File

@@ -0,0 +1,16 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {insertBeforeAndAfter} from "../util/MarkdownUtil";
4
+import {getMarkdownStateFromDraftState, buildNewDraftState} from "../util/DraftUtil";
5
+
6
+export const boldCommand: Command = {
7
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("bold"),
8
+
9
+    buttonProps: { "aria-label": "Add bold text" },
10
+
11
+    execute: (state) => {
12
+        let mdState = getMarkdownStateFromDraftState(state);
13
+        mdState = insertBeforeAndAfter(mdState, "**");
14
+        return buildNewDraftState(state, mdState);
15
+    },
16
+};

+ 16
- 0
source/react-mde/src/commands/checkListCommand.tsx View File

@@ -0,0 +1,16 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {makeList} from "../util/MarkdownUtil";
4
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
5
+
6
+export const checkListCommand: Command = {
7
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("tasks"),
8
+
9
+    buttonProps: { "aria-label": "Insert checklist" },
10
+
11
+    execute: (state) => {
12
+        let mdState = getMarkdownStateFromDraftState(state);
13
+        mdState = makeList(mdState, "- [ ] ");
14
+        return buildNewDraftState(state, mdState);
15
+    },
16
+};

+ 51
- 0
source/react-mde/src/commands/codeCommand.tsx View File

@@ -0,0 +1,51 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {
4
+    insertAfter,
5
+    insertBefore,
6
+    insertBeforeAndAfter, insertBreaksAfterSoThatThereIsAnEmptyLineAfter,
7
+    insertBreaksBeforeSoThatThereIsAnEmptyLineBefore,
8
+    selectWordIfCaretIsInsideOne,
9
+} from "../util/MarkdownUtil";
10
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
11
+
12
+export const codeCommand: Command = {
13
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("code"),
14
+
15
+    buttonProps: { "aria-label": "Insert code" },
16
+
17
+    execute: (state) => {
18
+        let {text, selection} = getMarkdownStateFromDraftState(state);
19
+        selection = selectWordIfCaretIsInsideOne({text, selection});
20
+
21
+        // when there's no breaking line
22
+        if (text.slice(selection.start, selection.end).indexOf("\n") === -1) {
23
+            const mdState = insertBeforeAndAfter({text, selection}, "`");
24
+            return buildNewDraftState(state, mdState);
25
+        }
26
+
27
+        let textInsertion;
28
+
29
+        // insert breaks before, if needed
30
+        textInsertion = insertBreaksBeforeSoThatThereIsAnEmptyLineBefore({text, selection});
31
+        text = textInsertion.newText;
32
+        selection = textInsertion.newSelection;
33
+
34
+        // inserts ```\n before
35
+        textInsertion = insertBefore(text, "```\n", selection, false);
36
+        text = textInsertion.newText;
37
+        selection = textInsertion.newSelection;
38
+
39
+        // inserts ```\n after
40
+        textInsertion = insertAfter(text, "\n```", selection);
41
+        text = textInsertion.newText;
42
+        selection = textInsertion.newSelection;
43
+
44
+        // insert breaks after, if needed
45
+        textInsertion = insertBreaksAfterSoThatThereIsAnEmptyLineAfter({text, selection});
46
+        text = textInsertion.newText;
47
+        selection = textInsertion.newSelection;
48
+
49
+        return buildNewDraftState(state, {text, selection});
50
+    },
51
+};

+ 35
- 0
source/react-mde/src/commands/headerCommand.tsx View File

@@ -0,0 +1,35 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {makeHeader} from "../util/MarkdownUtil";
4
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
5
+
6
+function setHeader(state, str) {
7
+    let mdState = getMarkdownStateFromDraftState(state);
8
+    mdState = makeHeader(mdState, str);
9
+    return buildNewDraftState(state, mdState);
10
+}
11
+
12
+export const headerCommand: Command = {
13
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("heading"),
14
+
15
+    buttonProps: { "aria-label": "Add header" },
16
+
17
+    children: [
18
+        {
19
+            buttonContentBuilder: () => <p className="header-1">Header 1</p>,
20
+            execute: (state) => setHeader(state, "# "),
21
+        },
22
+        {
23
+            buttonContentBuilder: () => <p className="header-2">Header 2</p>,
24
+            execute: (state) => setHeader(state, "## "),
25
+        },
26
+        {
27
+            buttonContentBuilder: () => <p className="header-3">Header 3</p>,
28
+            execute: (state) => setHeader(state, "### "),
29
+        },
30
+        {
31
+            buttonContentBuilder: () => <p className="header-4">Header 4</p>,
32
+            execute: (state) => setHeader(state, "#### "),
33
+        },
34
+    ],
35
+};

+ 27
- 0
source/react-mde/src/commands/imageCommand.tsx View File

@@ -0,0 +1,27 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {insertText} from "../util/MarkdownUtil";
4
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
5
+
6
+export const imageCommand: Command = {
7
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("image"),
8
+
9
+    buttonProps: { "aria-label": "Insert a picture" },
10
+
11
+    execute: (state) => {
12
+        const {text, selection} = getMarkdownStateFromDraftState(state);
13
+        const {newText, insertionLength} = insertText(text, "![", selection.start);
14
+        const finalText = insertText(newText, "](image-url)", selection.end + insertionLength).newText;
15
+
16
+        return buildNewDraftState(
17
+            state,
18
+            {
19
+                text: finalText,
20
+                selection: {
21
+                    start: selection.start + insertionLength,
22
+                    end: selection.end + insertionLength,
23
+                },
24
+            },
25
+        );
26
+    },
27
+};

+ 35
- 0
source/react-mde/src/commands/index.ts View File

@@ -0,0 +1,35 @@
1
+import { boldCommand } from "./boldCommand";
2
+import { codeCommand} from "./codeCommand";
3
+import { headerCommand } from "./headerCommand";
4
+import { imageCommand } from "./imageCommand";
5
+import { italicCommand } from "./italicCommand";
6
+import { strikethroughCommand } from "./strikethroughCommand";
7
+import { linkCommand } from "./linkCommand";
8
+import { orderedListCommand } from "./orderedCommand";
9
+import { quoteCommand } from "./quoteCommand";
10
+import { unorderedListCommand } from "./unorderedListCommand";
11
+import { checkListCommand } from "./checkListCommand";
12
+import { tabCommand } from "./tabCommand";
13
+import {Command} from "../types";
14
+
15
+const getDefaultCommands: () => Command[][] = () => [
16
+    [headerCommand, boldCommand, italicCommand, strikethroughCommand],
17
+    [linkCommand, quoteCommand, codeCommand, imageCommand],
18
+    [unorderedListCommand, orderedListCommand, checkListCommand],
19
+];
20
+
21
+export {
22
+    boldCommand,
23
+    codeCommand,
24
+    headerCommand,
25
+    imageCommand,
26
+    italicCommand,
27
+    strikethroughCommand,
28
+    linkCommand,
29
+    orderedListCommand,
30
+    quoteCommand,
31
+    unorderedListCommand,
32
+    checkListCommand,
33
+    tabCommand,
34
+    getDefaultCommands,
35
+};

+ 16
- 0
source/react-mde/src/commands/italicCommand.tsx View File

@@ -0,0 +1,16 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {insertBeforeAndAfter} from "../util/MarkdownUtil";
4
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
5
+
6
+export const italicCommand: Command = {
7
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("italic"),
8
+
9
+    buttonProps: { "aria-label": "Add italic text" },
10
+
11
+    execute: (state) => {
12
+        let mdState = getMarkdownStateFromDraftState(state);
13
+        mdState = insertBeforeAndAfter(mdState, "_");
14
+        return buildNewDraftState(state, mdState);
15
+    },
16
+};

+ 28
- 0
source/react-mde/src/commands/linkCommand.tsx View File

@@ -0,0 +1,28 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {insertText, selectWordIfCaretIsInsideOne} from "../util/MarkdownUtil";
4
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
5
+
6
+export const linkCommand: Command = {
7
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("link"),
8
+
9
+    buttonProps: { "aria-label": "Insert a link" },
10
+
11
+    execute: (state) => {
12
+        const {text, selection} = getMarkdownStateFromDraftState(state);
13
+        const newSelection = selectWordIfCaretIsInsideOne({text, selection});
14
+        const {newText, insertionLength} = insertText(text, "[", newSelection.start);
15
+        const finalText = insertText(newText, "](url)", newSelection.end + insertionLength).newText;
16
+
17
+        return buildNewDraftState(
18
+            state,
19
+            {
20
+                text: finalText,
21
+                selection: {
22
+                    start: newSelection.start + insertionLength,
23
+                    end: newSelection.end + insertionLength,
24
+                },
25
+            },
26
+        );
27
+    },
28
+};

+ 16
- 0
source/react-mde/src/commands/orderedCommand.tsx View File

@@ -0,0 +1,16 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {makeList} from "../util/MarkdownUtil";
4
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
5
+
6
+export const orderedListCommand: Command = {
7
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("list-ol"),
8
+
9
+    buttonProps: { "aria-label": "Insert numbered list" },
10
+
11
+    execute: (state) => {
12
+        let mdState = getMarkdownStateFromDraftState(state);
13
+        mdState = makeList(mdState, (item, index) => `${index + 1}. `);
14
+        return buildNewDraftState(state, mdState);
15
+    },
16
+};

+ 35
- 0
source/react-mde/src/commands/quoteCommand.tsx View File

@@ -0,0 +1,35 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {
4
+    insertBefore, insertBreaksAfterSoThatThereIsAnEmptyLineAfter,
5
+    insertBreaksBeforeSoThatThereIsAnEmptyLineBefore,
6
+    selectWordIfCaretIsInsideOne,
7
+} from "../util/MarkdownUtil";
8
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
9
+
10
+export const quoteCommand: Command = {
11
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("quote-right"),
12
+
13
+    buttonProps: { "aria-label": "Insert a quote" },
14
+
15
+    execute: (state) => {
16
+        let {text, selection} = getMarkdownStateFromDraftState(state);
17
+        selection = selectWordIfCaretIsInsideOne({text, selection});
18
+
19
+        let textInsertion;
20
+
21
+        textInsertion = insertBreaksBeforeSoThatThereIsAnEmptyLineBefore({text, selection});
22
+        text = textInsertion.newText;
23
+        selection = textInsertion.newSelection;
24
+
25
+        textInsertion = insertBefore(text, "> ", selection, false);
26
+        text = textInsertion.newText;
27
+        selection = textInsertion.newSelection;
28
+
29
+        textInsertion = insertBreaksAfterSoThatThereIsAnEmptyLineAfter({text, selection});
30
+        text = textInsertion.newText;
31
+        selection = textInsertion.newSelection;
32
+
33
+        return buildNewDraftState(state, {text, selection});
34
+    },
35
+};

+ 16
- 0
source/react-mde/src/commands/strikethroughCommand.tsx View File

@@ -0,0 +1,16 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {insertBeforeAndAfter} from "../util/MarkdownUtil";
4
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
5
+
6
+export const strikethroughCommand: Command = {
7
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("strikethrough"),
8
+
9
+    buttonProps: { "aria-label": "Add strikethrough text" },
10
+
11
+    execute: (state) => {
12
+        let mdState = getMarkdownStateFromDraftState(state);
13
+        mdState = insertBeforeAndAfter(mdState, "~~");
14
+        return buildNewDraftState(state, mdState);
15
+    },
16
+};

+ 15
- 0
source/react-mde/src/commands/tabCommand.tsx View File

@@ -0,0 +1,15 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {onTab} from "../util/MarkdownUtil";
4
+import {getMarkdownStateFromDraftState, buildNewDraftState} from "../util/DraftUtil";
5
+
6
+export const tabCommand: Command = {
7
+    buttonContentBuilder: () => null,
8
+    buttonProps: null,
9
+
10
+    execute: (state, reverse) => {
11
+        let mdState = getMarkdownStateFromDraftState(state);
12
+        mdState = onTab(mdState, reverse);
13
+        return buildNewDraftState(state, mdState);
14
+    },
15
+};

+ 16
- 0
source/react-mde/src/commands/unorderedListCommand.tsx View File

@@ -0,0 +1,16 @@
1
+import * as React from "react";
2
+import {Command} from "../types";
3
+import {makeList} from "../util/MarkdownUtil";
4
+import {buildNewDraftState, getMarkdownStateFromDraftState} from "../util/DraftUtil";
5
+
6
+export const unorderedListCommand: Command = {
7
+    buttonContentBuilder: ({ iconProvider }) => iconProvider("list-ul"),
8
+
9
+    buttonProps: { "aria-label": "Insert a bulleted list" },
10
+
11
+    execute: (state) => {
12
+        let mdState = getMarkdownStateFromDraftState(state);
13
+        mdState = makeList(mdState, "- ");
14
+        return buildNewDraftState(state, mdState);
15
+    },
16
+};

+ 60
- 0
source/react-mde/src/components-layout/HorizontalLayout.tsx View File

@@ -0,0 +1,60 @@
1
+import * as React from "react";
2
+import {Command, LayoutProps} from "../types";
3
+import {ReactMde} from "../ReactMde";
4
+import {MdePreview, MdeEditor, MdeToolbar} from "../components";
5
+import * as classNames from "classnames";
6
+
7
+export class HorizontalLayout extends React.Component<LayoutProps, {}> {
8
+    editorRef: MdeEditor;
9
+    previewRef: MdePreview;
10
+
11
+    /**
12
+     * Handler for the textArea value change
13
+     * @memberOf ReactMde
14
+     */
15
+    handleMdeStateChange = (value) => {
16
+        const {onChange} = this.props;
17
+        onChange(value);
18
+    }
19
+
20
+    handleCommand = (command: Command) => {
21
+        const {onCommand} = this.props;
22
+        onCommand(command);
23
+    }
24
+
25
+    /**
26
+     * Renders react-mde
27
+     * @returns
28
+     * @memberOf ReactMde
29
+     */
30
+    render() {
31
+        const {buttonContentOptions, commands, mdeEditorState, layoutOptions, emptyPreviewHtml, readOnly} = this.props;
32
+        const finalLayoutOptions = layoutOptions ? {...layoutOptions} : {};
33
+
34
+        return (
35
+            <div className="react-mde-horizontal-layout">
36
+                <MdeToolbar
37
+                    buttonContentOptions={buttonContentOptions}
38
+                    commands={commands}
39
+                    onCommand={this.handleCommand}
40
+                    readOnly={readOnly}
41
+                />
42
+                <div className="mde-content">
43
+                    <MdeEditor
44
+                        className={classNames(finalLayoutOptions.editorClassName)}
45
+                        editorRef={(c) => this.editorRef = c}
46
+                        onChange={this.handleMdeStateChange}
47
+                        editorState={mdeEditorState}
48
+                        readOnly={readOnly}
49
+                    />
50
+                    <MdePreview
51
+                        className={classNames(finalLayoutOptions.editorClassName)}
52
+                        previewRef={(c) => this.previewRef = c}
53
+                        html={mdeEditorState ? mdeEditorState.html : ""}
54
+                        emptyPreviewHtml={emptyPreviewHtml}
55
+                    />
56
+                </div>
57
+            </div>
58
+        );
59
+    }
60
+}

+ 51
- 0
source/react-mde/src/components-layout/NoPreviewLayout.tsx View File

@@ -0,0 +1,51 @@
1
+import * as React from "react";
2
+import {Command, LayoutProps} from "../types";
3
+import {ReactMde} from "../ReactMde";
4
+import {MdePreview, MdeEditor, MdeToolbar} from "../components";
5
+
6
+export class NoPreviewLayout extends React.Component<LayoutProps, {}> {
7
+
8
+    editorRef: MdeEditor;
9
+    previewRef: MdePreview;
10
+
11
+    /**
12
+     * Handler for the textArea value change
13
+     * @memberOf ReactMde
14
+     */
15
+    handleMdeStateChange = (value) => {
16
+        const {onChange} = this.props;
17
+        onChange(value);
18
+    }
19
+
20
+    handleCommand = (command: Command) => {
21
+        const { onCommand } = this.props;
22
+        onCommand(command);
23
+    }
24
+
25
+    /**
26
+     * Renders react-mde
27
+     * @returns
28
+     * @memberOf ReactMde
29
+     */
30
+    render() {
31
+
32
+        const { buttonContentOptions, commands, mdeEditorState, readOnly } = this.props;
33
+
34
+        return (
35
+            <div className="react-mde-no-preview-layout">
36
+                <MdeToolbar
37
+                    buttonContentOptions={buttonContentOptions}
38
+                    commands={commands}
39
+                    onCommand={this.handleCommand}
40
+                    readOnly={readOnly}
41
+                />
42
+                <MdeEditor
43
+                    editorRef={(c) => this.editorRef = c}
44
+                    onChange={this.handleMdeStateChange}
45
+                    editorState={mdeEditorState}
46
+                    readOnly={readOnly}
47
+                />
48
+            </div>
49
+        );
50
+    }
51
+}

+ 90
- 0
source/react-mde/src/components-layout/TabbedLayout.tsx View File

@@ -0,0 +1,90 @@
1
+import * as React from "react";
2
+import {Command, LayoutProps} from "../types";
3
+import {ReactMde} from "../ReactMde";
4
+import {MdePreview, MdeEditor, MdeToolbar} from "../components";
5
+
6
+export const TAB_CODE: string = "TAB_CODE";
7
+export const TAB_PREVIEW: string = "TAB_PREVIEW";
8
+
9
+export class TabbedLayout extends React.Component<LayoutProps, {}> {
10
+    state = {
11
+        tab: TAB_CODE,
12
+    };
13
+
14
+    editorRef: MdeEditor;
15
+    previewRef: MdePreview;
16
+
17
+    /**
18
+     * Handler for the textArea value change
19
+     * @memberOf ReactMde
20
+     */
21
+    handleMdeStateChange = (value) => {
22
+        const {onChange} = this.props;
23
+        onChange(value);
24
+    }
25
+
26
+    handleCommand = (command: Command) => {
27
+        const { onCommand } = this.props;
28
+        onCommand(command);
29
+    }
30
+
31
+    /**
32
+     * Renders react-mde
33
+     * @returns
34
+     * @memberOf ReactMde
35
+     */
36
+    render() {
37
+
38
+        const { buttonContentOptions, commands, mdeEditorState, emptyPreviewHtml, readOnly } = this.props;
39
+
40
+        let styleTabCode = "mde-tab";
41
+        let styleTabPreview = "mde-tab";
42
+        switch (this.state.tab) {
43
+            case TAB_CODE: styleTabCode += " mde-tab-activated"; break;
44
+            case TAB_PREVIEW: styleTabPreview += " mde-tab-activated"; break;
45
+        }
46
+
47
+        return (
48
+            <div className="react-mde-tabbed-layout">
49
+                <MdeToolbar
50
+                    buttonContentOptions={buttonContentOptions}
51
+                    commands={commands}
52
+                    onCommand={this.handleCommand}
53
+                    readOnly={readOnly}
54
+                >
55
+                    <div className="mde-tabs">
56
+                        <button
57
+                            type="button"
58
+                            className={styleTabCode}
59
+                            onClick={() => this.setState({tab: TAB_CODE})}
60
+                        >
61
+                            Code
62
+                        </button>
63
+                        <button
64
+                            type="button"
65
+                            className={styleTabPreview}
66
+                            onClick={() => this.setState({tab: TAB_PREVIEW})}
67
+                        >
68
+                            Preview
69
+                        </button>
70
+                    </div>
71
+                </MdeToolbar>
72
+                {
73
+                    this.state.tab === TAB_CODE ?
74
+                        <MdeEditor
75
+                            editorRef={(c) => this.editorRef = c}
76
+                            onChange={this.handleMdeStateChange}
77
+                            editorState={mdeEditorState}
78
+                            readOnly={readOnly}
79
+                        />
80
+                    :
81
+                        < MdePreview
82
+                            previewRef={(c) => this.previewRef = c}
83
+                            html={mdeEditorState ? mdeEditorState.html : ""}
84
+                            emptyPreviewHtml={emptyPreviewHtml}
85
+                        />
86
+                }
87
+            </div>
88
+        );
89
+    }
90
+}

+ 60
- 0
source/react-mde/src/components-layout/VerticalLayout.tsx View File

@@ -0,0 +1,60 @@
1
+import * as React from "react";
2
+import {Command, LayoutProps} from "../types";
3
+import {ReactMde} from "../ReactMde";
4
+import {MdePreview, MdeEditor, MdeToolbar} from "../components";
5
+import * as classNames from "classnames";
6
+
7
+export class VerticalLayout extends React.Component<LayoutProps, {}> {
8
+    editorRef: MdeEditor;
9
+    previewRef: MdePreview;
10
+
11
+    /**
12
+     * Handler for the textArea value change
13
+     * @memberOf ReactMde
14
+     */
15
+    handleMdeStateChange = (value) => {
16
+        const {onChange} = this.props;
17
+        onChange(value);
18
+    }
19
+
20
+    handleCommand = (command: Command) => {
21
+        const {onCommand} = this.props;
22
+        onCommand(command);
23
+    }
24
+
25
+    /**
26
+     * Renders react-mde
27
+     * @returns
28
+     * @memberOf ReactMde
29
+     */
30
+    render() {
31
+        const {buttonContentOptions, commands, mdeEditorState, layoutOptions, emptyPreviewHtml, readOnly} = this.props;
32
+        const finalLayoutOptions = layoutOptions ? {...layoutOptions} : {};
33
+
34
+        return (
35
+            <div className="react-mde-vertical-layout">
36
+                <MdeToolbar
37
+                    buttonContentOptions={buttonContentOptions}
38
+                    commands={commands}
39
+                    onCommand={this.handleCommand}
40
+                    readOnly={readOnly}
41
+                />
42
+                <div className="react-mde-content">
43
+                    <MdeEditor
44
+                        className={classNames(finalLayoutOptions.editorClassName)}
45
+                        editorRef={(c) => this.editorRef = c}
46
+                        onChange={this.handleMdeStateChange}
47
+                        editorState={mdeEditorState}
48
+                        readOnly={readOnly}
49
+                    />
50
+                    <MdePreview
51
+                        className={classNames(finalLayoutOptions.previewClassName)}
52
+                        previewRef={(c) => this.previewRef = c}
53
+                        html={mdeEditorState ? mdeEditorState.html : ""}
54
+                        emptyPreviewHtml={emptyPreviewHtml}
55
+                    />
56
+                </div>
57
+            </div>
58
+        );
59
+    }
60
+}

+ 4
- 0
source/react-mde/src/components-layout/index.tsx View File

@@ -0,0 +1,4 @@
1
+export * from "./VerticalLayout";
2
+export * from "./NoPreviewLayout";
3
+export * from "./HorizontalLayout";
4
+export * from "./TabbedLayout";

+ 77
- 0
source/react-mde/src/components/MdeEditor.tsx View File

@@ -0,0 +1,77 @@
1
+import * as React from "react";
2
+import {MdeState, Command} from "../types";
3
+import {Editor, EditorState} from "draft-js";
4
+import {boldCommand, codeCommand, italicCommand, tabCommand} from "../commands";
5
+
6
+export interface MdeEditorProps {
7
+    className?: string;
8
+    onChange: (value: EditorState) => void;
9
+    editorRef?: (ref: MdeEditor) => void;
10
+    editorState: MdeState;
11
+    readOnly: boolean;
12
+}
13
+
14
+export class MdeEditor extends React.Component<MdeEditorProps, {}> {
15
+    editorRef: Editor;
16
+
17
+    handleOnChange = (editorState: EditorState) => {
18
+        const {onChange} = this.props;
19
+        onChange(editorState);
20
+    }
21
+
22
+    executeCastAsEditorState = (commandToExecute: Command, editorState: EditorState, data?: any): EditorState => {
23
+        const newEditorState = commandToExecute.execute(editorState, data) as EditorState;
24
+        return newEditorState;
25
+    }
26
+
27
+    handleKeyCommand = (command, editorState) => {
28
+        const {onChange} = this.props;
29
+        switch (command) {
30
+            case "bold":
31
+                onChange(this.executeCastAsEditorState(boldCommand, editorState));
32
+                return "handled";
33
+
34
+            case "italic":
35
+                onChange(this.executeCastAsEditorState(italicCommand, editorState));
36
+                return "handled";
37
+
38
+            case "code":
39
+                onChange(this.executeCastAsEditorState(codeCommand, editorState));
40
+                return "handled";
41
+
42
+            default:
43
+                return "not-handled";
44
+        }
45
+    }
46
+
47
+    handleTab = (event) => {
48
+        event.preventDefault();
49
+
50
+        const {
51
+            editorState: {draftEditorState},
52
+            onChange,
53
+        } = this.props;
54
+        onChange(this.executeCastAsEditorState(tabCommand, draftEditorState, event.shiftKey));
55
+    }
56
+
57
+    render() {
58
+        const {
59
+            editorState: {draftEditorState},
60
+            className,
61
+            readOnly,
62
+        } = this.props;
63
+        return (
64
+            <div className={`mde-text ${className || ""}`}>
65
+                <Editor
66
+                    ref={(editor) => (this.editorRef = editor)}
67
+                    stripPastedStyles={true}
68
+                    editorState={draftEditorState}
69
+                    onChange={this.handleOnChange}
70
+                    onTab={this.handleTab}
71
+                    handleKeyCommand={this.handleKeyCommand}
72
+                    readOnly={readOnly}
73
+                />
74
+            </div>
75
+        );
76
+    }
77
+}

+ 28
- 0
source/react-mde/src/components/MdePreview.tsx View File

@@ -0,0 +1,28 @@
1
+import * as React from "react";
2
+
3
+export interface ReactMdePreviewProps {
4
+    className?: string;
5
+    previewRef?: (ref: MdePreview) => void;
6
+    html: string;
7
+    emptyPreviewHtml: string;
8
+}
9
+
10
+export interface MdePreviewState {
11
+}
12
+
13
+export class MdePreview extends React.Component<ReactMdePreviewProps, MdePreviewState> {
14
+    previewRef: HTMLDivElement;
15
+
16
+    render() {
17
+        const {html, className} = this.props;
18
+        return (
19
+            <div className={`mde-preview ${className || ""}`}>
20
+                <div
21
+                    className="mde-preview-content"
22
+                    dangerouslySetInnerHTML={{__html: html || "<p>&nbsp;</p>" }}
23
+                    ref={(p) => this.previewRef = p}
24
+                />
25
+            </div>
26
+        );
27
+    }
28
+}

+ 56
- 0
source/react-mde/src/components/MdeToolbar.tsx View File

@@ -0,0 +1,56 @@
1
+import * as React from "react";
2
+import {Command, ButtonContentOptions} from "../types";
3
+import {MdeToolbarButtonGroup} from "./MdeToolbarButtonGroup";
4
+import {MdeToolbarDropdown} from "./MdeToolbarDropdown";
5
+import {MdeToolbarButton} from "./MdeToolbarButton";
6
+
7
+export interface MdeToolbarProps {
8
+    buttonContentOptions: ButtonContentOptions;
9
+    commands: Command[][];
10
+    onCommand: (command: Command) => void;
11
+    readOnly: boolean;
12
+}
13
+
14
+export const MdeToolbar: React.SFC<MdeToolbarProps> = (props) => {
15
+    const {buttonContentOptions, children, commands, onCommand, readOnly} = props;
16
+    if ((!commands || commands.length === 0) && !children) {
17
+        return null;
18
+    }
19
+    return (
20
+        <div className="mde-header">
21
+            {
22
+                commands.map((cg: Command[], i: number) => (
23
+                    <MdeToolbarButtonGroup key={i}>
24
+                        {
25
+                            cg.map((c: Command, j) => {
26
+                                if (c.children) {
27
+                                    return (
28
+                                        <MdeToolbarDropdown
29
+                                            key={j}
30
+                                            buttonProps={c.buttonProps}
31
+                                            buttonContentOptions={buttonContentOptions}
32
+                                            buttonContent={c.buttonContentBuilder(buttonContentOptions)}
33
+                                            commands={c.children}
34
+                                            onCommand={(cmd) => onCommand(cmd)}
35
+                                            readOnly={readOnly}
36
+                                        />
37
+                                    );
38
+                                }
39
+                                return <MdeToolbarButton
40
+                                    key={j}
41
+                                    buttonContent={c.buttonContentBuilder(buttonContentOptions)}
42
+                                    buttonProps={c.buttonProps}
43
+                                    onClick={() => onCommand(c as Command)}
44
+                                    readOnly={readOnly}
45
+                                    buttonComponentClass={c.buttonComponentClass}
46
+                                />;
47
+                            })
48
+                        }
49
+                    </MdeToolbarButtonGroup>))
50
+            }
51
+            <div className="mde-toolbar-children">
52
+                {children}
53
+            </div>
54
+        </div>
55
+    );
56
+};

+ 24
- 0
source/react-mde/src/components/MdeToolbarButton.tsx View File

@@ -0,0 +1,24 @@
1
+import * as React from "react";
2
+
3
+export interface MdeToolbarButtonProps {
4
+    buttonComponentClass?: React.ComponentClass | string;
5
+    buttonProps: any;
6
+    buttonContent: React.ReactNode;
7
+    onClick: React.MouseEventHandler<any>;
8
+    readOnly: boolean;
9
+}
10
+
11
+export const MdeToolbarButton: React.SFC<MdeToolbarButtonProps> = (props) => {
12
+    const {buttonComponentClass, buttonContent, buttonProps, onClick, readOnly} = props;
13
+    const finalButtonComponent = buttonComponentClass || "button";
14
+    return (
15
+        <li className="mde-header-item">
16
+            {React.createElement(finalButtonComponent, {
17
+                ...buttonProps, ...{
18
+                    onClick,
19
+                    disabled: readOnly,
20
+                },
21
+            }, buttonContent)}
22
+        </li>
23
+    );
24
+};

+ 12
- 0
source/react-mde/src/components/MdeToolbarButtonGroup.tsx View File

@@ -0,0 +1,12 @@
1
+import * as React from "react";
2
+
3
+export interface MdeToolbarButtonGroupProps {
4
+}
5
+
6
+export const MdeToolbarButtonGroup: React.SFC<MdeToolbarButtonGroupProps> = (props) => {
7
+    return (
8
+        <ul className="mde-header-group">
9
+            {props.children}
10
+        </ul>
11
+    );
12
+};

+ 124
- 0
source/react-mde/src/components/MdeToolbarDropdown.tsx View File

@@ -0,0 +1,124 @@
1
+import * as React from "react";
2
+import {Command, ButtonContentOptions} from "../types";
3
+import {MdeToolbarButton} from "./MdeToolbarButton";
4
+
5
+export interface HeaderItemDropdownProps {
6
+    buttonContentOptions: ButtonContentOptions;
7
+    buttonContent: React.ReactNode;
8
+    buttonProps: any;
9
+    commands: Command[];
10
+    onCommand: (command: Command) => void;
11
+    readOnly: boolean;
12
+}
13
+
14
+export interface HeaderItemDropdownState {
15
+    open: boolean;
16
+}
17
+
18
+export class MdeToolbarDropdown extends React.Component<HeaderItemDropdownProps, HeaderItemDropdownState> {
19
+
20
+    dropdown: any; // TODO: Change this type
21
+    dropdownOpener: any; // TODO: Change this type
22
+
23
+    constructor(props: HeaderItemDropdownProps) {
24
+        super(props);
25
+        this.state = {
26
+            open: false,
27
+        };
28
+    }
29
+
30
+    componentDidMount() {
31
+        document.addEventListener("click", this.handleGlobalClick, false);
32
+    }
33
+
34
+    componentWillUnmount() {
35
+        document.removeEventListener("click", this.handleGlobalClick, false);
36
+    }
37
+
38
+    handleGlobalClick: EventListenerOrEventListenerObject = (e: Event) => {
39
+        if (this.clickedOutside(e)) {
40
+            this.closeDropdown();
41
+        }
42
+    }
43
+
44
+    openDropdown = () => {
45
+        this.setState({
46
+            open: true,
47
+        });
48
+    }
49
+
50
+    closeDropdown() {
51
+        this.setState({
52
+            open: false,
53
+        });
54
+    }
55
+
56
+    clickedOutside = (e: Event) => {
57
+        const {target} = e;
58
+        return this.state.open
59
+            && this.dropdown
60
+            && this.dropdownOpener
61
+            && !this.dropdown.contains(target)
62
+            && !this.dropdownOpener.contains(target);
63
+    }
64
+
65
+    handleOnClickCommand = (e: React.SyntheticEvent<any>, command: Command) => {
66
+        const {onCommand} = this.props;
67
+        onCommand(command);
68
+        this.closeDropdown();
69
+    }
70
+
71
+    handleClick = () => {
72
+        if (!this.state.open)
73
+            this.openDropdown();
74
+        else
75
+            this.closeDropdown();
76
+    }
77
+
78
+    render() {
79
+        const {buttonContentOptions, commands, readOnly} = this.props;
80
+        const {open} = this.state;
81
+
82
+        const items = commands.map((command, index) => (
83
+            <MdeToolbarButton
84
+                key={`header-item${index}`}
85
+                buttonProps={command.buttonProps}
86
+                buttonContent={command.buttonContentBuilder(buttonContentOptions)}
87
+                onClick={(e) => this.handleOnClickCommand(e, command)}
88
+                readOnly={readOnly}
89
+            />
90
+        ));
91
+
92
+        const dropdown = open
93
+            ? (
94
+                <ul
95
+                    className="react-mde-dropdown"
96
+                    ref={(ref) => {
97
+                        this.dropdown = ref;
98
+                    }}
99
+                >
100
+                    {items}
101
+                </ul>
102
+            )
103
+            : null;
104
+
105
+        const {buttonContent, buttonProps} = this.props;
106
+
107
+        return (
108
+            <li className="mde-header-item">
109
+                <button
110
+                    type="button"
111
+                    {...buttonProps}
112
+                    ref={(ref) => {
113
+                        this.dropdownOpener = ref;
114
+                    }}
115
+                    onClick={this.handleClick}
116
+                    disabled={readOnly}
117
+                >
118
+                    {buttonContent}
119
+                </button>
120
+                {dropdown}
121
+            </li>
122
+        );
123
+    }
124
+}

+ 9
- 0
source/react-mde/src/components/MdeToolbarIcon.tsx View File

@@ -0,0 +1,9 @@
1
+import * as React from "react";
2
+
3
+export interface MdeToolbarIconProps {
4
+    icon: string;
5
+}
6
+
7
+export const MdeToolbarIcon: React.SFC<MdeToolbarIconProps> = (({icon}) => {
8
+    return <i className={`fas fa-${icon}`} aria-hidden="true"/>;
9
+});

+ 7
- 0
source/react-mde/src/components/index.tsx View File

@@ -0,0 +1,7 @@
1
+export * from "./MdeToolbarButtonGroup";
2
+export * from "./MdeToolbarButton";
3
+export * from "./MdeToolbarDropdown";
4
+export * from "./MdeToolbarIcon";
5
+export * from "./MdePreview";
6
+export * from "./MdeEditor";
7
+export * from "./MdeToolbar";

+ 19
- 0
source/react-mde/src/index.tsx View File

@@ -0,0 +1,19 @@
1
+import * as ReactMdeCommands from "./commands";
2
+import * as ReactMdeTypes from "./types";
3
+import * as ReactMdeComponents from "./components";
4
+import * as ReactMdeLayoutComponents from "./components-layout";
5
+import * as DraftUtil from "./util/DraftUtil";
6
+import * as MarkdownUtil from "./util/MarkdownUtil";
7
+import { ReactMde, ReactMdeProps } from "./ReactMde";
8
+
9
+export {
10
+    ReactMdeTypes,
11
+    ReactMdeCommands,
12
+    ReactMdeComponents,
13
+    ReactMdeLayoutComponents,
14
+    ReactMdeProps,
15
+    DraftUtil,
16
+    MarkdownUtil,
17
+};
18
+
19
+export default ReactMde;

+ 8
- 0
source/react-mde/src/styles/react-mde-all.scss View File

@@ -0,0 +1,8 @@
1
+@import "react-mde-toolbar.scss";
2
+@import "react-mde-editor.scss";
3
+@import "react-mde-preview.scss";
4
+@import "react-mde.scss";
5
+@import "react-mde-vertical-layout.scss";
6
+@import "react-mde-no-preview-layout.scss";
7
+@import "react-mde-horizontal-layout.scss";
8
+@import "react-mde-tabbed-layout.scss";

+ 10
- 0
source/react-mde/src/styles/react-mde-editor.scss View File

@@ -0,0 +1,10 @@
1
+@import "variables.scss";
2
+
3
+.mde-text {
4
+
5
+  .public-DraftEditor-content {
6
+    width: 100%;
7
+    min-height: $mde-editor-default-min-height;
8
+    padding: $mde-editor-padding;
9
+  }
10
+}

+ 40
- 0
source/react-mde/src/styles/react-mde-horizontal-layout.scss View File

@@ -0,0 +1,40 @@
1
+@import "variables.scss";
2
+
3
+.react-mde-horizontal-layout {
4
+    height: 100%;
5
+    display: flex;
6
+    flex-direction: column;
7
+
8
+    .mde-tabs {
9
+        display: flex;
10
+        align-items: stretch;
11
+
12
+        .mde-tab {
13
+            border: none;
14
+            border-left: 1px solid $mde-border-color;
15
+            padding: 10px;
16
+            background: none;
17
+        }
18
+
19
+        .mde-tab:hover {
20
+            cursor: pointer;
21
+        }
22
+    }
23
+
24
+    .mde-content {
25
+        display: flex;
26
+        height: 100%;
27
+
28
+        .mde-text {
29
+            min-width: 50%;
30
+            overflow-y: auto;
31
+            border-radius: 0;
32
+        }
33
+
34
+        .mde-preview {
35
+            min-width: 50%;
36
+            border-left: 1px solid $mde-border-color;
37
+            overflow-y: auto;
38
+        }
39
+    }
40
+}

+ 11
- 0
source/react-mde/src/styles/react-mde-no-preview-layout.scss View File

@@ -0,0 +1,11 @@
1
+@import "variables.scss";
2
+
3
+.react-mde-no-preview-layout {
4
+  height: 100%;
5
+  display: flex;
6
+  flex-direction: column;
7
+
8
+  .mde-text {
9
+    overflow-y: auto;
10
+  }
11
+}

+ 127
- 0
source/react-mde/src/styles/react-mde-preview.scss View File

@@ -0,0 +1,127 @@
1
+@import "variables.scss";
2
+
3
+.mde-preview {
4
+  min-height: $mde-preview-default-min-height;
5
+  .mde-preview-content {
6
+    padding: $mde-preview-padding;
7
+
8
+    p, blockquote, ul, ol, dl, table, pre {
9
+      margin-top: 0;
10
+      margin-bottom: 16px;
11
+    }
12
+
13
+    h1, h2, h3 {
14
+      margin-top: 24px;
15
+      margin-bottom: 16px;
16
+      font-weight: 600;
17
+      line-height: 1.25;
18
+      border-bottom: 1px solid #eee;
19
+      padding-bottom: 0.3em;
20
+    }
21
+    h1 {
22
+      font-size: 1.6em;
23
+    }
24
+    h2 {
25
+      font-size: 1.4em;
26
+    }
27
+    h3 {
28
+      font-size: 1.2em;
29
+    }
30
+    ul, ol {
31
+      padding-left: 2em;
32
+    }
33
+    blockquote {
34
+      margin-left: 0;
35
+      padding: 0 1em;
36
+      color: #777;
37
+      border-left: 0.25em solid #ddd;
38
+      & > :first-child {
39
+        margin-top: 0;
40
+      }
41
+      & > :last-child {
42
+        margin-bottom: 0;
43
+      }
44
+    }
45
+
46
+    code {
47
+      padding: 0.2em 0 0.2em 0;
48
+      margin: 0;
49
+      font-size: 90%;
50
+      background-color: rgba(0, 0, 0, 0.04);
51
+      border-radius: 3px;
52
+      &::before, &::after {
53
+        letter-spacing: -0.2em;
54
+        content: "\00a0";
55
+      }
56
+    }
57
+
58
+    pre {
59
+      padding: 16px;
60
+      overflow: auto;
61
+      font-size: 85%;
62
+      line-height: 1.45;
63
+      background-color: #f7f7f7;
64
+      border-radius: 3px;
65
+
66
+      code {
67
+        display: inline;
68
+        padding: 0;
69
+        margin: 0;
70
+        overflow: visible;
71
+        line-height: inherit;
72
+        word-wrap: normal;
73
+        background-color: transparent;
74
+        border: 0;
75
+        &::before, &::after {
76
+          content: none;
77
+        }
78
+      }
79
+
80
+      > code {
81
+        padding: 0;
82
+        margin: 0;
83
+        font-size: 100%;
84
+        word-break: normal;
85
+        white-space: pre;
86
+        background: transparent;
87
+        border: 0;
88
+      }
89
+    }
90
+
91
+    a {
92
+      color: #4078c0;
93
+      text-decoration: none;
94
+      &:hover {
95
+        text-decoration: underline;
96
+      }
97
+    }
98
+    & > *:first-child {
99
+      margin-top: 0 !important;
100
+    }
101
+    & > *:last-child {
102
+      margin-bottom: 0 !important;
103
+    }
104
+    &::after {
105
+      display: table;
106
+      clear: both;
107
+      content: "";
108
+    }
109
+
110
+    table {
111
+      display: block;
112
+      width: 100%;
113
+      border-spacing: 0;
114
+      border-collapse: collapse;
115
+      thead {
116
+        th {
117
+          font-weight: bold;
118
+        }
119
+      }
120
+      th, td {
121
+        padding: 6px 13px;
122
+        border: 1px solid $mde-border-color;
123
+      }
124
+    }
125
+  }
126
+}
127
+

+ 0
- 0
source/react-mde/src/styles/react-mde-tabbed-layout.scss View File


Some files were not shown because too many files changed in this diff