Optimistic Updates (#114)
* Optimistic Updates structure * Optimistic update for Editorial Workflow
This commit is contained in:
@ -12,7 +12,7 @@ class FindBar extends Component {
|
||||
commands: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
pattern: PropTypes.string.isRequired
|
||||
pattern: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
defaultCommands: PropTypes.arrayOf(PropTypes.string),
|
||||
runCommand: PropTypes.func.isRequired,
|
||||
@ -23,9 +23,9 @@ class FindBar extends Component {
|
||||
this._compiledCommands = [];
|
||||
this._searchCommand = {
|
||||
search: true,
|
||||
regexp: `(?:${SEARCH})?(.*)`,
|
||||
regexp: `(?:${ SEARCH })?(.*)`,
|
||||
param: { name: 'searchTerm', display: '' },
|
||||
token: SEARCH
|
||||
token: SEARCH,
|
||||
};
|
||||
this.state = {
|
||||
value: '',
|
||||
@ -56,7 +56,7 @@ class FindBar extends Component {
|
||||
}
|
||||
|
||||
// Generates a regexp and splits a token and param details for a command
|
||||
compileCommand = command => {
|
||||
compileCommand = (command) => {
|
||||
let regexp = '';
|
||||
let param = null;
|
||||
|
||||
@ -75,7 +75,7 @@ class FindBar extends Component {
|
||||
return Object.assign({}, command, {
|
||||
regexp,
|
||||
token,
|
||||
param
|
||||
param,
|
||||
});
|
||||
};
|
||||
|
||||
@ -84,15 +84,15 @@ class FindBar extends Component {
|
||||
matchCommand = () => {
|
||||
const string = this.state.activeScope ? this.state.activeScope + this.state.value : this.state.value;
|
||||
let match;
|
||||
let command = this._compiledCommands.find(command => {
|
||||
match = string.match(RegExp(`^${command.regexp}`, 'i'));
|
||||
let command = this._compiledCommands.find((command) => {
|
||||
match = string.match(RegExp(`^${ command.regexp }`, 'i'));
|
||||
return match;
|
||||
});
|
||||
|
||||
// If no command was found, trigger a search command
|
||||
if (!command) {
|
||||
command = this._searchCommand;
|
||||
match = string.match(RegExp(`^${this._searchCommand.regexp}`, 'i'));
|
||||
match = string.match(RegExp(`^${ this._searchCommand.regexp }`, 'i'));
|
||||
}
|
||||
|
||||
const paramName = command && command.param ? command.param.name : null;
|
||||
@ -101,7 +101,7 @@ class FindBar extends Component {
|
||||
if (command.search) {
|
||||
this.setState({
|
||||
activeScope: SEARCH,
|
||||
placeholder: ''
|
||||
placeholder: '',
|
||||
});
|
||||
|
||||
enteredParamValue && this.props.runCommand(SEARCH, { searchTerm: enteredParamValue });
|
||||
@ -112,7 +112,7 @@ class FindBar extends Component {
|
||||
this.setState({
|
||||
value: '',
|
||||
activeScope: command.token,
|
||||
placeholder: command.param.display
|
||||
placeholder: command.param.display,
|
||||
});
|
||||
} else {
|
||||
// Match
|
||||
@ -121,7 +121,7 @@ class FindBar extends Component {
|
||||
this.setState({
|
||||
value: '',
|
||||
placeholder: PLACEHOLDER,
|
||||
activeScope: null
|
||||
activeScope: null,
|
||||
}, () => {
|
||||
this._input.blur();
|
||||
});
|
||||
@ -137,7 +137,7 @@ class FindBar extends Component {
|
||||
if (this.state.value.length === 0 && this.state.activeScope) {
|
||||
this.setState({
|
||||
activeScope: null,
|
||||
placeholder: PLACEHOLDER
|
||||
placeholder: PLACEHOLDER,
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -160,7 +160,7 @@ class FindBar extends Component {
|
||||
const results = fuzzy.filter(value, commands, {
|
||||
pre: '<strong>',
|
||||
post: '</strong>',
|
||||
extract: el => el.token
|
||||
extract: el => el.token,
|
||||
});
|
||||
|
||||
const returnResults = results.slice(0, 4).map(result => (
|
||||
@ -171,8 +171,9 @@ class FindBar extends Component {
|
||||
return returnResults;
|
||||
}
|
||||
|
||||
handleKeyDown = event => {
|
||||
let highlightedIndex, index;
|
||||
handleKeyDown = (event) => {
|
||||
let highlightedIndex,
|
||||
index;
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
@ -202,7 +203,7 @@ class FindBar extends Component {
|
||||
const command = this.getSuggestions()[this.state.highlightedIndex];
|
||||
const newState = {
|
||||
isOpen: false,
|
||||
highlightedIndex: 0
|
||||
highlightedIndex: 0,
|
||||
};
|
||||
if (command && !command.search) {
|
||||
newState.value = command.token;
|
||||
@ -223,24 +224,24 @@ class FindBar extends Component {
|
||||
highlightedIndex: 0,
|
||||
isOpen: false,
|
||||
activeScope: null,
|
||||
placeholder: PLACEHOLDER
|
||||
placeholder: PLACEHOLDER,
|
||||
});
|
||||
break;
|
||||
case 'Backspace':
|
||||
this.setState({
|
||||
highlightedIndex: 0,
|
||||
isOpen: true
|
||||
isOpen: true,
|
||||
}, this.maybeRemoveActiveScope);
|
||||
break;
|
||||
default:
|
||||
this.setState({
|
||||
highlightedIndex: 0,
|
||||
isOpen: true
|
||||
isOpen: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = event => {
|
||||
handleChange = (event) => {
|
||||
this.setState({
|
||||
value: event.target.value,
|
||||
});
|
||||
@ -250,7 +251,7 @@ class FindBar extends Component {
|
||||
if (this._ignoreBlur) return;
|
||||
this.setState({
|
||||
isOpen: false,
|
||||
highlightedIndex: 0
|
||||
highlightedIndex: 0,
|
||||
});
|
||||
};
|
||||
|
||||
@ -261,17 +262,17 @@ class FindBar extends Component {
|
||||
|
||||
handleInputClick = () => {
|
||||
if (this.state.isOpen === false)
|
||||
this.setState({ isOpen: true });
|
||||
{ this.setState({ isOpen: true }); }
|
||||
};
|
||||
|
||||
highlightCommandFromMouse = index => {
|
||||
highlightCommandFromMouse = (index) => {
|
||||
this.setState({ highlightedIndex: index });
|
||||
};
|
||||
|
||||
selectCommandFromMouse = command => {
|
||||
selectCommandFromMouse = (command) => {
|
||||
const newState = {
|
||||
isOpen: false,
|
||||
highlightedIndex: 0
|
||||
highlightedIndex: 0,
|
||||
};
|
||||
if (command && !command.search) {
|
||||
newState.value = command.token;
|
||||
@ -283,7 +284,7 @@ class FindBar extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
setIgnoreBlur = ignore => {
|
||||
setIgnoreBlur = (ignore) => {
|
||||
this._ignoreBlur = ignore;
|
||||
};
|
||||
|
||||
@ -292,14 +293,14 @@ class FindBar extends Component {
|
||||
let children;
|
||||
if (!command.search) {
|
||||
children = (
|
||||
<span><span dangerouslySetInnerHTML={{ __html: command.string }}/></span>
|
||||
<span><span dangerouslySetInnerHTML={{ __html: command.string }} /></span>
|
||||
);
|
||||
} else {
|
||||
children = (
|
||||
<span>
|
||||
{this.state.value.length === 0 ?
|
||||
<span><Icon type="search"/>Search... </span> :
|
||||
<span className={styles.faded}><Icon type="search"/>Search for: </span>
|
||||
{this.state.value.length === 0 ?
|
||||
<span><Icon type="search" />Search... </span> :
|
||||
<span className={styles.faded}><Icon type="search" />Search for: </span>
|
||||
}
|
||||
<strong>{this.state.value}</strong>
|
||||
</span>
|
||||
@ -331,7 +332,7 @@ class FindBar extends Component {
|
||||
|
||||
renderActiveScope() {
|
||||
if (this.state.activeScope === SEARCH) {
|
||||
return <div className={styles.inputScope}><Icon type="search"/></div>;
|
||||
return <div className={styles.inputScope}><Icon type="search" /></div>;
|
||||
} else {
|
||||
return <div className={styles.inputScope}>{this.state.activeScope}</div>;
|
||||
}
|
||||
@ -346,7 +347,7 @@ class FindBar extends Component {
|
||||
{scope}
|
||||
<input
|
||||
className={styles.inputField}
|
||||
ref={(c) => this._input = c}
|
||||
ref={c => this._input = c}
|
||||
onFocus={this.handleInputFocus}
|
||||
onBlur={this.handleInputBlur}
|
||||
onChange={this.handleChange}
|
||||
|
@ -10,7 +10,7 @@ const defaultSchema = {
|
||||
[BLOCKS.PARAGRAPH]: 'p',
|
||||
[BLOCKS.FOOTNOTE]: 'footnote',
|
||||
[BLOCKS.HTML]: ({ token }) => {
|
||||
return <div dangerouslySetInnerHTML={{ __html: token.get('raw') }}/>;
|
||||
return <div dangerouslySetInnerHTML={{ __html: token.get('raw') }} />;
|
||||
},
|
||||
[BLOCKS.HR]: 'hr',
|
||||
[BLOCKS.HEADING_1]: 'h1',
|
||||
@ -35,7 +35,7 @@ const defaultSchema = {
|
||||
[ENTITIES.LINK]: 'a',
|
||||
[ENTITIES.IMAGE]: 'img',
|
||||
[ENTITIES.FOOTNOTE_REF]: 'sup',
|
||||
[ENTITIES.HARD_BREAK]: 'br'
|
||||
[ENTITIES.HARD_BREAK]: 'br',
|
||||
};
|
||||
|
||||
const notAllowedAttributes = ['loose'];
|
||||
@ -50,7 +50,7 @@ function renderToken(schema, token, index = 0, key = '0') {
|
||||
const text = token.get('text');
|
||||
const tokens = token.get('tokens');
|
||||
const nodeType = schema[type];
|
||||
key = `${key}.${index}`;
|
||||
key = `${ key }.${ index }`;
|
||||
|
||||
// Only render if type is registered as renderer
|
||||
if (typeof nodeType !== 'undefined') {
|
||||
@ -101,6 +101,6 @@ MarkupItReactRenderer.propTypes = {
|
||||
syntax: PropTypes.instanceOf(Syntax).isRequired,
|
||||
schema: PropTypes.objectOf(PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.func
|
||||
]))
|
||||
PropTypes.func,
|
||||
])),
|
||||
};
|
||||
|
@ -13,9 +13,9 @@ export default class PreviewPane extends React.Component {
|
||||
this.renderPreview();
|
||||
}
|
||||
|
||||
widgetFor = name => {
|
||||
widgetFor = (name) => {
|
||||
const { collection, entry, getMedia } = this.props;
|
||||
const field = collection.get('fields').find((field) => field.get('name') === name);
|
||||
const field = collection.get('fields').find(field => field.get('name') === name);
|
||||
const widget = resolveWidget(field.get('widget'));
|
||||
return React.createElement(widget.preview, {
|
||||
key: field.get('name'),
|
||||
@ -29,7 +29,7 @@ export default class PreviewPane extends React.Component {
|
||||
const component = registry.getPreviewTemplate(this.props.collection.get('name')) || Preview;
|
||||
const previewProps = {
|
||||
...this.props,
|
||||
widgetFor: this.widgetFor
|
||||
widgetFor: this.widgetFor,
|
||||
};
|
||||
// We need to use this API in order to pass context to the iframe
|
||||
ReactDOM.unstable_renderSubtreeIntoContainer(
|
||||
@ -40,7 +40,7 @@ export default class PreviewPane extends React.Component {
|
||||
, this.previewEl);
|
||||
}
|
||||
|
||||
handleIframeRef = ref => {
|
||||
handleIframeRef = (ref) => {
|
||||
if (ref) {
|
||||
registry.getPreviewStyles().forEach((style) => {
|
||||
const linkEl = document.createElement('link');
|
||||
@ -61,7 +61,7 @@ export default class PreviewPane extends React.Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <iframe className={styles.frame} ref={this.handleIframeRef}></iframe>;
|
||||
return <iframe className={styles.frame} ref={this.handleIframeRef} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,44 +21,44 @@ export default class ScrollSync extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
registerPane = node => {
|
||||
registerPane = (node) => {
|
||||
if (!this.findPane(node)) {
|
||||
this.addEvents(node);
|
||||
this.panes.push(node);
|
||||
}
|
||||
};
|
||||
|
||||
unregisterPane = node => {
|
||||
unregisterPane = (node) => {
|
||||
if (this.findPane(node)) {
|
||||
this.removeEvents(node);
|
||||
this.panes = without(this.panes, node);
|
||||
}
|
||||
};
|
||||
|
||||
addEvents = node => {
|
||||
addEvents = (node) => {
|
||||
node.onscroll = this.handlePaneScroll.bind(this, node);
|
||||
// node.addEventListener('scroll', this.handlePaneScroll, false)
|
||||
};
|
||||
|
||||
removeEvents = node => {
|
||||
removeEvents = (node) => {
|
||||
node.onscroll = null;
|
||||
// node.removeEventListener('scroll', this.handlePaneScroll, false)
|
||||
};
|
||||
|
||||
findPane = node => {
|
||||
findPane = (node) => {
|
||||
return this.panes.find(p => p === node);
|
||||
};
|
||||
|
||||
handlePaneScroll = node => {
|
||||
handlePaneScroll = (node) => {
|
||||
// const node = evt.target
|
||||
window.requestAnimationFrame(() => {
|
||||
this.syncScrollPositions(node);
|
||||
});
|
||||
};
|
||||
|
||||
syncScrollPositions = scrolledPane => {
|
||||
syncScrollPositions = (scrolledPane) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = scrolledPane;
|
||||
this.panes.forEach(pane => {
|
||||
this.panes.forEach((pane) => {
|
||||
/* For all panes beside the currently scrolling one */
|
||||
if (scrolledPane !== pane) {
|
||||
/* Remove event listeners from the node that we'll manipulate */
|
||||
|
@ -5,7 +5,7 @@ export default class ScrollSyncPane extends Component {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
attachTo: PropTypes.any
|
||||
attachTo: PropTypes.any,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -19,7 +19,6 @@ export default class Loader extends React.Component {
|
||||
const { children } = this.props;
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
|
||||
const nextItem = (this.state.currentItem === children.length - 1) ? 0 : this.state.currentItem + 1;
|
||||
this.setState({ currentItem: nextItem });
|
||||
}, 5000);
|
||||
@ -34,7 +33,7 @@ export default class Loader extends React.Component {
|
||||
return <div className={styles.text}>{children}</div>;
|
||||
} else if (Array.isArray(children)) {
|
||||
this.setAnimation();
|
||||
return <div className={styles.text}>
|
||||
return (<div className={styles.text}>
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={styles}
|
||||
transitionEnterTimeout={500}
|
||||
@ -42,7 +41,7 @@ export default class Loader extends React.Component {
|
||||
>
|
||||
<div key={currentItem} className={styles.animateItem}>{children[currentItem]}</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>;
|
||||
</div>);
|
||||
}
|
||||
};
|
||||
|
||||
@ -52,13 +51,12 @@ export default class Loader extends React.Component {
|
||||
// Class names
|
||||
let classNames = styles.loader;
|
||||
if (active) {
|
||||
classNames += ` ${styles.active}`;
|
||||
classNames += ` ${ styles.active }`;
|
||||
}
|
||||
if (className.length > 0) {
|
||||
classNames += ` ${className}`;
|
||||
classNames += ` ${ className }`;
|
||||
}
|
||||
|
||||
return <div className={classNames} style={style}>{this.renderChild()}</div>;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,6 @@ class UnpublishedListing extends React.Component {
|
||||
};
|
||||
|
||||
requestPublish = (collection, slug, ownStatus) => {
|
||||
console.log('HERE');
|
||||
console.log(ownStatus);
|
||||
console.log(status.last());
|
||||
if (ownStatus !== status.last()) return;
|
||||
if (window.confirm('Are you sure you want to publish this entry?')) {
|
||||
this.props.handlePublish(collection, slug, ownStatus);
|
||||
|
@ -5,25 +5,25 @@ import MediaProxy from '../../valueObjects/MediaProxy';
|
||||
const MAX_DISPLAY_LENGTH = 50;
|
||||
|
||||
export default class ImageControl extends React.Component {
|
||||
handleFileInputRef = el => {
|
||||
handleFileInputRef = (el) => {
|
||||
this._fileInput = el;
|
||||
};
|
||||
|
||||
handleClick = e => {
|
||||
handleClick = (e) => {
|
||||
this._fileInput.click();
|
||||
};
|
||||
|
||||
handleDragEnter = e => {
|
||||
handleDragEnter = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
handleDragOver = e => {
|
||||
handleDragOver = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
handleChange = e => {
|
||||
handleChange = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@ -46,7 +46,6 @@ export default class ImageControl extends React.Component {
|
||||
} else {
|
||||
this.props.onChange(null);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
renderImageName = () => {
|
||||
@ -56,7 +55,6 @@ export default class ImageControl extends React.Component {
|
||||
} else {
|
||||
return truncateMiddle(this.props.value, MAX_DISPLAY_LENGTH);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -84,7 +82,7 @@ export default class ImageControl extends React.Component {
|
||||
|
||||
const styles = {
|
||||
input: {
|
||||
display: 'none'
|
||||
display: 'none',
|
||||
},
|
||||
imageUpload: {
|
||||
backgroundColor: '#fff',
|
||||
@ -94,8 +92,8 @@ const styles = {
|
||||
display: 'block',
|
||||
border: '1px dashed #eee',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px'
|
||||
}
|
||||
fontSize: '12px',
|
||||
},
|
||||
};
|
||||
|
||||
ImageControl.propTypes = {
|
||||
|
@ -33,7 +33,7 @@ class MarkdownControl extends React.Component {
|
||||
const { editor, onChange, onAddMedia, getMedia, value } = this.props;
|
||||
if (editor.get('useVisualMode')) {
|
||||
return (
|
||||
<div className='cms-editor-visual'>
|
||||
<div className="cms-editor-visual">
|
||||
{null && <button onClick={this.useRawEditor}>Switch to Raw Editor</button>}
|
||||
<VisualEditor
|
||||
onChange={onChange}
|
||||
@ -46,7 +46,7 @@ class MarkdownControl extends React.Component {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className='cms-editor-raw'>
|
||||
<div className="cms-editor-raw">
|
||||
{null && <button onClick={this.useVisualEditor}>Switch to Visual Editor</button>}
|
||||
<RawEditor
|
||||
onChange={onChange}
|
||||
|
@ -8,10 +8,10 @@ import styles from './index.css';
|
||||
|
||||
Prism.languages.markdown = Prism.languages.extend('markup', {});
|
||||
Prism.languages.insertBefore('markdown', 'prolog', marks);
|
||||
Prism.languages.markdown['bold'].inside['url'] = Prism.util.clone(Prism.languages.markdown['url']);
|
||||
Prism.languages.markdown['italic'].inside['url'] = Prism.util.clone(Prism.languages.markdown['url']);
|
||||
Prism.languages.markdown['bold'].inside['italic'] = Prism.util.clone(Prism.languages.markdown['italic']);
|
||||
Prism.languages.markdown['italic'].inside['bold'] = Prism.util.clone(Prism.languages.markdown['bold']);
|
||||
Prism.languages.markdown.bold.inside.url = Prism.util.clone(Prism.languages.markdown.url);
|
||||
Prism.languages.markdown.italic.inside.url = Prism.util.clone(Prism.languages.markdown.url);
|
||||
Prism.languages.markdown.bold.inside.italic = Prism.util.clone(Prism.languages.markdown.italic);
|
||||
Prism.languages.markdown.italic.inside.bold = Prism.util.clone(Prism.languages.markdown.bold);
|
||||
|
||||
function renderDecorations(text, block) {
|
||||
let characters = text.characters.asMutable();
|
||||
@ -28,7 +28,7 @@ function renderDecorations(text, block) {
|
||||
|
||||
const length = offset + token.matchedStr.length;
|
||||
const name = token.alias || token.type;
|
||||
const type = `highlight-${name}`;
|
||||
const type = `highlight-${ name }`;
|
||||
|
||||
for (let i = offset; i < length; i++) {
|
||||
let char = characters.get(i);
|
||||
@ -47,13 +47,13 @@ function renderDecorations(text, block) {
|
||||
const SCHEMA = {
|
||||
rules: [
|
||||
{
|
||||
match: (object) => object.kind == 'block',
|
||||
decorate: renderDecorations
|
||||
}
|
||||
match: object => object.kind == 'block',
|
||||
decorate: renderDecorations,
|
||||
},
|
||||
],
|
||||
marks: {
|
||||
'highlight-comment': {
|
||||
opacity: '0.33'
|
||||
opacity: '0.33',
|
||||
},
|
||||
'highlight-important': {
|
||||
fontWeight: 'bold',
|
||||
@ -68,8 +68,8 @@ const SCHEMA = {
|
||||
},
|
||||
'highlight-punctuation': {
|
||||
color: '#006',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default class RawEditor extends React.Component {
|
||||
@ -86,19 +86,19 @@ export default class RawEditor extends React.Component {
|
||||
const content = props.value ? Plain.deserialize(props.value) : Plain.deserialize('');
|
||||
|
||||
this.state = {
|
||||
state: content
|
||||
state: content,
|
||||
};
|
||||
|
||||
this.plugins = [
|
||||
PluginDropImages({
|
||||
applyTransform: (transform, file) => {
|
||||
const mediaProxy = new MediaProxy(file.name, file);
|
||||
const state = Plain.deserialize(`\n\n\n\n`);
|
||||
const state = Plain.deserialize(`\n\n\n\n`);
|
||||
props.onAddMedia(mediaProxy);
|
||||
return transform
|
||||
.insertFragment(state.get('document'));
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ export default class RawEditor extends React.Component {
|
||||
* It also have an onDocumentChange, that get's dispatched only when the actual
|
||||
* content changes
|
||||
*/
|
||||
handleChange = state => {
|
||||
handleChange = (state) => {
|
||||
this.setState({ state });
|
||||
};
|
||||
|
||||
|
@ -10,11 +10,11 @@ class BlockTypesMenu extends Component {
|
||||
plugins: PropTypes.array.isRequired,
|
||||
onClickBlock: PropTypes.func.isRequired,
|
||||
onClickPlugin: PropTypes.func.isRequired,
|
||||
onClickImage: PropTypes.func.isRequired
|
||||
onClickImage: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
expanded: false
|
||||
expanded: false,
|
||||
};
|
||||
|
||||
componentWillUpdate() {
|
||||
@ -33,7 +33,7 @@ class BlockTypesMenu extends Component {
|
||||
|
||||
handlePluginClick = (e, plugin) => {
|
||||
const data = {};
|
||||
plugin.fields.forEach(field => {
|
||||
plugin.fields.forEach((field) => {
|
||||
data[field.name] = window.prompt(field.label); // eslint-disable-line
|
||||
});
|
||||
this.props.onClickPlugin(plugin.id, data);
|
||||
@ -43,7 +43,7 @@ class BlockTypesMenu extends Component {
|
||||
this._fileInput.click();
|
||||
};
|
||||
|
||||
handleFileUploadChange = e => {
|
||||
handleFileUploadChange = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@ -62,20 +62,19 @@ class BlockTypesMenu extends Component {
|
||||
const mediaProxy = new MediaProxy(file.name, file);
|
||||
this.props.onClickImage(mediaProxy);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
renderBlockTypeButton = (type, icon) => {
|
||||
const onClick = e => this.handleBlockTypeClick(e, type);
|
||||
return (
|
||||
<Icon key={type} type={icon} onClick={onClick} className={styles.icon}/>
|
||||
<Icon key={type} type={icon} onClick={onClick} className={styles.icon} />
|
||||
);
|
||||
};
|
||||
|
||||
renderPluginButton = plugin => {
|
||||
renderPluginButton = (plugin) => {
|
||||
const onClick = e => this.handlePluginClick(e, plugin);
|
||||
return (
|
||||
<Icon key={plugin.id} type={plugin.icon} onClick={onClick} className={styles.icon}/>
|
||||
<Icon key={plugin.id} type={plugin.icon} onClick={onClick} className={styles.icon} />
|
||||
);
|
||||
};
|
||||
|
||||
@ -86,13 +85,13 @@ class BlockTypesMenu extends Component {
|
||||
<div className={styles.menu}>
|
||||
{this.renderBlockTypeButton('hr', 'dot-3')}
|
||||
{plugins.map(plugin => this.renderPluginButton(plugin))}
|
||||
<Icon type="picture" onClick={this.handleFileUploadClick} className={styles.icon}/>
|
||||
<Icon type="picture" onClick={this.handleFileUploadClick} className={styles.icon} />
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={this.handleFileUploadChange}
|
||||
className={styles.input}
|
||||
ref={el => {
|
||||
ref={(el) => {
|
||||
this._fileInput = el;
|
||||
}}
|
||||
/>
|
||||
@ -106,7 +105,7 @@ class BlockTypesMenu extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<Icon type="plus-squared" className={styles.button} onClick={this.toggleMenu}/>
|
||||
<Icon type="plus-squared" className={styles.button} onClick={this.toggleMenu} />
|
||||
{this.renderMenu()}
|
||||
</div>
|
||||
);
|
||||
|
@ -11,23 +11,23 @@ class StylesMenu extends Component {
|
||||
inlines: PropTypes.object.isRequired,
|
||||
onClickBlock: PropTypes.func.isRequired,
|
||||
onClickMark: PropTypes.func.isRequired,
|
||||
onClickInline: PropTypes.func.isRequired
|
||||
onClickInline: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to set toolbar buttons to active state
|
||||
*/
|
||||
hasMark = type => {
|
||||
hasMark = (type) => {
|
||||
const { marks } = this.props;
|
||||
return marks.some(mark => mark.type == type);
|
||||
};
|
||||
|
||||
hasBlock = type => {
|
||||
hasBlock = (type) => {
|
||||
const { blocks } = this.props;
|
||||
return blocks.some(node => node.type == type);
|
||||
};
|
||||
|
||||
hasLinks = type => {
|
||||
hasLinks = (type) => {
|
||||
const { inlines } = this.props;
|
||||
return inlines.some(inline => inline.type == 'link');
|
||||
};
|
||||
@ -42,7 +42,7 @@ class StylesMenu extends Component {
|
||||
const onMouseDown = e => this.handleMarkClick(e, type);
|
||||
return (
|
||||
<span className={styles.button} onMouseDown={onMouseDown} data-active={isActive}>
|
||||
<Icon type={icon}/>
|
||||
<Icon type={icon} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@ -57,7 +57,7 @@ class StylesMenu extends Component {
|
||||
const onMouseDown = e => this.handleInlineClick(e, 'link', isActive);
|
||||
return (
|
||||
<span className={styles.button} onMouseDown={onMouseDown} data-active={isActive}>
|
||||
<Icon type="link"/>
|
||||
<Icon type="link" />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@ -75,14 +75,14 @@ class StylesMenu extends Component {
|
||||
const onMouseDown = e => this.handleBlockClick(e, type);
|
||||
return (
|
||||
<span className={styles.button} onMouseDown={onMouseDown} data-active={isActive}>
|
||||
<Icon type={icon}/>
|
||||
<Icon type={icon} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={`${styles.menu} ${styles.hoverMenu}`}>
|
||||
<div className={`${ styles.menu } ${ styles.hoverMenu }`}>
|
||||
{this.renderMarkButton('BOLD', 'bold')}
|
||||
{this.renderMarkButton('ITALIC', 'italic')}
|
||||
{this.renderMarkButton('CODE', 'code')}
|
||||
|
@ -40,7 +40,7 @@ export default class VisualEditor extends React.Component {
|
||||
rawJson = emptyParagraphBlock;
|
||||
}
|
||||
this.state = {
|
||||
state: Raw.deserialize(rawJson, { terse: true })
|
||||
state: Raw.deserialize(rawJson, { terse: true }),
|
||||
};
|
||||
|
||||
this.plugins = [
|
||||
@ -50,12 +50,12 @@ export default class VisualEditor extends React.Component {
|
||||
props.onAddMedia(mediaProxy);
|
||||
return transform
|
||||
.insertBlock(mediaproxyBlock(mediaProxy));
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
getMedia = src => {
|
||||
getMedia = (src) => {
|
||||
return this.props.getMedia(src);
|
||||
};
|
||||
|
||||
@ -65,7 +65,7 @@ export default class VisualEditor extends React.Component {
|
||||
* It also have an onDocumentChange, that get's dispatched only when the actual
|
||||
* content changes
|
||||
*/
|
||||
handleChange = state => {
|
||||
handleChange = (state) => {
|
||||
if (this.blockEdit) {
|
||||
this.blockEdit = false;
|
||||
} else {
|
||||
@ -82,7 +82,7 @@ export default class VisualEditor extends React.Component {
|
||||
/**
|
||||
* Toggle marks / blocks when button is clicked
|
||||
*/
|
||||
handleMarkStyleClick = type => {
|
||||
handleMarkStyleClick = (type) => {
|
||||
let { state } = this.state;
|
||||
|
||||
state = state
|
||||
@ -100,7 +100,6 @@ export default class VisualEditor extends React.Component {
|
||||
|
||||
// Handle everything but list buttons.
|
||||
if (type != 'unordered_list' && type != 'ordered_list') {
|
||||
|
||||
if (isList) {
|
||||
transform = transform
|
||||
.setBlock(isActive ? DEFAULT_NODE : type)
|
||||
@ -165,7 +164,7 @@ export default class VisualEditor extends React.Component {
|
||||
.transform()
|
||||
.wrapInline({
|
||||
type: 'link',
|
||||
data: { href }
|
||||
data: { href },
|
||||
})
|
||||
.collapseToEnd()
|
||||
.apply();
|
||||
@ -174,14 +173,14 @@ export default class VisualEditor extends React.Component {
|
||||
this.setState({ state });
|
||||
};
|
||||
|
||||
handleBlockTypeClick = type => {
|
||||
handleBlockTypeClick = (type) => {
|
||||
let { state } = this.state;
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.insertBlock({
|
||||
type: type,
|
||||
isVoid: true
|
||||
type,
|
||||
isVoid: true,
|
||||
})
|
||||
.apply();
|
||||
|
||||
@ -194,9 +193,9 @@ export default class VisualEditor extends React.Component {
|
||||
state = state
|
||||
.transform()
|
||||
.insertInline({
|
||||
type: type,
|
||||
data: data,
|
||||
isVoid: true
|
||||
type,
|
||||
data,
|
||||
isVoid: true,
|
||||
})
|
||||
.collapseToEnd()
|
||||
.insertBlock(DEFAULT_NODE)
|
||||
@ -206,7 +205,7 @@ export default class VisualEditor extends React.Component {
|
||||
this.setState({ state });
|
||||
};
|
||||
|
||||
handleImageClick = mediaProxy => {
|
||||
handleImageClick = (mediaProxy) => {
|
||||
let { state } = this.state;
|
||||
this.props.onAddMedia(mediaProxy);
|
||||
|
||||
@ -229,12 +228,12 @@ export default class VisualEditor extends React.Component {
|
||||
.splitBlock()
|
||||
.setBlock(DEFAULT_NODE)
|
||||
.apply({
|
||||
snapshot: false
|
||||
snapshot: false,
|
||||
});
|
||||
this.setState({ state: normalized });
|
||||
};
|
||||
|
||||
handleKeyDown = evt => {
|
||||
handleKeyDown = (evt) => {
|
||||
if (evt.shiftKey && evt.key === 'Enter') {
|
||||
this.blockEdit = true;
|
||||
let { state } = this.state;
|
||||
|
@ -6,13 +6,13 @@ export default function withPortalAtCursorPosition(WrappedComponent) {
|
||||
return class extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
isOpen: React.PropTypes.bool.isRequired
|
||||
}
|
||||
isOpen: React.PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
menu: null,
|
||||
cursorPosition: null
|
||||
}
|
||||
cursorPosition: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.adjustPosition();
|
||||
@ -36,22 +36,22 @@ export default function withPortalAtCursorPosition(WrappedComponent) {
|
||||
);
|
||||
const centerY = cursorPosition.top + window.scrollY;
|
||||
menu.style.opacity = 1;
|
||||
menu.style.top = `${centerY}px`;
|
||||
menu.style.left = `${centerX}px`;
|
||||
}
|
||||
menu.style.top = `${ centerY }px`;
|
||||
menu.style.left = `${ centerX }px`;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the portal opens, cache the menu element.
|
||||
*/
|
||||
handleOpen = (portal) => {
|
||||
this.setState({ menu: portal.firstChild });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isOpen, ...rest } = this.props;
|
||||
return (
|
||||
<Portal isOpened={isOpen} onOpen={this.handleOpen}>
|
||||
<WrappedComponent {...rest}/>
|
||||
<WrappedComponent {...rest} />
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ export const emptyParagraphBlock = {
|
||||
nodes: [{
|
||||
kind: 'text',
|
||||
ranges: [{
|
||||
text: ''
|
||||
}]
|
||||
}]
|
||||
}
|
||||
]
|
||||
text: '',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mediaproxyBlock = mediaproxy => ({
|
||||
@ -22,7 +22,7 @@ export const mediaproxyBlock = mediaproxy => ({
|
||||
isVoid: true,
|
||||
data: {
|
||||
alt: mediaproxy.name,
|
||||
src: mediaproxy.public_path
|
||||
}
|
||||
}]
|
||||
src: mediaproxy.public_path,
|
||||
},
|
||||
}],
|
||||
});
|
||||
|
@ -13,7 +13,7 @@ const MarkdownPreview = ({ value, getMedia }) => {
|
||||
src={getMedia(token.getIn(['data', 'src']))}
|
||||
alt={token.getIn(['data', 'alt'])}
|
||||
/>
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
const { markdown } = getSyntaxes();
|
||||
|
@ -24,13 +24,13 @@ function processEditorPlugins(plugins) {
|
||||
// to determine whether we need to process again.
|
||||
if (plugins === processedPlugins) return;
|
||||
|
||||
plugins.forEach(plugin => {
|
||||
plugins.forEach((plugin) => {
|
||||
const basicRule = MarkupIt.Rule(plugin.id).regExp(plugin.pattern, (state, match) => (
|
||||
{ data: plugin.fromBlock(match) }
|
||||
));
|
||||
|
||||
const markdownRule = basicRule.toText((state, token) => (
|
||||
plugin.toBlock(token.getData().toObject()) + '\n\n'
|
||||
`${ plugin.toBlock(token.getData().toObject()) }\n\n`
|
||||
));
|
||||
|
||||
const htmlRule = basicRule.toText((state, token) => (
|
||||
@ -43,9 +43,9 @@ function processEditorPlugins(plugins) {
|
||||
const className = isFocused ? 'plugin active' : 'plugin';
|
||||
return (
|
||||
<div {...props.attributes} className={className}>
|
||||
<div className="plugin_icon" contentEditable={false}><Icon type={plugin.icon}/></div>
|
||||
<div className="plugin_icon" contentEditable={false}><Icon type={plugin.icon} /></div>
|
||||
<div className="plugin_fields" contentEditable={false}>
|
||||
{plugin.fields.map(field => `${field.label}: “${node.data.get(field.name)}”`)}
|
||||
{plugin.fields.map(field => `${ field.label }: “${ node.data.get(field.name) }”`)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -66,43 +66,43 @@ function processMediaProxyPlugins(getMedia) {
|
||||
return;
|
||||
}
|
||||
|
||||
var imgData = Map({
|
||||
const imgData = Map({
|
||||
alt: match[1],
|
||||
src: match[2],
|
||||
title: match[3]
|
||||
title: match[3],
|
||||
}).filter(Boolean);
|
||||
|
||||
return {
|
||||
data: imgData
|
||||
data: imgData,
|
||||
};
|
||||
});
|
||||
const mediaProxyMarkdownRule = mediaProxyRule.toText((state, token) => {
|
||||
var data = token.getData();
|
||||
var alt = data.get('alt', '');
|
||||
var src = data.get('src', '');
|
||||
var title = data.get('title', '');
|
||||
const data = token.getData();
|
||||
const alt = data.get('alt', '');
|
||||
const src = data.get('src', '');
|
||||
const title = data.get('title', '');
|
||||
|
||||
if (title) {
|
||||
return '';
|
||||
return ``;
|
||||
} else {
|
||||
return '';
|
||||
return ``;
|
||||
}
|
||||
});
|
||||
const mediaProxyHTMLRule = mediaProxyRule.toText((state, token) => {
|
||||
var data = token.getData();
|
||||
var alt = data.get('alt', '');
|
||||
var src = data.get('src', '');
|
||||
return `<img src=${getMedia(src)} alt=${alt} />`;
|
||||
const data = token.getData();
|
||||
const alt = data.get('alt', '');
|
||||
const src = data.get('src', '');
|
||||
return `<img src=${ getMedia(src) } alt=${ alt } />`;
|
||||
});
|
||||
|
||||
nodes['mediaproxy'] = (props) => {
|
||||
nodes.mediaproxy = (props) => {
|
||||
/* eslint react/prop-types: 0 */
|
||||
const { node, state } = props;
|
||||
const isFocused = state.selection.hasEdgeIn(node);
|
||||
const className = isFocused ? 'active' : null;
|
||||
const src = node.data.get('src');
|
||||
return (
|
||||
<img {...props.attributes} src={getMedia(src)} className={className}/>
|
||||
<img {...props.attributes} src={getMedia(src)} className={className} />
|
||||
);
|
||||
};
|
||||
augmentedMarkdownSyntax = augmentedMarkdownSyntax.addInlineRules(mediaProxyMarkdownRule);
|
||||
@ -113,7 +113,7 @@ function getPlugins() {
|
||||
return processedPlugins.map(plugin => ({
|
||||
id: plugin.id,
|
||||
icon: plugin.icon,
|
||||
fields: plugin.fields
|
||||
fields: plugin.fields,
|
||||
})).toArray();
|
||||
}
|
||||
|
||||
|
@ -27,16 +27,16 @@ const commands = [
|
||||
|
||||
const style = {
|
||||
width: 800,
|
||||
margin: 20
|
||||
margin: 20,
|
||||
};
|
||||
|
||||
storiesOf('FindBar', module)
|
||||
.add('Default View', () => (
|
||||
<div style={style}>
|
||||
<FindBar
|
||||
commands={commands}
|
||||
defaultCommands={[CREATE_POST, CREATE_COLLECTION, OPEN_SETTINGS, HELP, MORE_COMMANDS]}
|
||||
runCommand={action}
|
||||
commands={commands}
|
||||
defaultCommands={[CREATE_POST, CREATE_COLLECTION, OPEN_SETTINGS, HELP, MORE_COMMANDS]}
|
||||
runCommand={action}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
@ -22,13 +22,13 @@ const htmlContent = `
|
||||
storiesOf('MarkupItReactRenderer', module)
|
||||
.add('Markdown', () => (
|
||||
<MarkupItReactRenderer
|
||||
value={mdContent}
|
||||
syntax={markdownSyntax}
|
||||
value={mdContent}
|
||||
syntax={markdownSyntax}
|
||||
/>
|
||||
|
||||
)).add('HTML', () => (
|
||||
<MarkupItReactRenderer
|
||||
value={htmlContent}
|
||||
syntax={htmlSyntax}
|
||||
value={htmlContent}
|
||||
syntax={htmlSyntax}
|
||||
/>
|
||||
));
|
||||
|
Reference in New Issue
Block a user