Merge branch 'master' into party-bugsa

This commit is contained in:
Kim Gardner
2018-03-23 13:54:52 -04:00
committed by GitHub
12 changed files with 321 additions and 218 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"exec": "npm-run-all --parallel generate-introspection start:development",
"ignore": ["test/*", "client/*", "dist/*", "plugins/*/client"],
"ignore": ["test/*", "client/*", "dist/*", "plugins/*/client", "docs/*"],
"ext": "js,json,graphql,yml",
"watch": [
".",
@@ -1,39 +1,15 @@
import React from 'react';
import QuestionBox from '../../../components/QuestionBox';
import { Icon, Spinner } from 'coral-ui';
import DefaultQuestionBoxIcon from '../../../components/DefaultQuestionBoxIcon';
import cn from 'classnames';
import styles from './QuestionBoxBuilder.css';
import { Icon } from 'coral-ui';
import MarkdownEditor from 'coral-framework/components/MarkdownEditor';
const DefaultIcon = <DefaultQuestionBoxIcon className={styles.defaultIcon} />;
const icons = [{ default: DefaultIcon }, 'forum', 'build', 'format_quote'];
class QuestionBoxBuilder extends React.Component {
constructor() {
super();
this.state = {
loading: true,
};
}
componentWillMount() {
this.loadEditor();
}
async loadEditor() {
const {
default: MarkdownEditor,
} = await import(/* webpackChunkName: "markdownEditor" */
'coral-framework/components/MarkdownEditor');
return this.setState({
loading: false,
MarkdownEditor,
});
}
render() {
const {
questionBoxIcon,
@@ -41,11 +17,6 @@ class QuestionBoxBuilder extends React.Component {
onContentChange,
onIconChange,
} = this.props;
const { loading, MarkdownEditor } = this.state;
if (loading) {
return <Spinner />;
}
return (
<div className={styles.root}>
@@ -1,154 +1,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import SimpleMDE from 'simplemde';
import cn from 'classnames';
import noop from 'lodash/noop';
import styles from './MarkdownEditor.css';
import { Spinner } from 'coral-ui';
import Loadable from 'react-loadable';
const config = {
status: false,
const MarkdownEditor = Loadable({
loader: () =>
import(/* webpackChunkName: "markdownEditor" */
'./loadable/MarkdownEditor'),
loading: Spinner,
});
// Do not download fontAwesome icons as we replace them with
// material icons.
autoDownloadFontAwesome: false,
// Disable built-in spell checker as it is very rudimentary.
spellChecker: false,
toolbar: [
{
name: 'bold',
action: SimpleMDE.toggleBold,
className: styles.iconBold,
title: 'Bold',
},
{
name: 'italic',
action: SimpleMDE.toggleItalic,
className: styles.iconItalic,
title: 'Italic',
},
{
name: 'title',
action: SimpleMDE.toggleHeadingSmaller,
className: styles.iconTitle,
title: 'Title, Subtitle, Heading',
},
'|',
{
name: 'quote',
action: SimpleMDE.toggleBlockquote,
className: styles.iconQuote,
title: 'Quote',
},
{
name: 'unordered-list',
action: SimpleMDE.toggleUnorderedList,
className: styles.iconUnorderedList,
title: 'Generic List',
},
{
name: 'ordered-list',
action: SimpleMDE.toggleOrderedList,
className: styles.iconOrderedList,
title: 'Numbered List',
},
'|',
{
name: 'link',
action: SimpleMDE.drawLink,
className: styles.iconLink,
title: 'Create Link',
},
{
name: 'image',
action: SimpleMDE.drawImage,
className: styles.iconImage,
title: 'Insert Image',
},
'|',
{
name: 'preview',
action: SimpleMDE.togglePreview,
className: cn(styles.iconPreview, 'no-disable'),
title: 'Toggle Preview',
},
{
name: 'side-by-side',
action: SimpleMDE.toggleSideBySide,
className: cn(styles.iconSideBySide, 'no-disable'),
title: 'Toggle Side by Side',
},
{
name: 'fullscreen',
action: SimpleMDE.toggleFullScreen,
className: cn(styles.iconFullscreen, 'no-disable'),
title: 'Toggle Fullscreen',
},
'|',
{
name: 'guide',
action: 'https://simplemde.com/markdown-guide',
className: styles.iconGuide,
title: 'Markdown Guide',
},
],
};
export default class MarkdownEditor extends Component {
textarea = null;
editor = null;
onRef = ref => (this.textarea = ref);
componentDidMount() {
this.editor = new SimpleMDE({
...config,
element: this.textarea,
});
// Don't trap the key, to stay accessible.
this.editor.codemirror.options.extraKeys['Tab'] = false;
this.editor.codemirror.options.extraKeys['Shift-Tab'] = false;
this.editor.codemirror.on('change', this.onChange);
}
componentWillReceiveProps(nextProps) {
if (
this.props.value !== nextProps.value &&
nextProps.value !== this.editor.value()
) {
this.editor.value(nextProps.value);
}
}
componentDidUpdate() {
// Workaround empty render issue.
// https://github.com/NextStepWebs/simplemde-markdown-editor/issues/313
this.editor.codemirror.refresh();
}
componentWillUnmount() {
this.editor.toTextArea();
}
onChange = () => {
if (this.props.onChange) {
this.props.onChange(this.editor.value());
}
};
render() {
return (
<div className={styles.wrapper}>
<textarea ref={this.onRef} {...this.props} onChange={noop} />
</div>
);
}
}
MarkdownEditor.propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
};
export default MarkdownEditor;
@@ -0,0 +1,154 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import SimpleMDE from 'simplemde';
import cn from 'classnames';
import noop from 'lodash/noop';
import styles from './MarkdownEditor.css';
const config = {
status: false,
// Do not download fontAwesome icons as we replace them with
// material icons.
autoDownloadFontAwesome: false,
// Disable built-in spell checker as it is very rudimentary.
spellChecker: false,
toolbar: [
{
name: 'bold',
action: SimpleMDE.toggleBold,
className: styles.iconBold,
title: 'Bold',
},
{
name: 'italic',
action: SimpleMDE.toggleItalic,
className: styles.iconItalic,
title: 'Italic',
},
{
name: 'title',
action: SimpleMDE.toggleHeadingSmaller,
className: styles.iconTitle,
title: 'Title, Subtitle, Heading',
},
'|',
{
name: 'quote',
action: SimpleMDE.toggleBlockquote,
className: styles.iconQuote,
title: 'Quote',
},
{
name: 'unordered-list',
action: SimpleMDE.toggleUnorderedList,
className: styles.iconUnorderedList,
title: 'Generic List',
},
{
name: 'ordered-list',
action: SimpleMDE.toggleOrderedList,
className: styles.iconOrderedList,
title: 'Numbered List',
},
'|',
{
name: 'link',
action: SimpleMDE.drawLink,
className: styles.iconLink,
title: 'Create Link',
},
{
name: 'image',
action: SimpleMDE.drawImage,
className: styles.iconImage,
title: 'Insert Image',
},
'|',
{
name: 'preview',
action: SimpleMDE.togglePreview,
className: cn(styles.iconPreview, 'no-disable'),
title: 'Toggle Preview',
},
{
name: 'side-by-side',
action: SimpleMDE.toggleSideBySide,
className: cn(styles.iconSideBySide, 'no-disable'),
title: 'Toggle Side by Side',
},
{
name: 'fullscreen',
action: SimpleMDE.toggleFullScreen,
className: cn(styles.iconFullscreen, 'no-disable'),
title: 'Toggle Fullscreen',
},
'|',
{
name: 'guide',
action: 'https://simplemde.com/markdown-guide',
className: styles.iconGuide,
title: 'Markdown Guide',
},
],
};
export default class MarkdownEditor extends Component {
textarea = null;
editor = null;
onRef = ref => (this.textarea = ref);
componentDidMount() {
this.editor = new SimpleMDE({
...config,
element: this.textarea,
});
// Don't trap the key, to stay accessible.
this.editor.codemirror.options.extraKeys['Tab'] = false;
this.editor.codemirror.options.extraKeys['Shift-Tab'] = false;
this.editor.codemirror.on('change', this.onChange);
}
componentWillReceiveProps(nextProps) {
if (
this.props.value !== nextProps.value &&
nextProps.value !== this.editor.value()
) {
this.editor.value(nextProps.value);
}
}
componentDidUpdate() {
// Workaround empty render issue.
// https://github.com/NextStepWebs/simplemde-markdown-editor/issues/313
this.editor.codemirror.refresh();
}
componentWillUnmount() {
this.editor.toTextArea();
}
onChange = () => {
if (this.props.onChange) {
this.props.onChange(this.editor.value());
}
};
render() {
return (
<div className={styles.wrapper}>
<textarea ref={this.onRef} {...this.props} onChange={noop} />
</div>
);
}
}
MarkdownEditor.propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
};
+2 -1
View File
@@ -2,4 +2,5 @@ public/*
!public/_redirects
.deploy*/
db.json
*.log
*.log
source/_data/introspection.json
+1 -1
View File
@@ -12,4 +12,4 @@ interact with Talk's GraphQL endpoint.
# GraphQL Schema
{% graphqldocs ../../client/coral-framework/graphql/introspection.json %}
{% graphqldocs _data/introspection.json %}
+11 -10
View File
@@ -66,28 +66,29 @@ function getManifest() {
}
/**
* resolve is a function that can be used in templates to resolve an asset from
* the manifest. In production, the manifest is cached.
* resolveFactory is a function that can be used in templates to resolve an
* asset from the manifest. In production, the manifest is cached.
*/
const resolve = (() => {
const createResolveFactory = (() => {
if (process.env.NODE_ENV === 'production') {
// In production, we should attempt to load the manifest early.
const manifest = getManifest();
return key => `${STATIC_URL}static/${manifest[key]}`;
return () => key => `${STATIC_URL}static/${manifest[key]}`;
}
// In dev mode, we are more forgiving and we always load the
// newest version of the manifest.
return key => {
return () => {
let manifest = {};
try {
const manifest = getManifest();
return `${STATIC_URL}static/${manifest[key]}`;
manifest = getManifest();
} catch (err) {
console.warn(err);
return '';
}
return key =>
key in manifest ? `${STATIC_URL}static/${manifest[key]}` : '';
};
})();
@@ -105,7 +106,7 @@ module.exports = async (req, res, next) => {
// Resolve will help resolving paths to static files
// using the manifest.
res.locals.resolve = resolve;
res.locals.resolve = createResolveFactory();
// Forward the request.
next();
+2 -1
View File
@@ -6,7 +6,7 @@
"private": true,
"scripts": {
"generate-introspection": "WEBPACK=TRUE NODE_ENV=test ./scripts/generateIntrospectionResult.js",
"clean": "rm -rf dist client/coral-framework/graphql/introspection.json",
"clean": "rm -rf dist client/coral-framework/graphql/introspection.json docs/source/_data/introspection.json",
"watch": "npm-run-all clean generate-introspection --parallel watch:*",
"watch:client": "NODE_ENV=development webpack --progress --watch",
"watch:server": "nodemon --config .nodemon.json",
@@ -165,6 +165,7 @@
"react-broadcast": "^0.6.2",
"react-dom": "^15.4.2",
"react-input-autosize": "^1.1.4",
"react-loadable": "^5.3.1",
"react-mdl": "^1.11.0",
"react-mdl-selectfield": "^0.2.0",
"react-paginate": "^5.0.0",
+131 -20
View File
@@ -1,7 +1,120 @@
#! /usr/bin/env node
const path = require('path');
const introspectionFilename = path.resolve(
const fs = require('fs');
const { graphql } = require('graphql');
const schema = require('../graph/schema');
// Copied from https://github.com/graphql/graphql-js/blob/f995c1f92e94d9c451104b6a0db8034165ef8640/src/utilities/introspectionQuery.js#L18-L113
// which is available in graphql@0.13.2
//
// TODO: remove when we upgrade to at least graphql@0.13.2.
function getIntrospectionQuery(options = {}) {
const descriptions = !(options && options.descriptions === false);
return `
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
${descriptions ? 'description' : ''}
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
${descriptions ? 'description' : ''}
fields(includeDeprecated: true) {
name
${descriptions ? 'description' : ''}
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
${descriptions ? 'description' : ''}
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
${descriptions ? 'description' : ''}
type { ...TypeRef }
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
`;
}
const generateIntrospectionResult = (resultLocation, options = {}) =>
graphql(schema, getIntrospectionQuery(options)).then(({ data }) => {
// Serialize the introspection result as JSON.
const introspectionResult = JSON.stringify(data, null, 2);
// Write the introspection result to the filesystem.
fs.writeFileSync(resultLocation, introspectionResult, 'utf8');
console.log(`Outputted result of introspectionQuery to ${resultLocation}`);
});
const graphIntrospectionFilename = path.resolve(
__dirname,
'..',
'client',
@@ -10,23 +123,21 @@ const introspectionFilename = path.resolve(
'introspection.json'
);
const fs = require('fs');
const { graphql, introspectionQuery } = require('graphql');
const schema = require('../graph/schema');
const docsIntrospectionFilename = path.resolve(
__dirname,
'..',
'docs',
'source',
'_data',
'introspection.json'
);
graphql(schema, introspectionQuery)
.then(({ data }) => {
// Serialize the introspection result as JSON.
const introspectionResult = JSON.stringify(data, null, 2);
// Write the introspection result to the filesystem.
fs.writeFileSync(introspectionFilename, introspectionResult, 'utf8');
console.log(
`Outputted result of introspectionQuery to ${introspectionFilename}`
);
})
.catch(err => {
console.error(err);
process.exit(1);
});
Promise.all([
generateIntrospectionResult(graphIntrospectionFilename, {
descriptions: false,
}),
generateIntrospectionResult(docsIntrospectionFilename),
]).catch(err => {
console.error(err);
process.exit(1);
});
+2 -1
View File
@@ -335,7 +335,8 @@ module.exports = [
{
output: {
library: 'Coral',
// don't hash the embed.
// don't hash the embed, cache-busting must be completed by the requester
// as this lives in a static template on the embed site.
filename: '[name].js',
},
plugins: [
+6
View File
@@ -9217,6 +9217,12 @@ react-linkify@^0.2.1:
prop-types "^15.5.8"
tlds "^1.57.0"
react-loadable@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/react-loadable/-/react-loadable-5.3.1.tgz#9699e9a08fed49bacd69caaa282034b62a76bcdd"
dependencies:
prop-types "^15.5.0"
react-mdl-selectfield@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/react-mdl-selectfield/-/react-mdl-selectfield-0.2.0.tgz#36e1a97233036c057ab2bdb31ec09ad8d9988411"