update sidebar styling, add new entry links (#276)
* udpate sidebar styling, add new entry links * make sidebar new entry links always visible * simplify app bar implementation, findbar
This commit is contained in:
parent
e7dc2c4e84
commit
705e348138
@ -1,13 +1,32 @@
|
|||||||
import history from '../routing/history';
|
import history from '../routing/history';
|
||||||
import { SEARCH } from '../components/FindBar/FindBar';
|
import { SEARCH } from '../components/FindBar/FindBar';
|
||||||
|
import { getCollectionUrl, getNewEntryUrl } from '../lib/urlHelper';
|
||||||
|
|
||||||
export const RUN_COMMAND = 'RUN_COMMAND';
|
export const RUN_COMMAND = 'RUN_COMMAND';
|
||||||
export const SHOW_COLLECTION = 'SHOW_COLLECTION';
|
export const SHOW_COLLECTION = 'SHOW_COLLECTION';
|
||||||
export const CREATE_COLLECTION = 'CREATE_COLLECTION';
|
export const CREATE_COLLECTION = 'CREATE_COLLECTION';
|
||||||
export const HELP = 'HELP';
|
export const HELP = 'HELP';
|
||||||
|
|
||||||
export function run(commandName, payload) {
|
export function runCommand(command, payload) {
|
||||||
return { type: RUN_COMMAND, command: commandName, payload };
|
return (dispatch) => {
|
||||||
|
switch (command) {
|
||||||
|
case SHOW_COLLECTION:
|
||||||
|
history.push(getCollectionUrl(payload.collectionName));
|
||||||
|
break;
|
||||||
|
case CREATE_COLLECTION:
|
||||||
|
history.push(getNewEntryUrl(payload.collectionName));
|
||||||
|
break;
|
||||||
|
case HELP:
|
||||||
|
window.alert('Find Bar Help (PLACEHOLDER)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit.');
|
||||||
|
break;
|
||||||
|
case SEARCH:
|
||||||
|
history.push(`/search/${ payload.searchTerm }`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dispatch({ type: RUN_COMMAND, command, payload });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function navigateToCollection(collectionName) {
|
export function navigateToCollection(collectionName) {
|
||||||
@ -17,23 +36,3 @@ export function navigateToCollection(collectionName) {
|
|||||||
export function createNewEntryInCollection(collectionName) {
|
export function createNewEntryInCollection(collectionName) {
|
||||||
return runCommand(CREATE_COLLECTION, { collectionName });
|
return runCommand(CREATE_COLLECTION, { collectionName });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function runCommand(commandName, payload) {
|
|
||||||
return dispatch => {
|
|
||||||
switch (commandName) {
|
|
||||||
case SHOW_COLLECTION:
|
|
||||||
history.push(`/collections/${payload.collectionName}`);
|
|
||||||
break;
|
|
||||||
case CREATE_COLLECTION:
|
|
||||||
history.push(`/collections/${payload.collectionName}/entries/new`);
|
|
||||||
break;
|
|
||||||
case HELP:
|
|
||||||
window.alert('Find Bar Help (PLACEHOLDER)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit.');
|
|
||||||
break;
|
|
||||||
case SEARCH:
|
|
||||||
history.push(`/search/${payload.searchTerm}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
dispatch(run(commandName, payload));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
:root {
|
@import '../UI/theme';
|
||||||
--foregroundColor: #fff;
|
|
||||||
--backgroundColor: #272e30;
|
|
||||||
--textFieldBorderColor: #e7e7e7;
|
|
||||||
--highlightFGColor: #fff;
|
|
||||||
--highlightBGColor: #3ab7a5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appBar {
|
.appBar {
|
||||||
background-color: var(--backgroundColor);
|
background-color: var(--backgroundAltColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
/* Gross stuff below, React Toolbox hacks */
|
||||||
|
.homeLink,
|
||||||
|
.iconMenu {
|
||||||
|
margin-left: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homeLink .icon {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon,
|
||||||
|
.icon span,
|
||||||
|
.leftIcon span {
|
||||||
/* stylelint-disable */
|
/* stylelint-disable */
|
||||||
/* Cascade is evil :( */
|
|
||||||
color: var(--foregroundColor) !important;
|
color: var(--foregroundAltColor) !important;
|
||||||
|
font-size: 30px !important;
|
||||||
|
|
||||||
/* stylelint-enable */
|
/* stylelint-enable */
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@ export default class AppHeader extends React.Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
user: ImmutablePropTypes.map.isRequired,
|
user: ImmutablePropTypes.map.isRequired,
|
||||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
collections: ImmutablePropTypes.orderedMap.isRequired,
|
||||||
commands: PropTypes.array.isRequired, // eslint-disable-line
|
|
||||||
defaultCommands: PropTypes.array.isRequired, // eslint-disable-line
|
|
||||||
runCommand: PropTypes.func.isRequired,
|
runCommand: PropTypes.func.isRequired,
|
||||||
toggleDrawer: PropTypes.func.isRequired,
|
toggleDrawer: PropTypes.func.isRequired,
|
||||||
onCreateEntryClick: PropTypes.func.isRequired,
|
onCreateEntryClick: PropTypes.func.isRequired,
|
||||||
@ -57,17 +55,12 @@ export default class AppHeader extends React.Component {
|
|||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
collections,
|
collections,
|
||||||
commands,
|
|
||||||
defaultCommands,
|
|
||||||
runCommand,
|
runCommand,
|
||||||
toggleDrawer,
|
toggleDrawer,
|
||||||
onLogoutClick,
|
onLogoutClick,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const { userMenuActive } = this.state;
|
||||||
createMenuActive,
|
|
||||||
userMenuActive,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const avatarStyle = {
|
const avatarStyle = {
|
||||||
backgroundColor: `#${ stringToRGB(user.get("name")) }`,
|
backgroundColor: `#${ stringToRGB(user.get("name")) }`,
|
||||||
@ -97,18 +90,12 @@ export default class AppHeader extends React.Component {
|
|||||||
onLeftIconClick={toggleDrawer}
|
onLeftIconClick={toggleDrawer}
|
||||||
onRightIconClick={this.handleRightIconClick}
|
onRightIconClick={this.handleRightIconClick}
|
||||||
>
|
>
|
||||||
<IndexLink to="/">
|
<IndexLink to="/" className={styles.homeLink}>
|
||||||
<FontIcon value="home" />
|
<FontIcon value="home" className={styles.icon} />
|
||||||
</IndexLink>
|
</IndexLink>
|
||||||
|
|
||||||
<FindBar
|
|
||||||
commands={commands}
|
|
||||||
defaultCommands={defaultCommands}
|
|
||||||
runCommand={runCommand}
|
|
||||||
/>
|
|
||||||
<IconMenu
|
<IconMenu
|
||||||
theme={styles}
|
theme={styles}
|
||||||
icon="create"
|
icon="add"
|
||||||
onClick={this.handleCreateButtonClick}
|
onClick={this.handleCreateButtonClick}
|
||||||
onHide={this.handleCreateMenuHide}
|
onHide={this.handleCreateMenuHide}
|
||||||
>
|
>
|
||||||
@ -123,6 +110,7 @@ export default class AppHeader extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
</IconMenu>
|
</IconMenu>
|
||||||
|
<FindBar runCommand={runCommand} />
|
||||||
</AppBar>
|
</AppBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
:root {
|
@import '../UI/theme';
|
||||||
--defaultColorLight: #eee;
|
|
||||||
--backgroundColor: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.controlPane {
|
.controlPane {
|
||||||
height: calc(100% - 55px);
|
height: calc(100% - 55px);
|
||||||
@ -18,14 +11,10 @@
|
|||||||
height: calc(100% - 55px);
|
height: calc(100% - 55px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blocker {
|
|
||||||
}
|
|
||||||
|
|
||||||
.blocker > * {
|
.blocker > * {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
height: 55px;
|
height: 55px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
@ -1,98 +1,19 @@
|
|||||||
:root {
|
@import '../UI/theme';
|
||||||
--foregroundColor: #fff;
|
|
||||||
--backgroundColor: #272e30;
|
|
||||||
--textFieldBorderColor: #e7e7e7;
|
|
||||||
--highlightFGColor: #fff;
|
|
||||||
--highlightBGColor: #3ab7a5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
background-color: var(--backgroundAltColor);
|
||||||
background-color: var(--backgroundColor);
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
.inputArea {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
color: var(--foregroundColor);
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid var(--textFieldBorderColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputScope {
|
|
||||||
display: table-cell;
|
|
||||||
width: 1%;
|
|
||||||
padding: 0 6px 0 8px;
|
|
||||||
color: #767676;
|
|
||||||
font-size: 16px;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-right: 1px solid var(--textFieldBorderColor);
|
|
||||||
margin:0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputField {
|
.inputField {
|
||||||
display: table-cell;
|
width: 80%;
|
||||||
width: 99%;
|
max-width: 800px;
|
||||||
padding: 6px;
|
display: block;
|
||||||
font-size: 16px;
|
margin: 0 auto;
|
||||||
background: none transparent;
|
padding: 10px 14px;
|
||||||
border: 0 none;
|
border: 0;
|
||||||
|
border-radius: var(--borderRadius);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: inherit;
|
font-size: 18px;
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 44px;
|
|
||||||
left: 0;
|
|
||||||
z-index: 9999;
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
background: var(--backgroundColor);
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 400;
|
|
||||||
padding: 5px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestions {
|
|
||||||
display: table-cell;
|
|
||||||
width: 100%;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history {
|
|
||||||
display: table-cell;
|
|
||||||
width: 50%;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command {
|
|
||||||
padding: 6px;
|
|
||||||
cursor: default;
|
|
||||||
color: var(--foregroundColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.command strong, .highlightedCommand strong {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command .faded {
|
|
||||||
font-weight: 300;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlightedCommand {
|
|
||||||
color: var(--highlightFGColor);
|
|
||||||
background: var(--highlightBGColor);
|
|
||||||
padding: 6px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlightedCommand .faded {
|
|
||||||
font-weight: 300;
|
|
||||||
color: #282c34;
|
|
||||||
}
|
}
|
||||||
|
@ -1,363 +1,53 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import fuzzy from 'fuzzy';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { Icon } from '../UI';
|
|
||||||
import styles from './FindBar.css';
|
import styles from './FindBar.css';
|
||||||
|
|
||||||
export const SEARCH = 'SEARCH';
|
export const SEARCH = 'SEARCH';
|
||||||
const PLACEHOLDER = 'Search or enter a command';
|
const PLACEHOLDER = 'Search entry titles...';
|
||||||
|
|
||||||
class FindBar extends Component {
|
class FindBar extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
commands: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
type: PropTypes.string.isRequired,
|
|
||||||
pattern: PropTypes.string.isRequired,
|
|
||||||
})).isRequired,
|
|
||||||
defaultCommands: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
runCommand: PropTypes.func.isRequired,
|
runCommand: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._compiledCommands = [];
|
|
||||||
this._searchCommand = {
|
this._searchCommand = {
|
||||||
search: true,
|
|
||||||
regexp: `(?:${ SEARCH })?(.*)`,
|
regexp: `(?:${ SEARCH })?(.*)`,
|
||||||
param: { name: 'searchTerm', display: '' },
|
param: { name: 'searchTerm', display: '' },
|
||||||
token: SEARCH,
|
|
||||||
};
|
};
|
||||||
this.state = {
|
this.state = {
|
||||||
value: '',
|
value: '',
|
||||||
placeholder: PLACEHOLDER,
|
placeholder: PLACEHOLDER,
|
||||||
activeScope: null,
|
|
||||||
isOpen: false,
|
|
||||||
highlightedIndex: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this._getSuggestions = _.memoize(this._getSuggestions, (value, activeScope) => value + activeScope);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
search = () => {
|
||||||
this._ignoreBlur = false;
|
const string = this.state.value;
|
||||||
}
|
const command = this._searchCommand;
|
||||||
|
const match = string.match(RegExp(`^${ this._searchCommand.regexp }`, 'i'));
|
||||||
componentDidMount() {
|
|
||||||
this._compiledCommands = this.props.commands.map(this.compileCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
_escapeRegExp(string) {
|
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
||||||
}
|
|
||||||
|
|
||||||
_camelCaseToSpace(string) {
|
|
||||||
const result = string.replace(/([A-Z])/g, ' $1');
|
|
||||||
return result.charAt(0).toUpperCase() + result.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates a regexp and splits a token and param details for a command
|
|
||||||
compileCommand = (command) => {
|
|
||||||
let regexp = '';
|
|
||||||
let param = null;
|
|
||||||
|
|
||||||
const matcher = /\(:([a-zA-Z_$][a-zA-Z0-9_$]*)(?:(?: as )(.*))?\)/g;
|
|
||||||
const match = matcher.exec(command.pattern);
|
|
||||||
const matchIndex = match ? match.index : command.pattern.length;
|
|
||||||
|
|
||||||
const token = command.pattern.slice(0, matchIndex) || command.token;
|
|
||||||
regexp += this._escapeRegExp(command.pattern.slice(0, matchIndex));
|
|
||||||
|
|
||||||
if (match && match[1]) {
|
|
||||||
regexp += '(.*)';
|
|
||||||
param = { name: match[1], display: match[2] || this._camelCaseToSpace(match[1]) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign({}, command, {
|
|
||||||
regexp,
|
|
||||||
token,
|
|
||||||
param,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if the entered string matches any command.
|
|
||||||
// adds a scope (so user can type param value) and dispatches action for fully matched commands
|
|
||||||
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'));
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
|
|
||||||
// If no command was found, trigger a search command
|
|
||||||
if (!command) {
|
|
||||||
command = this._searchCommand;
|
|
||||||
match = string.match(RegExp(`^${ this._searchCommand.regexp }`, 'i'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const paramName = command && command.param ? command.param.name : null;
|
|
||||||
const enteredParamValue = command && command.param && match[1] ? match[1].trim() : null;
|
const enteredParamValue = command && command.param && match[1] ? match[1].trim() : null;
|
||||||
|
|
||||||
if (command.search) {
|
if (enteredParamValue) {
|
||||||
this.setState({
|
this.props.runCommand(SEARCH, { searchTerm: enteredParamValue });
|
||||||
value: '',
|
|
||||||
placeholder: PLACEHOLDER,
|
|
||||||
activeScope: null,
|
|
||||||
}, () => {
|
|
||||||
this._input.blur();
|
|
||||||
});
|
|
||||||
|
|
||||||
enteredParamValue && this.props.runCommand(SEARCH, { searchTerm: enteredParamValue });
|
|
||||||
} else if (command.param && !enteredParamValue) {
|
|
||||||
// Partial Match
|
|
||||||
// Command was partially matched: It requires a param, but param wasn't entered
|
|
||||||
// Set a scope so user can fill the param
|
|
||||||
this.setState({
|
|
||||||
value: '',
|
|
||||||
activeScope: command.token,
|
|
||||||
placeholder: command.param.display,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Match
|
|
||||||
// Command was matched and either it doesn't require a param or it's required param was entered
|
|
||||||
// Dispatch action
|
|
||||||
this.setState({
|
|
||||||
value: '',
|
|
||||||
placeholder: PLACEHOLDER,
|
|
||||||
activeScope: null,
|
|
||||||
}, () => {
|
|
||||||
this._input.blur();
|
|
||||||
});
|
|
||||||
const payload = command.payload || {};
|
|
||||||
if (paramName) {
|
|
||||||
payload[paramName] = enteredParamValue;
|
|
||||||
}
|
|
||||||
this.props.runCommand(command.type, payload);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
maybeRemoveActiveScope = () => {
|
handleKeyDown = event => (event.key === 'Enter' && this.search());
|
||||||
if (this.state.value.length === 0 && this.state.activeScope) {
|
handleChange = event => this.setState({ value: event.target.value });
|
||||||
this.setState({
|
|
||||||
activeScope: null,
|
|
||||||
placeholder: PLACEHOLDER,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getSuggestions = () => {
|
|
||||||
return this._getSuggestions(this.state.value, this.state.activeScope, this._compiledCommands, this.props.defaultCommands);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Memoized version
|
|
||||||
_getSuggestions(value, scope, commands, defaultCommands) {
|
|
||||||
if (scope) return []; // No autocomplete for scoped input
|
|
||||||
if (value.length === 0 && defaultCommands) {
|
|
||||||
return commands
|
|
||||||
.filter(command => defaultCommands.indexOf(command.id) !== -1)
|
|
||||||
.map(result => (
|
|
||||||
Object.assign({}, result, { string: result.token }
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = fuzzy.filter(value, commands, {
|
|
||||||
pre: '<strong>',
|
|
||||||
post: '</strong>',
|
|
||||||
extract: el => el.token,
|
|
||||||
});
|
|
||||||
|
|
||||||
const returnResults = results.slice(0, 4).map(result => (
|
|
||||||
Object.assign({}, result.original, { string: result.string }
|
|
||||||
)));
|
|
||||||
returnResults.push(this._searchCommand);
|
|
||||||
|
|
||||||
return returnResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown = (event) => {
|
|
||||||
let highlightedIndex,
|
|
||||||
index;
|
|
||||||
switch (event.key) {
|
|
||||||
case 'ArrowDown':
|
|
||||||
event.preventDefault();
|
|
||||||
highlightedIndex = this.state.highlightedIndex;
|
|
||||||
index = (
|
|
||||||
highlightedIndex === this.getSuggestions().length - 1 ||
|
|
||||||
this.state.isOpen === false
|
|
||||||
) ? 0 : highlightedIndex + 1;
|
|
||||||
this.setState({
|
|
||||||
highlightedIndex: index,
|
|
||||||
isOpen: true,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'ArrowUp':
|
|
||||||
event.preventDefault();
|
|
||||||
highlightedIndex = this.state.highlightedIndex;
|
|
||||||
index = (
|
|
||||||
highlightedIndex === 0
|
|
||||||
) ? this.getSuggestions().length - 1 : highlightedIndex - 1;
|
|
||||||
this.setState({
|
|
||||||
highlightedIndex: index,
|
|
||||||
isOpen: true,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'Enter':
|
|
||||||
if (this.state.isOpen) {
|
|
||||||
const command = this.getSuggestions()[this.state.highlightedIndex];
|
|
||||||
const newState = {
|
|
||||||
isOpen: false,
|
|
||||||
highlightedIndex: 0,
|
|
||||||
};
|
|
||||||
if (command && !command.search) {
|
|
||||||
newState.value = command.token;
|
|
||||||
}
|
|
||||||
this.setState(newState, () => {
|
|
||||||
this._input.focus();
|
|
||||||
this._input.setSelectionRange(
|
|
||||||
this.state.value.length,
|
|
||||||
this.state.value.length
|
|
||||||
);
|
|
||||||
this.matchCommand();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'Escape':
|
|
||||||
this.setState({
|
|
||||||
value: '',
|
|
||||||
highlightedIndex: 0,
|
|
||||||
isOpen: false,
|
|
||||||
activeScope: null,
|
|
||||||
placeholder: PLACEHOLDER,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'Backspace':
|
|
||||||
this.setState({
|
|
||||||
highlightedIndex: 0,
|
|
||||||
isOpen: true,
|
|
||||||
}, this.maybeRemoveActiveScope);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.setState({
|
|
||||||
highlightedIndex: 0,
|
|
||||||
isOpen: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange = (event) => {
|
|
||||||
this.setState({
|
|
||||||
value: event.target.value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleInputBlur = () => {
|
|
||||||
if (this._ignoreBlur) return;
|
|
||||||
this.setState({
|
|
||||||
isOpen: false,
|
|
||||||
highlightedIndex: 0,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleInputFocus = () => {
|
|
||||||
if (this._ignoreBlur) return;
|
|
||||||
this.setState({ isOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleInputClick = () => {
|
|
||||||
if (this.state.isOpen === false)
|
|
||||||
{ this.setState({ isOpen: true }); }
|
|
||||||
};
|
|
||||||
|
|
||||||
highlightCommandFromMouse = (index) => {
|
|
||||||
this.setState({ highlightedIndex: index });
|
|
||||||
};
|
|
||||||
|
|
||||||
selectCommandFromMouse = (command) => {
|
|
||||||
const newState = {
|
|
||||||
isOpen: false,
|
|
||||||
highlightedIndex: 0,
|
|
||||||
};
|
|
||||||
if (command && !command.search) {
|
|
||||||
newState.value = command.token;
|
|
||||||
}
|
|
||||||
this.setState(newState, () => {
|
|
||||||
this.matchCommand();
|
|
||||||
this._input.focus();
|
|
||||||
this.setIgnoreBlur(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
setIgnoreBlur = (ignore) => {
|
|
||||||
this._ignoreBlur = ignore;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderMenu() {
|
|
||||||
const commands = this.getSuggestions().map((command, index) => {
|
|
||||||
let children;
|
|
||||||
if (!command.search) {
|
|
||||||
children = (
|
|
||||||
<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>
|
|
||||||
}
|
|
||||||
<strong>{this.state.value}</strong>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
|
|
||||||
key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')}
|
|
||||||
onMouseDown={() => this.setIgnoreBlur(true)}
|
|
||||||
onMouseEnter={() => this.highlightCommandFromMouse(index)}
|
|
||||||
onClick={() => this.selectCommandFromMouse(command)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return commands.length === 0 ? null : (
|
|
||||||
<div className={styles.menu}>
|
|
||||||
<div className={styles.suggestions}>
|
|
||||||
{commands}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderActiveScope() {
|
|
||||||
if (this.state.activeScope === SEARCH) {
|
|
||||||
return <div className={styles.inputScope}><Icon type="search" /></div>;
|
|
||||||
} else {
|
|
||||||
return <div className={styles.inputScope}>{this.state.activeScope}</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const menu = this.state.isOpen && this.renderMenu();
|
|
||||||
const scope = this.state.activeScope && this.renderActiveScope();
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<label className={styles.inputArea}>
|
<label htmlFor="searchInput" />
|
||||||
{scope}
|
|
||||||
<input
|
<input
|
||||||
|
id="searchInput"
|
||||||
className={styles.inputField}
|
className={styles.inputField}
|
||||||
ref={c => this._input = c}
|
ref={c => this._input = c} // eslint-disable-line no-return-assign
|
||||||
onFocus={this.handleInputFocus}
|
|
||||||
onBlur={this.handleInputBlur}
|
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
onClick={this.handleInputClick}
|
|
||||||
placeholder={this.state.placeholder}
|
placeholder={this.state.placeholder}
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
/>
|
/>
|
||||||
</label>
|
|
||||||
{menu}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,11 @@
|
|||||||
--errorColor: #f52;
|
--errorColor: #f52;
|
||||||
--borderRadius: 2px;
|
--borderRadius: 2px;
|
||||||
--topmostZindex: 99999;
|
--topmostZindex: 99999;
|
||||||
|
--foregroundAltColor: #fff;
|
||||||
|
--backgroundAltColor: #272e30;
|
||||||
|
--textFieldBorderColor: #e7e7e7;
|
||||||
|
--highlightFGColor: #fff;
|
||||||
|
--highlightBGColor: #3ab7a5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.base {
|
.base {
|
||||||
|
@ -3,28 +3,6 @@ import { storiesOf, action } from '@kadira/storybook';
|
|||||||
|
|
||||||
import FindBar from '../FindBar/FindBar';
|
import FindBar from '../FindBar/FindBar';
|
||||||
|
|
||||||
const CREATE_COLLECTION = 'CREATE_COLLECTION';
|
|
||||||
const CREATE_POST = 'CREATE_POST';
|
|
||||||
const CREATE_ARTICLE = 'CREATE_ARTICLE';
|
|
||||||
const CREATE_FAQ = 'CREATE_FAQ';
|
|
||||||
const ADD_NEWS = 'ADD_NEWS';
|
|
||||||
const ADD_USER = 'ADD_USER';
|
|
||||||
const OPEN_SETTINGS = 'OPEN_SETTINGS';
|
|
||||||
const HELP = 'HELP';
|
|
||||||
const MORE_COMMANDS = 'MORE_COMMANDS';
|
|
||||||
|
|
||||||
const commands = [
|
|
||||||
{ id: CREATE_COLLECTION, pattern: 'Create new Collection(:collectionName)' },
|
|
||||||
{ id: CREATE_POST, pattern: 'Create new Post(:postName)' },
|
|
||||||
{ id: CREATE_ARTICLE, pattern: 'Create new Article(:articleName)' },
|
|
||||||
{ id: CREATE_FAQ, pattern: 'Create new FAQ item(:faqName as FAQ item name)' },
|
|
||||||
{ id: ADD_NEWS, pattern: 'Add news item(:headline)' },
|
|
||||||
{ id: ADD_USER, pattern: 'Add new User(:userName as User name)' },
|
|
||||||
{ id: OPEN_SETTINGS, pattern: 'Go to Settings' },
|
|
||||||
{ id: HELP, pattern: 'Help' },
|
|
||||||
{ id: MORE_COMMANDS, pattern: 'More Commands...' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
width: 800,
|
width: 800,
|
||||||
margin: 20,
|
margin: 20,
|
||||||
@ -33,10 +11,6 @@ const style = {
|
|||||||
storiesOf('FindBar', module)
|
storiesOf('FindBar', module)
|
||||||
.add('Default View', () => (
|
.add('Default View', () => (
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
<FindBar
|
<FindBar runCommand={action} />
|
||||||
commands={commands}
|
|
||||||
defaultCommands={[CREATE_POST, CREATE_COLLECTION, OPEN_SETTINGS, HELP, MORE_COMMANDS]}
|
|
||||||
runCommand={action}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
.root {
|
.entriesPanel {
|
||||||
margin-top: 64px;
|
padding: 0 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
width: 200px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
@ -2,27 +2,28 @@ import React, { PropTypes } from 'react';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { IndexLink } from "react-router";
|
||||||
|
import FontIcon from 'react-toolbox/lib/font_icon';
|
||||||
import { Layout, Panel } from 'react-toolbox/lib/layout';
|
import { Layout, Panel } from 'react-toolbox/lib/layout';
|
||||||
import { Navigation } from 'react-toolbox/lib/navigation';
|
import { Navigation } from 'react-toolbox/lib/navigation';
|
||||||
import { Link } from 'react-toolbox/lib/link';
|
|
||||||
import { Notifs } from 'redux-notifications';
|
import { Notifs } from 'redux-notifications';
|
||||||
import TopBarProgress from 'react-topbar-progress-indicator';
|
import TopBarProgress from 'react-topbar-progress-indicator';
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
import { loadConfig } from '../actions/config';
|
import { loadConfig as actionLoadConfig } from '../actions/config';
|
||||||
import { loginUser, logoutUser } from '../actions/auth';
|
import { loginUser as actionLoginUser, logoutUser as actionLogoutUser } from '../actions/auth';
|
||||||
import { toggleSidebar } from '../actions/globalUI';
|
import { toggleSidebar as actionToggleSidebar } from '../actions/globalUI';
|
||||||
import { currentBackend } from '../backends/backend';
|
import { currentBackend } from '../backends/backend';
|
||||||
import {
|
import {
|
||||||
SHOW_COLLECTION,
|
runCommand as actionRunCommand,
|
||||||
CREATE_COLLECTION,
|
navigateToCollection as actionNavigateToCollection,
|
||||||
HELP,
|
createNewEntryInCollection as actionCreateNewEntryInCollection,
|
||||||
runCommand,
|
|
||||||
navigateToCollection,
|
|
||||||
createNewEntryInCollection,
|
|
||||||
} from '../actions/findbar';
|
} from '../actions/findbar';
|
||||||
import AppHeader from '../components/AppHeader/AppHeader';
|
import AppHeader from '../components/AppHeader/AppHeader';
|
||||||
import { Loader, Toast } from '../components/UI/index';
|
import { Loader, Toast } from '../components/UI/index';
|
||||||
|
import { getCollectionUrl, getNewEntryUrl } from '../lib/urlHelper';
|
||||||
|
import { SIMPLE, EDITORIAL_WORKFLOW } from '../constants/publishModes';
|
||||||
import styles from './App.css';
|
import styles from './App.css';
|
||||||
|
import sidebarStyles from './Sidebar.css';
|
||||||
|
|
||||||
TopBarProgress.config({
|
TopBarProgress.config({
|
||||||
barColors: {
|
barColors: {
|
||||||
@ -49,6 +50,7 @@ class App extends React.Component {
|
|||||||
user: ImmutablePropTypes.map,
|
user: ImmutablePropTypes.map,
|
||||||
runCommand: PropTypes.func.isRequired,
|
runCommand: PropTypes.func.isRequired,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]),
|
||||||
};
|
};
|
||||||
|
|
||||||
static configError(config) {
|
static configError(config) {
|
||||||
@ -63,11 +65,11 @@ class App extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.dispatch(loadConfig());
|
this.props.dispatch(actionLoadConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLogin(credentials) {
|
handleLogin(credentials) {
|
||||||
this.props.dispatch(loginUser(credentials));
|
this.props.dispatch(actionLoginUser(credentials));
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticating() {
|
authenticating() {
|
||||||
@ -91,35 +93,9 @@ class App extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateFindBarCommands() {
|
handleLinkClick(event, handler, ...args) {
|
||||||
// Generate command list
|
event.preventDefault();
|
||||||
const commands = [];
|
handler(...args);
|
||||||
const defaultCommands = [];
|
|
||||||
|
|
||||||
this.props.collections.forEach((collection) => {
|
|
||||||
commands.push({
|
|
||||||
id: `show_${ collection.get('name') }`,
|
|
||||||
pattern: `Show ${ pluralize(collection.get('label')) }`,
|
|
||||||
type: SHOW_COLLECTION,
|
|
||||||
payload: { collectionName: collection.get('name') },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (defaultCommands.length < 5) defaultCommands.push(`show_${ collection.get('name') }`);
|
|
||||||
|
|
||||||
// if (collection.get('create') === true) {
|
|
||||||
// commands.push({
|
|
||||||
// id: `create_${ collection.get('name') }`,
|
|
||||||
// pattern: `Create new ${ pluralize(collection.get('label'), 1) }(:itemName as ${ pluralize(collection.get('label'), 1) } Name)`,
|
|
||||||
// type: CREATE_COLLECTION,
|
|
||||||
// payload: { collectionName: collection.get('name') },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
|
|
||||||
// commands.push({ id: HELP, type: HELP, pattern: 'Help' });
|
|
||||||
// defaultCommands.push(HELP);
|
|
||||||
|
|
||||||
return { commands, defaultCommands };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -134,6 +110,7 @@ class App extends React.Component {
|
|||||||
createNewEntryInCollection,
|
createNewEntryInCollection,
|
||||||
logoutUser,
|
logoutUser,
|
||||||
isFetching,
|
isFetching,
|
||||||
|
publishMode,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
|
||||||
@ -153,45 +130,67 @@ class App extends React.Component {
|
|||||||
return this.authenticating();
|
return this.authenticating();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { commands, defaultCommands } = this.generateFindBarCommands();
|
|
||||||
const sidebarContent = (
|
const sidebarContent = (
|
||||||
<nav className={styles.nav}>
|
<div>
|
||||||
<h1 className={styles.heading}>Collections</h1>
|
<Navigation type="vertical" className={sidebarStyles.nav}>
|
||||||
<Navigation type="vertical">
|
|
||||||
{
|
{
|
||||||
collections.valueSeq().map(collection =>
|
publishMode === SIMPLE ? null :
|
||||||
<Link
|
<section>
|
||||||
key={collection.get('name')}
|
<h1 className={sidebarStyles.heading}>Publishing</h1>
|
||||||
onClick={navigateToCollection.bind(this, collection.get('name'))} // eslint-disable-line
|
<div className={sidebarStyles.linkWrapper}>
|
||||||
>
|
<IndexLink to="/" className={sidebarStyles.viewEntriesLink}>Editorial Workflow</IndexLink>
|
||||||
{collection.get('label')}
|
</div>
|
||||||
</Link>
|
</section>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
<section>
|
||||||
|
<h1 className={sidebarStyles.heading}>Collections</h1>
|
||||||
|
{
|
||||||
|
collections.valueSeq().map((collection) => {
|
||||||
|
const collectionName = collection.get('name');
|
||||||
|
return (
|
||||||
|
<div key={collectionName} className={sidebarStyles.linkWrapper}>
|
||||||
|
<a
|
||||||
|
href={getCollectionUrl(collectionName, true)}
|
||||||
|
className={sidebarStyles.viewEntriesLink}
|
||||||
|
onClick={e => this.handleLinkClick(e, navigateToCollection, collectionName)}
|
||||||
|
>
|
||||||
|
{pluralize(collection.get('label'))}
|
||||||
|
</a>
|
||||||
|
{
|
||||||
|
collection.get('create') ? (
|
||||||
|
<a
|
||||||
|
href={getNewEntryUrl(collectionName, true)}
|
||||||
|
className={sidebarStyles.createEntryLink}
|
||||||
|
onClick={e => this.handleLinkClick(e, createNewEntryInCollection, collectionName)}
|
||||||
|
>
|
||||||
|
<FontIcon value="add_circle_outline" />
|
||||||
|
</a>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</section>
|
||||||
</Navigation>
|
</Navigation>
|
||||||
</nav>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar content={sidebarContent}>
|
<Sidebar content={sidebarContent}>
|
||||||
<Layout theme={styles}>
|
<Layout>
|
||||||
<Notifs
|
<Notifs CustomComponent={Toast} />
|
||||||
className={styles.notifsContainer}
|
|
||||||
CustomComponent={Toast}
|
|
||||||
/>
|
|
||||||
<AppHeader
|
<AppHeader
|
||||||
user={user}
|
user={user}
|
||||||
collections={collections}
|
collections={collections}
|
||||||
commands={commands}
|
|
||||||
defaultCommands={defaultCommands}
|
|
||||||
runCommand={runCommand}
|
runCommand={runCommand}
|
||||||
onCreateEntryClick={createNewEntryInCollection}
|
onCreateEntryClick={createNewEntryInCollection}
|
||||||
onLogoutClick={logoutUser}
|
onLogoutClick={logoutUser}
|
||||||
toggleDrawer={toggleSidebar}
|
toggleDrawer={toggleSidebar}
|
||||||
/>
|
/>
|
||||||
<Panel scrollY>
|
<Panel scrollY className={styles.entriesPanel}>
|
||||||
{ isFetching && <TopBarProgress /> }
|
{ isFetching && <TopBarProgress /> }
|
||||||
<div className={styles.main}>
|
<div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
@ -206,24 +205,25 @@ function mapStateToProps(state) {
|
|||||||
const { auth, config, collections, globalUI } = state;
|
const { auth, config, collections, globalUI } = state;
|
||||||
const user = auth && auth.get('user');
|
const user = auth && auth.get('user');
|
||||||
const isFetching = globalUI.get('isFetching');
|
const isFetching = globalUI.get('isFetching');
|
||||||
return { auth, config, collections, user, isFetching };
|
const publishMode = config && config.get('publish_mode');
|
||||||
|
return { auth, config, collections, user, isFetching, publishMode };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
dispatch,
|
dispatch,
|
||||||
toggleSidebar: () => dispatch(toggleSidebar()),
|
toggleSidebar: () => dispatch(actionToggleSidebar()),
|
||||||
runCommand: (type, payload) => {
|
runCommand: (type, payload) => {
|
||||||
dispatch(runCommand(type, payload));
|
dispatch(actionRunCommand(type, payload));
|
||||||
},
|
},
|
||||||
navigateToCollection: (collection) => {
|
navigateToCollection: (collection) => {
|
||||||
dispatch(navigateToCollection(collection));
|
dispatch(actionNavigateToCollection(collection));
|
||||||
},
|
},
|
||||||
createNewEntryInCollection: (collectionName) => {
|
createNewEntryInCollection: (collectionName) => {
|
||||||
dispatch(createNewEntryInCollection(collectionName));
|
dispatch(actionCreateNewEntryInCollection(collectionName));
|
||||||
},
|
},
|
||||||
logoutUser: () => {
|
logoutUser: () => {
|
||||||
dispatch(logoutUser());
|
dispatch(actionLogoutUser());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import { loadEntries } from '../actions/entries';
|
|||||||
import { selectEntries } from '../reducers';
|
import { selectEntries } from '../reducers';
|
||||||
import { Loader } from '../components/UI';
|
import { Loader } from '../components/UI';
|
||||||
import EntryListing from '../components/EntryListing/EntryListing';
|
import EntryListing from '../components/EntryListing/EntryListing';
|
||||||
import styles from './breakpoints.css';
|
|
||||||
|
|
||||||
class CollectionPage extends React.Component {
|
class CollectionPage extends React.Component {
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ class CollectionPage extends React.Component {
|
|||||||
if (collections == null) {
|
if (collections == null) {
|
||||||
return <h1>No collections defined in your config.yml</h1>;
|
return <h1>No collections defined in your config.yml</h1>;
|
||||||
}
|
}
|
||||||
return (<div className={styles.root}>
|
return (<div>
|
||||||
{entries ?
|
{entries ?
|
||||||
<EntryListing
|
<EntryListing
|
||||||
collections={collection}
|
collections={collection}
|
||||||
|
@ -3,7 +3,6 @@ import { connect } from 'react-redux';
|
|||||||
import { SIMPLE, EDITORIAL_WORKFLOW } from '../constants/publishModes';
|
import { SIMPLE, EDITORIAL_WORKFLOW } from '../constants/publishModes';
|
||||||
import history from '../routing/history';
|
import history from '../routing/history';
|
||||||
import UnpublishedEntriesPanel from './editorialWorkflow/UnpublishedEntriesPanel';
|
import UnpublishedEntriesPanel from './editorialWorkflow/UnpublishedEntriesPanel';
|
||||||
import styles from './breakpoints.css';
|
|
||||||
|
|
||||||
|
|
||||||
class DashboardPage extends Component {
|
class DashboardPage extends Component {
|
||||||
@ -15,7 +14,7 @@ class DashboardPage extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div>
|
||||||
<h1>Dashboard</h1>
|
<h1>Dashboard</h1>
|
||||||
<UnpublishedEntriesPanel />
|
<UnpublishedEntriesPanel />
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,10 +2,9 @@ import React, { PropTypes } from 'react';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectSearchedEntries } from '../reducers';
|
import { selectSearchedEntries } from '../reducers';
|
||||||
import { searchEntries, clearSearch } from '../actions/search';
|
import { searchEntries as actionSearchEntries, clearSearch as actionClearSearch } from '../actions/search';
|
||||||
import { Loader } from '../components/UI';
|
import { Loader } from '../components/UI';
|
||||||
import EntryListing from '../components/EntryListing/EntryListing';
|
import EntryListing from '../components/EntryListing/EntryListing';
|
||||||
import styles from './breakpoints.css';
|
|
||||||
|
|
||||||
class SearchPage extends React.Component {
|
class SearchPage extends React.Component {
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ class SearchPage extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { collections, searchTerm, entries, isFetching, page, publicFolder } = this.props;
|
const { collections, searchTerm, entries, isFetching, page, publicFolder } = this.props;
|
||||||
return (<div className={styles.root}>
|
return (<div>
|
||||||
{(isFetching === true || !entries) ?
|
{(isFetching === true || !entries) ?
|
||||||
<Loader active>{['Loading Entries', 'Caching Entries', 'This might take several minutes']}</Loader>
|
<Loader active>{['Loading Entries', 'Caching Entries', 'This might take several minutes']}</Loader>
|
||||||
:
|
:
|
||||||
@ -76,7 +75,7 @@ function mapStateToProps(state, ownProps) {
|
|||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
{
|
{
|
||||||
searchEntries,
|
searchEntries: actionSearchEntries,
|
||||||
clearSearch,
|
clearSearch: actionClearSearch,
|
||||||
}
|
}
|
||||||
)(SearchPage);
|
)(SearchPage);
|
||||||
|
@ -1,8 +1,47 @@
|
|||||||
|
@import '../components/UI/theme';
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
margin-top: 64px;
|
margin-top: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 200px;
|
width: 220px;
|
||||||
background-color: #fff
|
background-color: #fff;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkWrapper {
|
||||||
|
color: var(--defaultColor);
|
||||||
|
border-radius: var(--borderRadius);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--defaultColorLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewEntriesLink {
|
||||||
|
font-size: 18px;
|
||||||
|
color: inherit;
|
||||||
|
padding: 10px 6px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.createEntryLink {
|
||||||
|
color: inherit;
|
||||||
|
padding: 0 6px;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
.root {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 749px) and (min-width: 495px) {
|
|
||||||
.root {
|
|
||||||
margin: auto;
|
|
||||||
width: 495px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1004px) and (min-width: 750px) {
|
|
||||||
.root {
|
|
||||||
margin: auto;
|
|
||||||
width: 750px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1514px) and (min-width: 1005px) {
|
|
||||||
.root {
|
|
||||||
margin: auto;
|
|
||||||
width: 1005px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1769px) and (min-width: 1515px) {
|
|
||||||
.root {
|
|
||||||
margin: auto;
|
|
||||||
width: 1515px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1770px) {
|
|
||||||
.root {
|
|
||||||
margin: auto;
|
|
||||||
width: 1770px;
|
|
||||||
}
|
|
||||||
}
|
|
11
src/lib/urlHelper.js
Normal file
11
src/lib/urlHelper.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
function getUrl(url, direct) {
|
||||||
|
return `${ direct ? '/#' : '' }${ url }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCollectionUrl(collectionName, direct) {
|
||||||
|
return getUrl(`/collections/${ collectionName }`, direct);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNewEntryUrl(collectionName, direct) {
|
||||||
|
return getUrl(`/collections/${ collectionName }/entries/new`, direct);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user