preview
This commit is contained in:
@@ -57,7 +57,7 @@
|
|||||||
"react-redux": "^4.4.0",
|
"react-redux": "^4.4.0",
|
||||||
"react-router": "^2.0.0",
|
"react-router": "^2.0.0",
|
||||||
"redux": "^3.3.1",
|
"redux": "^3.3.1",
|
||||||
"redux-storage": "^3.0.0",
|
"redux-storage": "^4.0.0",
|
||||||
"redux-storage-engine-localstorage": "^1.0.0",
|
"redux-storage-engine-localstorage": "^1.0.0",
|
||||||
"sass-loader": "^3.1.2",
|
"sass-loader": "^3.1.2",
|
||||||
"style-loader": "^0.13.0",
|
"style-loader": "^0.13.0",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import MarkdownIcon from '../../Components/Icons/Markdown';
|
||||||
|
|
||||||
import styles from './Header.scss';
|
import styles from './Header.scss';
|
||||||
import classNames from 'classnames/bind';
|
import classNames from 'classnames/bind';
|
||||||
const cx = classNames.bind(styles);
|
const cx = classNames.bind(styles);
|
||||||
@@ -7,23 +9,26 @@ const cx = classNames.bind(styles);
|
|||||||
const Header = ({
|
const Header = ({
|
||||||
activeEditors,
|
activeEditors,
|
||||||
toggleEditors,
|
toggleEditors,
|
||||||
addRevision,
|
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.header }>
|
<div className={ styles.header }>
|
||||||
<div className={ styles.headerItem }><i>Beautiful</i> Atlas</div>
|
<div className={ styles.headerItem }><i>Beautiful</i> Atlas</div>
|
||||||
<div className={ cx('headerItem', 'editorToggle') }>
|
<div
|
||||||
<span
|
className={ cx('headerItem', 'editorToggle') }
|
||||||
|
style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
onClick={toggleEditors.bind(this, 'MARKDOWN')}
|
onClick={toggleEditors.bind(this, 'MARKDOWN')}
|
||||||
className={ activeEditors.includes('MARKDOWN') ? styles.active : '' }
|
className={ activeEditors.includes('MARKDOWN') ? styles.active : '' }
|
||||||
>Markdown</span>
|
>
|
||||||
<span
|
<MarkdownIcon style={{ width: '32px', height: '20px', color: '#fff' }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
onClick={toggleEditors.bind(this, 'TEXT')}
|
onClick={toggleEditors.bind(this, 'TEXT')}
|
||||||
className={ activeEditors.includes('TEXT') ? styles.active : '' }
|
className={ activeEditors.includes('TEXT') ? styles.active : '' }
|
||||||
>Text</span>
|
>
|
||||||
</div>
|
<span className={ styles.textIcon }>Aa</span>
|
||||||
<div className={ cx('headerItem', 'sidebar') }>
|
</div>
|
||||||
<span onClick={addRevision.bind(this, (new Date()).toISOString())}>Save</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -32,7 +37,6 @@ const Header = ({
|
|||||||
Header.propTypes = {
|
Header.propTypes = {
|
||||||
activeEditors: React.PropTypes.array.isRequired,
|
activeEditors: React.PropTypes.array.isRequired,
|
||||||
toggleEditors: React.PropTypes.func.isRequired,
|
toggleEditors: React.PropTypes.func.isRequired,
|
||||||
addRevision: React.PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Header;
|
export default Header;
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
@@ -29,26 +32,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.editorToggle {
|
.editorToggle div {
|
||||||
span {
|
margin-right: 12px;
|
||||||
margin-right: 12px;
|
cursor: pointer;
|
||||||
cursor: pointer;
|
opacity: 0.4;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
&:last-child {
|
&.active {
|
||||||
margin-right: 0;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-top: -2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.textIcon {
|
||||||
span {
|
font-family: Times, serif;
|
||||||
margin-right: 12px;
|
font-size: 20px;
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.active {
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-color: #fff;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
31
src/Components/Icons/Markdown.js
Normal file
31
src/Components/Icons/Markdown.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default ({ style = {}, className }) => {
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={ style.width || 208 }
|
||||||
|
height={ style.height || 128 }
|
||||||
|
viewBox="0 0 208 128"
|
||||||
|
color={ style.color }
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
width="198"
|
||||||
|
height="118"
|
||||||
|
x="5"
|
||||||
|
y="5"
|
||||||
|
ry="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="10"
|
||||||
|
fill="none"
|
||||||
|
fillOpacity="0"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M30 98v-68h20l20 25 20-25h20v68h-20v-39l-20 25-20-25v39zM155 98l-30-33h20v-35h20v35h20z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Codemirror from 'react-codemirror';
|
import Codemirror from 'react-codemirror';
|
||||||
import 'codemirror/mode/gfm/gfm';
|
import 'codemirror/mode/gfm/gfm';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
import 'codemirror/addon/edit/continuelist';
|
import 'codemirror/addon/edit/continuelist';
|
||||||
|
|
||||||
import styles from './MarkdownEditor.scss';
|
import styles from './MarkdownEditor.scss';
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
.container {
|
.container {
|
||||||
width: 70%;
|
font-weight: 400;
|
||||||
margin: 48px auto;
|
font-size: 1em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2em 3em;
|
||||||
|
max-width: 50em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 2000px) and (min-width: 960px) {
|
||||||
|
.container {font-size: 1.1em}
|
||||||
}
|
}
|
||||||
@@ -11,10 +11,11 @@
|
|||||||
@import url(https://fonts.googleapis.com/css?family=Cousine:400,700,700italic,400italic);
|
@import url(https://fonts.googleapis.com/css?family=Cousine:400,700,700italic,400italic);
|
||||||
|
|
||||||
.cm-s-atlas.CodeMirror {
|
.cm-s-atlas.CodeMirror {
|
||||||
background: #fcfcfc;
|
background: #ffffff;
|
||||||
color: #202020;
|
color: #202020;
|
||||||
font-family: 'Cousine', 'Monaco', monospace;
|
font-family: 'Cousine', 'Monaco', monospace;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
.cm-s-atlas div.CodeMirror-selected {
|
.cm-s-atlas div.CodeMirror-selected {
|
||||||
background: #90CAF9;
|
background: #90CAF9;
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
|
.container {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2em 3em;
|
||||||
|
max-width: 50em;
|
||||||
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
outline: none;
|
outline: none;
|
||||||
margin: 0 0 20px 0;
|
}
|
||||||
padding: 0 0 20px 0;
|
|
||||||
|
@media all and (max-width: 2000px) and (min-width: 960px) {
|
||||||
|
.container {font-size: 1.1em}
|
||||||
}
|
}
|
||||||
@@ -12,11 +12,12 @@ import {
|
|||||||
const activeEditors = (state = [ActiveEditors.MARKDOWN, ActiveEditors.TEXT], action) => {
|
const activeEditors = (state = [ActiveEditors.MARKDOWN, ActiveEditors.TEXT], action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case TOGGLE_EDITORS: {
|
case TOGGLE_EDITORS: {
|
||||||
|
// TODO: A rewrite would be nice
|
||||||
const newState = _.xor(state, [action.toggledEditor]);
|
const newState = _.xor(state, [action.toggledEditor]);
|
||||||
if (newState.length > 0) {
|
if (newState.length > 0) {
|
||||||
return newState;
|
return newState;
|
||||||
} else {
|
} else {
|
||||||
return [action.toggledEditor];
|
return _.xor([ActiveEditors.MARKDOWN, ActiveEditors.TEXT], [action.toggledEditor]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -37,15 +38,28 @@ const historySidebar = (state = { visible: false }, action) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const text = (state = { text: '', revisions: [] }, action) => {
|
const textDefaultState = {
|
||||||
|
text: '',
|
||||||
|
revisions: [],
|
||||||
|
unsavedChanges: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = (state = textDefaultState, action) => {
|
||||||
|
const lastRevision = _.last(state.revisions);
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case UPDATE_TEXT:
|
case UPDATE_TEXT: {
|
||||||
|
let unsavedChanges = false;
|
||||||
|
if (lastRevision && lastRevision.text !== state.text) {
|
||||||
|
unsavedChanges = true;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
unsavedChanges,
|
||||||
text: action.text,
|
text: action.text,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
case ADD_REVISION: {
|
case ADD_REVISION: {
|
||||||
const lastRevision = _.last(state.revisions);
|
|
||||||
// Create new revision if it differs from the previous one
|
// Create new revision if it differs from the previous one
|
||||||
if (!lastRevision || lastRevision.text !== state.text) {
|
if (!lastRevision || lastRevision.text !== state.text) {
|
||||||
const lastId = lastRevision ? lastRevision.id : 0;
|
const lastId = lastRevision ? lastRevision.id : 0;
|
||||||
@@ -59,6 +73,7 @@ const text = (state = { text: '', revisions: [] }, action) => {
|
|||||||
created_at: action.createdAt,
|
created_at: action.createdAt,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
unsavedChanges: false,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class App extends Component {
|
|||||||
activeEditors: React.PropTypes.array.isRequired,
|
activeEditors: React.PropTypes.array.isRequired,
|
||||||
toggleEditors: React.PropTypes.func.isRequired,
|
toggleEditors: React.PropTypes.func.isRequired,
|
||||||
addRevision: React.PropTypes.func.isRequired,
|
addRevision: React.PropTypes.func.isRequired,
|
||||||
|
unsavedChanges: React.PropTypes.bool.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@@ -47,6 +48,7 @@ class App extends Component {
|
|||||||
activeEditors={this.props.activeEditors}
|
activeEditors={this.props.activeEditors}
|
||||||
toggleEditors={this.props.toggleEditors}
|
toggleEditors={this.props.toggleEditors}
|
||||||
addRevision={this.props.addRevision}
|
addRevision={this.props.addRevision}
|
||||||
|
unsavedChanges={this.props.unsavedChanges}
|
||||||
/>
|
/>
|
||||||
<div className={ styles.content }>
|
<div className={ styles.content }>
|
||||||
{ this.props.children }
|
{ this.props.children }
|
||||||
@@ -59,7 +61,7 @@ class App extends Component {
|
|||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
return {
|
return {
|
||||||
activeEditors: state.activeEditors,
|
activeEditors: state.activeEditors,
|
||||||
showHistorySidebar: state.historySidebar.visible,
|
unsavedChanges: state.text.unsavedChanges,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding-top: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
@@ -11,5 +12,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown {
|
.markdown {
|
||||||
background-color: #fbfbfb;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user