Fix conflict

This commit is contained in:
cyril lopez 2017-09-14 15:10:08 +02:00
commit 7c6a3f4687
387 changed files with 2374 additions and 2015 deletions

View File

@ -1,16 +1,7 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[{package.json,*.yml}]
insert_final_newline = false
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

103
packages/strapi-admin/.gitattributes vendored Normal file
View File

@ -0,0 +1,103 @@
# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes
# Handle line endings automatically for files detected as text
# and leave all files detected as binary untouched.
* text=auto
#
# The above will handle all files NOT found below
#
#
## These files are text and should be normalized (Convert crlf => lf)
#
# source code
*.php text
*.css text
*.sass text
*.scss text
*.less text
*.styl text
*.js text eol=lf
*.coffee text
*.json text
*.htm text
*.html text
*.xml text
*.svg text
*.txt text
*.ini text
*.inc text
*.pl text
*.rb text
*.py text
*.scm text
*.sql text
*.sh text
*.bat text
# templates
*.ejs text
*.hbt text
*.jade text
*.haml text
*.hbs text
*.dot text
*.tmpl text
*.phtml text
# git config
.gitattributes text
.gitignore text
.gitconfig text
# code analysis config
.jshintrc text
.jscsrc text
.jshintignore text
.csslintrc text
# misc config
*.yaml text
*.yml text
.editorconfig text
# build config
*.npmignore text
*.bowerrc text
# Heroku
Procfile text
.slugignore text
# Documentation
*.md text
LICENSE text
AUTHORS text
#
## These files are binary and should be left untouched
#
# (binary is a macro for -text -diff)
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.zip binary
*.7z binary
*.ttf binary
*.eot binary
*.woff binary
*.pyc binary
*.pdf binary

105
packages/strapi-admin/.gitignore vendored Executable file → Normal file
View File

@ -1,102 +1,11 @@
############################
# OS X
############################
.DS_Store
.AppleDouble
.LSOverride
Icon
.Spotlight-V100
.Trashes
._*
############################
# Linux
############################
*~
############################
# Windows
############################
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msm
*.msp
############################
# Packages
############################
*.7z
*.csv
*.dat
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
*.com
*.class
*.dll
*.exe
*.o
*.seed
*.so
*.swo
*.swp
*.swn
*.swm
*.out
*.pid
############################
# Logs and databases
############################
*.log
*.sql
############################
# Misc.
############################
*#
ssl
.idea
nbproject
############################
# Node.js
############################
lib-cov
lcov.info
pids
logs
results
# Don't check auto-generated stuff into git
coverage
build
node_modules
.node_history
stats.json
package-lock.json
############################
# Tests
############################
testApp
coverage
# Cruft
.DS_Store
npm-debug.log
.idea

View File

@ -14,16 +14,16 @@ Go in your project: `cd myApp`.
Remove the generated admin panel: `rm -rf admin`.
Create a symlink in order to be able to easily develop the admin panel from your generated
Strapi application: `ln -s /usr/local/lib/node_modules/strapi-generate-admin/files/admin admin`
Create a symlink in order to be able to easily develop the admin panel from your generated
Strapi application: `ln -s /usr/local/lib/node_modules/strapi-generate-admin admin`
(supposing `/usr/local/lib/node_modules` is your global node modules folder).
### Development
Start the React application: `cd myApp/admin/public`, then `npm start`.
Start the React application: `cd myApp/admin`, then `npm start`.
The admin panel should now be available at [http://localhost:4000](http://localhost:4000).
### Build
In order to check your updates, you can build the admin panel: `cd myApp/admin/public`, then `npm run build`.
In order to check your updates, you can build the admin panel: `cd myApp`, then `npm run build`.

View File

@ -10,58 +10,36 @@ import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { applyRouterMiddleware, Router, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import useScroll from 'react-router-scroll';
import { ConnectedRouter } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import _ from 'lodash';
import LanguageProvider from 'containers/LanguageProvider';
import NotificationProvider from 'containers/NotificationProvider';
import configureStore from './store';
import 'sanitize.css/sanitize.css';
import 'whatwg-fetch';
// Import i18n messages
import LanguageProvider from 'containers/LanguageProvider';
import App from 'containers/App';
import { showNotification } from 'containers/NotificationProvider/actions';
import { pluginLoaded, updatePlugin } from 'containers/App/actions';
import { plugins } from '../../config/admin.json';
import configureStore from './store';
import { translationMessages, languages } from './i18n';
// Import the CSS reset, which HtmlWebpackPlugin transfers to the build folder
import 'sanitize.css/sanitize.css';
// Create redux store with history
// this uses the singleton browserHistory provided by react-router
// Optionally, this could be changed to leverage a created history
// e.g. `const browserHistory = useRouterHistory(createBrowserHistory)();`
const initialState = {};
const store = configureStore(initialState, browserHistory);
// Sync history and store, as the react-router-redux reducer
// is under the non-default key ("routing"), selectLocationState
// must be provided for resolving how to retrieve the "route" in the state
import { selectLocationState } from 'containers/App/selectors';
const history = syncHistoryWithStore(browserHistory, store, {
selectLocationState: selectLocationState(),
const history = createHistory({
basename: '/admin',
});
// Set up the router, wrapping all Routes in the App component
import App from 'containers/App';
import createRoutes from './routes';
const rootRoute = {
component: App,
childRoutes: createRoutes(store),
};
const store = configureStore(initialState, history);
const render = (translatedMessages) => {
ReactDOM.render(
<Provider store={store}>
<LanguageProvider messages={translatedMessages}>
<NotificationProvider>
<Router
history={history}
routes={rootRoute}
render={
// Scroll to top when going to a new page, imitating default browser
// behaviour
applyRouterMiddleware(useScroll())
}
/>
</NotificationProvider>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</LanguageProvider>
</Provider>,
document.getElementById('app')
@ -91,14 +69,6 @@ window.onload = function onLoad() {
}
};
// Install ServiceWorker and AppCache in the end since
// it's not most important operation and if main code fails,
// we do not want it installed
// import { install } from 'offline-plugin/runtime';
// install();
import { pluginLoaded, updatePlugin } from './containers/App/actions';
/**
* Public Strapi object exposed to the `window` object
*/
@ -111,22 +81,6 @@ import { pluginLoaded, updatePlugin } from './containers/App/actions';
const registerPlugin = (plugin) => {
const formattedPlugin = plugin;
// Add routes
// Initial list of routes
const homeRoute = rootRoute.childRoutes[0];
const pluginsRoute = _.find(homeRoute.childRoutes, { name: 'plugins' });
// Create a new prefixed route for each plugin routes
if (formattedPlugin && formattedPlugin.routes) {
formattedPlugin.routes.forEach(route => {
pluginsRoute.childRoutes.push({
path: `/plugins/${formattedPlugin.id}${route.path}`,
name: `plugins_${formattedPlugin.id}_${route.name}`,
getComponent: route.getComponent,
});
});
}
// Merge admin translation messages
_.merge(translationMessages, formattedPlugin.translationMessages);
@ -135,8 +89,6 @@ const registerPlugin = (plugin) => {
store.dispatch(pluginLoaded(formattedPlugin));
};
import { showNotification } from './containers/NotificationProvider/actions';
const displayNotification = (message, status) => {
store.dispatch(showNotification(message, status));
};
@ -170,11 +122,28 @@ window.Strapi = {
store.dispatch(updatePlugin(pluginId, 'leftMenuSections', leftMenuSectionsUpdated));
},
}),
router: browserHistory,
router: history,
languages,
};
// Ping each plugins port defined in configuration
if (window.location.hostname === 'localhost') {
plugins.ports.forEach(pluginPort => {
// Define plugin url
const pluginUrl = `http://localhost:${pluginPort}/main.js`;
// Check that the server in running
fetch(pluginUrl)
.then(() => {
// Inject `script` tag in DOM
const script = window.document.createElement('script');
script.src = pluginUrl;
window.document.body.appendChild(script);
});
});
}
const dispatch = store.dispatch;
export {
dispatch,
};
};

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -6,8 +6,10 @@
import React from 'react';
import { defineMessages, FormattedMessage } from 'react-intl';
import styles from './styles.scss';
import LocaleToggle from 'containers/LocaleToggle';
import styles from './styles.scss';
import messages from './messages.json';
defineMessages(messages);

View File

@ -5,7 +5,7 @@
*/
import React from 'react';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import styles from './styles.scss';

View File

@ -17,8 +17,7 @@
font-size: 2rem;
letter-spacing: 0.2rem;
color: $white;
// TMP
background-image: url('../../assets/images/logo-strapi.png');
background-repeat: no-repeat;
background-position: center center;

View File

@ -7,7 +7,7 @@
import _ from 'lodash';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import styles from './styles.scss';
@ -17,10 +17,6 @@ class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer
// because of the two levels router.
const isLinkActive = _.startsWith(window.location.pathname.replace('/admin', ''), this.props.destination);
// const label = this.props.label.id
// ? <FormattedMessage id={this.props.label.id} className={styles.linkLabel} />
// : <span className={styles.linkLabel}>{this.props.label}</span>;
return (
<li className={styles.item}>
<Link className={`${styles.link} ${isLinkActive ? styles.linkActive : ''}`} to={this.props.destination}>
@ -33,11 +29,9 @@ class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer
}
LeftMenuLink.propTypes = {
icon: React.PropTypes.string,
label: React.PropTypes.string,
destination: React.PropTypes.string,
isActive: React.PropTypes.bool,
leftMenuSections: React.PropTypes.object,
destination: React.PropTypes.string.isRequired,
icon: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired,
};
export default LeftMenuLink;

View File

@ -8,7 +8,7 @@
}
.link {
padding-top: 0.8rem;
padding-top: .9rem;
padding-bottom: 0.2rem;
padding-left: 1.6rem;
min-height: 3.6rem;
@ -24,10 +24,19 @@
border-left: 0.3rem solid $strapi-blue;
text-decoration: none;
}
&:focus {
color: $white;
text-decoration: none;
}
&:visited {
color: $left-menu-link-color;
}
}
.linkActive {
color: $white;
color: $white !important;
border-left: 0.3rem solid $strapi-blue;
}

View File

@ -6,6 +6,8 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { map } from 'lodash';
import LeftMenuLink from 'components/LeftMenuLink';
import styles from './styles.scss';
@ -16,20 +18,11 @@ class LeftMenuLinkContainer extends React.Component { // eslint-disable-line rea
// Generate the list of sections
const linkSections = this.props.plugins.valueSeq().map(plugin => (
plugin.get('leftMenuSections').map((leftMenuSection, j) => {
const sectionlinks = leftMenuSection.get('links').map((sectionLink, k) => (
<LeftMenuLink
key={k}
icon={sectionLink.get('icon') || 'link'}
label={sectionLink.get('label')}
destination={`/plugins/${plugin.get('id')}/${sectionLink.get('destination')}`}
/>
));
return (
<div key={j}>
<p className={styles.title}>{leftMenuSection.get('name')}</p>
<ul className={styles.list}>
{sectionlinks}
{map(this.links, (link, k) => <LeftMenuLink key={k} icon={link.get('icon') || 'link'} label={link.get('label')} destination={`/plugins/${plugin.get('id')}/${link.get('destination')}`} /> )}
</ul>
</div>
);
@ -46,7 +39,11 @@ class LeftMenuLinkContainer extends React.Component { // eslint-disable-line rea
destination={`/plugins/${plugin.get('id')}`}
/>
))
: <span className={styles.noPluginsInstalled}>No plugins installed yet.</span>;
: (
<li className={styles.noPluginsInstalled}>
<FormattedMessage {...messages.noPluginsInstalled} />.
</li>
);
return (
<div className={styles.leftMenuLinkContainer}>
@ -83,8 +80,7 @@ class LeftMenuLinkContainer extends React.Component { // eslint-disable-line rea
}
LeftMenuLinkContainer.propTypes = {
plugins: React.PropTypes.object,
params: React.PropTypes.object,
plugins: React.PropTypes.object.isRequired,
};
export default LeftMenuLinkContainer;

View File

@ -18,5 +18,9 @@
"general": {
"id": "app.components.LeftMenuLinkContainer.general",
"defaultMessage": "General"
},
"noPluginsInstalled": {
"id": "app.components.LeftMenuLinkContainer.noPluginsInstalled",
"defaultMessage": "No plugins installed yet"
}
}

View File

@ -28,4 +28,6 @@
padding-left: 1.6rem;
padding-right: 1.6rem;
font-weight: 300;
min-height: 3.6rem;
padding-top: .9rem;
}

View File

@ -16,22 +16,22 @@ class Notification extends React.Component { // eslint-disable-line react/prefer
options = {
success: {
icon: 'ion-ios-checkmark-outline',
icon: 'fa-check',
title: 'Success',
class: 'notificationSuccess',
},
warning: {
icon: 'ion-ios-information-outline',
icon: 'fa-exclamation',
title: 'Warning',
class: 'notificationWarning',
},
error: {
icon: 'ion-ios-close-outline',
icon: 'fa-exclamation',
title: 'Error',
class: 'notificationError',
},
info: {
icon: 'ion-ios-information-outline',
icon: 'fa-info',
title: 'Info',
class: 'notificationInfo',
},
@ -42,20 +42,19 @@ class Notification extends React.Component { // eslint-disable-line react/prefer
return (
<li key={this.props.notification.id} className={`${styles.notification} ${styles[options.class]}`}>
<icon className={`ion ${options.icon} ${styles.notificationIcon}`}></icon>
<p className={styles.notificationContent}>
<span className={styles.notificationTitle}>{options.title}: </span>
<span><FormattedMessage id={this.props.notification.message} /></span>
</p>
<icon className={`ion ion-ios-close-empty pull-right ${styles.notificationClose}`} onClick={this.onCloseClicked}></icon>
<icon className={`fa ${options.icon} ${styles.notificationIcon}`}></icon>
<div className={styles.notificationContent}>
<p className={styles.notificationTitle}><FormattedMessage id={this.props.notification.message} /></p>
</div>
<icon className={`fa fa-close ${styles.notificationClose}`} onClick={this.onCloseClicked}></icon>
</li>
);
}
}
Notification.propTypes = {
notification: React.PropTypes.object,
onHideNotification: React.PropTypes.func,
notification: React.PropTypes.object.isRequired,
onHideNotification: React.PropTypes.func.isRequired,
};
export default Notification;

View File

@ -0,0 +1,103 @@
/* Import */
@import '../../styles/variables/variables';
.notification {
position: relative;
display: flex;
width: 28.6rem;
align-items: stretch;
background: $white;
margin-bottom: 1.4rem;
transition: all 0.2s ease;
border-radius: 0.1rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.15);
color: #333740;
}
.notification:hover {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.5);
}
.notificationIcon {
position: relative;
display: block;
width: 18%;
text-align: center;
font-size: 2.4rem;
&:before {
position: absolute;
top: calc(50% - 1rem);
left: calc(50% - 1rem);
border-radius: 100%;
border: 1px solid $brand-success;
color: $brand-success;
height: 2rem;
width: 2rem;
font-size: 1.2rem;
padding-top: .3rem;
padding-left: 0.1rem;
text-align: center;
}
}
.notificationContent {
display: block;
margin: 0;
padding-top: 1.5rem;
padding-bottom: 1.5rem;
padding-right: 1rem;
border-right: 1px solid rgba(255, 255, 255, 0.3);
width: 70%;
}
.notificationTitle {
font-weight: 500;
font-size: 1.4rem;
margin-bottom: 0;
}
.notificationClose {
position: relative;
display: block;
cursor: pointer;
width: 12%;
opacity: 0.6;
transition: opacity 0.1s ease;
font-size: 1.6rem;
color: #c2c4c7;
&:hover {
opacity: 1;
}
&:before {
position: absolute;
top: calc(50% - .9rem);
left: calc(50% - 0.5rem);
}
}
.notificationWarning {
.notificationIcon:before {
border-color: $brand-warning;
color: $brand-warning;
padding-top: .4rem;
}
}
.notificationError {
.notificationIcon:before {
border-color: $brand-danger;
color: $brand-danger;
padding-top: .4rem;
}
}
.notificationInfo {
.notificationIcon:before {
border-color: $brand-primary;
color: $brand-primary;
}
}

View File

@ -38,8 +38,8 @@ class NotificationsContainer extends React.Component { // eslint-disable-line re
}
NotificationsContainer.propTypes = {
notifications: React.PropTypes.object,
onHideNotification: React.PropTypes.func,
notifications: React.PropTypes.object.isRequired,
onHideNotification: React.PropTypes.func.isRequired,
};
export default NotificationsContainer;

View File

@ -3,7 +3,7 @@
.notificationsContainer { /* stylelint-disable */
position: absolute;
top: 1rem;
top: 7rem;
right: 1rem;
z-index: 1000;
list-style: none;

View File

@ -6,11 +6,11 @@
import React from 'react';
// import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
import ToggleOption from '../ToggleOption';
import ToggleOption from 'components/ToggleOption';
function Toggle(props) { // eslint-disable-line react/prefer-stateless-function
import styles from './styles.scss';
function Toggle(props) { // eslint-disable-line react/prefer-stateless-function
let content = (<option>--</option>);
// If we have items, render them
@ -21,16 +21,17 @@ function Toggle(props) { // eslint-disable-line react/prefer-stateless-function
}
return (
<select onChange={props.onToggle} className={styles.toggle}>
<select onChange={props.onToggle} className={styles.toggle} defaultValue={props.value}>
{content}
</select>
);
}
Toggle.propTypes = {
onToggle: React.PropTypes.func,
values: React.PropTypes.array,
messages: React.PropTypes.object,
messages: React.PropTypes.object.isRequired,
onToggle: React.PropTypes.func.isRequired,
value: React.PropTypes.string.isRequired,
values: React.PropTypes.array.isRequired,
};
export default Toggle;

View File

@ -14,12 +14,12 @@ const ToggleOption = ({ value, message, intl }) => (
);
ToggleOption.propTypes = {
value: React.PropTypes.string.isRequired,
message: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.string,
]),
intl: intlShape.isRequired,
message: React.PropTypes.oneOfType([
React.PropTypes.object.isRequired,
React.PropTypes.string.isRequired,
]).isRequired,
value: React.PropTypes.string.isRequired,
};
export default injectIntl(ToggleOption);

View File

@ -0,0 +1,74 @@
/*
* AdminPage
*
* This is the first thing users see of our AdminPage, at the '/' route
*
* NOTE: while this component should technically be a stateless functional
* component (SFC), hot reloading does not currently support SFCs. If hot
* reloading is not a neccessity for you then you can refactor it and remove
* the linting exception.
*/
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { Switch, Route } from 'react-router-dom';
import HomePage from 'containers/HomePage';
import PluginPage from 'containers/PluginPage';
import ComingSoonPage from 'containers/ComingSoonPage';
import LeftMenu from 'containers/LeftMenu';
import Content from 'containers/Content';
import NotFoundPage from 'containers/NotFoundPage';
import { selectPlugins } from 'containers/App/selectors';
import { hideNotification } from 'containers/NotificationProvider/actions';
import Header from 'components/Header/index';
import styles from './syles.scss';
export class AdminPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
return (
<div className={styles.adminPage}>
<LeftMenu plugins={this.props.plugins} />
<div className={styles.adminPageRightWrapper}>
<Header />
<Content {...this.props}>
<Switch>
<Route path="/" component={HomePage} exact />
<Route path="/plugins/:pluginId" component={PluginPage} />
<Route path="/plugins" component={ComingSoonPage} />
<Route path="/list-plugins" component={ComingSoonPage} exact />
<Route path="/install-plugin" component={ComingSoonPage} exact />
<Route path="/configuration" component={ComingSoonPage} exact />
<Route path="" component={NotFoundPage} />
</Switch>
</Content>
</div>
</div>
);
}
}
AdminPage.contextTypes = {
router: React.PropTypes.object.isRequired,
};
AdminPage.propTypes = {
plugins: React.PropTypes.object.isRequired,
};
const mapStateToProps = createStructuredSelector({
plugins: selectPlugins(),
});
function mapDispatchToProps(dispatch) {
return {
onHideNotification: (id) => { dispatch(hideNotification(id)); },
dispatch,
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AdminPage);

View File

@ -1,10 +1,10 @@
/* Import */
@import '../../styles/variables/variables';
.homePage { /* stylelint-disable */
.adminPage { /* stylelint-disable */
display: flex;
}
.homePageRightWrapper {
.adminPageRightWrapper {
width: calc(100% - #{$left-menu-width});
}
}

View File

@ -0,0 +1,47 @@
/**
*
* App.js
*
* This component is the skeleton around the actual pages, and should only
* contain code that should be seen on all pages. (e.g. navigation bar)
*
* NOTE: while this component should technically be a stateless functional
* component (SFC), hot reloading does not currently support SFCs. If hot
* reloading is not a neccessity for you then you can refactor it and remove
* the linting exception.
*/
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import AdminPage from 'containers/AdminPage';
import NotFoundPage from 'containers/NotFoundPage';
import NotificationProvider from 'containers/NotificationProvider';
import '../../styles/main.scss';
import styles from './styles.scss';
export class App extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
return (
<div>
<NotificationProvider />
<div className={styles.container}>
<Switch>
<Route path="/" component={AdminPage} />
<Route path="" component={NotFoundPage} />
</Switch>
</div>
</div>
);
}
}
App.contextTypes = {
router: React.PropTypes.object.isRequired,
};
App.propTypes = {};
export default App;

View File

@ -0,0 +1,20 @@
import { createSelector } from 'reselect';
/**
* Direct selector to the languageToggle state domain
*/
const selectApp = () => (state) => state.get('app');
/**
* Select the language locale
*/
const selectPlugins = () => createSelector(
selectApp(),
(appState) => appState.get('plugins')
);
export {
selectApp,
selectPlugins,
};

View File

@ -6,15 +6,13 @@
import React from 'react';
import { connect } from 'react-redux';
import styles from './styles.scss';
import { createSelector } from 'reselect';
import { selectPlugins } from 'containers/App/selectors';
export class Content extends React.Component { // eslint-disable-line react/prefer-stateless-function
static propTypes = {
children: React.PropTypes.node,
};
import styles from './styles.scss';
export class Content extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
return (
<div className={styles.content}>
@ -25,9 +23,7 @@ export class Content extends React.Component { // eslint-disable-line react/pref
}
Content.propTypes = {
plugins: React.PropTypes.object,
onRegisterPluginClicked: React.PropTypes.func,
params: React.PropTypes.object,
children: React.PropTypes.node.isRequired,
};
const mapStateToProps = createSelector(

View File

@ -0,0 +1,34 @@
/*
*
* HomePage
*
*/
import React from 'react';
import { connect } from 'react-redux';
import Helmet from 'react-helmet';
import { FormattedMessage } from 'react-intl';
import messages from './messages.json';
import styles from './styles.scss';
export class HomePage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
return (
<div className={styles.wrapper}>
<Helmet
title="Home Page"
/>
<p><FormattedMessage {...messages.welcome} />.</p>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return {
dispatch,
};
}
export default connect(mapDispatchToProps)(HomePage);

View File

@ -0,0 +1,6 @@
{
"welcome": {
"id": "app.components.HomePage.welcome",
"defaultMessage": "Welcome to the home page"
}
}

View File

@ -0,0 +1,3 @@
.wrapper {
padding: 2.3rem;
}

View File

@ -23,9 +23,9 @@ export class LanguageProvider extends React.Component { // eslint-disable-line r
}
LanguageProvider.propTypes = {
locale: React.PropTypes.string,
messages: React.PropTypes.object,
children: React.PropTypes.element.isRequired,
locale: React.PropTypes.string.isRequired,
messages: React.PropTypes.object.isRequired,
};

View File

@ -0,0 +1,45 @@
/*
*
* LanguageProvider reducer
*
*/
import { fromJS } from 'immutable';
import { first, get, includes, split } from 'lodash';
// Import supported languages from admin config.
import { languages } from '../../../../config/admin.json';
import {
CHANGE_LOCALE,
} from './constants';
// Define a key to store and get user preferences in local storage.
const localStorageKey = 'strapi-admin-language';
// Detect user language.
const userLanguage = window.localStorage.getItem(localStorageKey) || window.navigator.language || window.navigator.userLanguage;
// Split user language in a correct format.
const userLanguageShort = get(split(userLanguage, '-'), '0');
// Check that the language is included in the admin configuration.
const foundLanguage = includes(languages, userLanguageShort) && userLanguageShort;
const initialState = fromJS({
locale: foundLanguage || first(languages) || 'en',
});
function languageProviderReducer(state = initialState, action) {
switch (action.type) {
case CHANGE_LOCALE:
// Set user language in local storage.
window.localStorage.setItem(localStorageKey, action.locale);
return state
.set('locale', action.locale);
default:
return state;
}
}
export default languageProviderReducer;

View File

@ -3,7 +3,7 @@ import { createSelector } from 'reselect';
/**
* Direct selector to the languageToggle state domain
*/
const selectLanguage = () => state => state.get('language');
const selectLanguage = () => (state) => state.get('language');
/**
* Select the language locale

View File

@ -6,9 +6,11 @@
import React from 'react';
import { connect } from 'react-redux';
import LeftMenuHeader from 'components/LeftMenuHeader';
import LeftMenuLinkContainer from 'components/LeftMenuLinkContainer';
import LeftMenuFooter from 'components/LeftMenuFooter';
import styles from './styles.scss';
export class LeftMenu extends React.Component { // eslint-disable-line react/prefer-stateless-function
@ -24,8 +26,7 @@ export class LeftMenu extends React.Component { // eslint-disable-line react/pre
}
LeftMenu.propTypes = {
plugins: React.PropTypes.object,
params: React.PropTypes.object,
plugins: React.PropTypes.object.isRequired,
};
function mapDispatchToProps(dispatch) {

View File

@ -6,12 +6,14 @@
import React from 'react';
import { connect } from 'react-redux';
import { selectLocale } from '../LanguageProvider/selectors';
import { changeLocale } from '../LanguageProvider/actions';
import { languages } from '../../i18n';
import { createSelector } from 'reselect';
import styles from './styles.scss';
import Toggle from 'components/Toggle';
import { selectLocale } from 'containers/LanguageProvider/selectors';
import { changeLocale } from 'containers/LanguageProvider/actions';
import { languages } from 'i18n';
import styles from './styles.scss';
export class LocaleToggle extends React.Component { // eslint-disable-line
render() {
@ -23,14 +25,15 @@ export class LocaleToggle extends React.Component { // eslint-disable-line
return (
<div className={styles.localeToggle}>
<Toggle values={languages} messages={messages} onToggle={this.props.onLocaleToggle} />
<Toggle values={languages} value={this.props.locale} messages={messages} onToggle={this.props.onLocaleToggle} />
</div>
);
}
}
LocaleToggle.propTypes = {
onLocaleToggle: React.PropTypes.func,
locale: React.PropTypes.string.isRequired,
onLocaleToggle: React.PropTypes.func.isRequired,
};
const mapStateToProps = createSelector(

View File

@ -11,9 +11,11 @@
import React from 'react';
import { defineMessages, FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import styles from './styles.scss';
import { Link } from 'react-router';
import messages from './messages.json';
defineMessages(messages);
export default class NotFound extends React.Component { // eslint-disable-line react/prefer-stateless-function
@ -26,7 +28,7 @@ export default class NotFound extends React.Component { // eslint-disable-line r
<h2 className={styles.notFoundDescription}>
<FormattedMessage {...messages.description} />
</h2>
<Link to={'/'}>Back to home page.</Link>
<Link to="/admin">Back to home page.</Link>
</div>
);
}

View File

@ -4,16 +4,17 @@
*
*/
import { dispatch } from 'app';
import {
SHOW_NOTIFICATION,
HIDE_NOTIFICATION,
} from './constants';
import { dispatch } from '../../app';
let nextNotificationId = 0;
export function showNotification(message, status) {
nextNotificationId++;
nextNotificationId++; // eslint-disable-line no-plusplus
// Start timeout to hide the notification
((id) => {

View File

@ -0,0 +1,45 @@
/*
*
* NotificationProvider
*
*/
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import NotificationsContainer from 'components/NotificationsContainer';
import { selectNotifications } from './selectors';
import { hideNotification } from './actions';
export class NotificationProvider extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
return (
<NotificationsContainer
onHideNotification={this.props.onHideNotification}
notifications={this.props.notifications}
/>
);
}
}
NotificationProvider.propTypes = {
notifications: React.PropTypes.object.isRequired,
onHideNotification: React.PropTypes.func.isRequired,
};
const mapStateToProps = createStructuredSelector({
notifications: selectNotifications(),
});
function mapDispatchToProps(dispatch) {
return {
onHideNotification: (id) => {
dispatch(hideNotification(id));
},
dispatch,
};
}
export default connect(mapStateToProps, mapDispatchToProps)(NotificationProvider);

View File

@ -9,17 +9,17 @@ import { connect } from 'react-redux';
import Helmet from 'react-helmet';
import { createSelector } from 'reselect';
import { selectPlugins } from 'containers/App/selectors';
import PluginHeader from 'components/PluginHeader';
const exposedComponents = {
PluginHeader,
};
export class PluginPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const containers = this.props.plugins.valueSeq().map((plugin, i) => {
const Elem = plugin.get('mainComponent');
return <Elem key={i} {...this.props} exposedComponents={exposedComponents}></Elem>;
// Detect plugin id from url params
const pluginId = this.props.match.params.pluginId;
const containers = this.props.plugins.valueSeq().map((plugin) => {
if (plugin.get('id') === pluginId) {
const Elem = plugin.get('mainComponent');
return <Elem key={plugin.get('id')} {...this.props} />;
}
});
return (
@ -41,7 +41,8 @@ PluginPage.contextTypes = {
};
PluginPage.propTypes = {
plugins: React.PropTypes.object,
match: React.PropTypes.object.isRequired,
plugins: React.PropTypes.object.isRequired,
};
const mapStateToProps = createSelector(

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -5,16 +5,12 @@
<meta charset="utf-8">
<!-- Make the page mobile compatible -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Allow installing the app to the homescreen -->
<link rel="manifest" href="manifest.json">
<meta name="mobile-web-app-capable" content="yes">
<title>Strapi Admin</title>
<base href="/admin">
</head>
<body>
<!-- The app hooks into this div -->
<div id="app"></div>
<!-- A lot of magic happens in this file. HtmlWebpackPlugin automatically includes all assets (e.g. bundle.js, main.css) with the correct HTML tags, which is why they are missing in this HTML file. Don't add any assets here! (Check out webpackconfig.js if you want to know more) -->
</body>
<script type="text/javascript" src="http://localhost:3000/main.js"></script>
</html>

View File

@ -1,12 +1,12 @@
/**
* Combine all reducers in this file and export the combined reducers.
* If we were to do this in store.js, reducers wouldn't be hot reloadable.
*/
import { combineReducers } from 'redux-immutable';
import { fromJS } from 'immutable';
import { combineReducers } from 'redux-immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
import appReducer from 'containers/App/reducer';
import globalReducer from 'containers/App/reducer';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import notificationProviderReducer from 'containers/NotificationProvider/reducer';
@ -14,13 +14,13 @@ import notificationProviderReducer from 'containers/NotificationProvider/reducer
* routeReducer
*
* The reducer merges route location changes into our immutable state.
* The change is necessitated by moving to react-router-redux@4
* The change is necessitated by moving to react-router-redux@5
*
*/
// Initial routing state
const routeInitialState = fromJS({
locationBeforeTransitions: null,
location: null,
});
/**
@ -31,7 +31,7 @@ function routeReducer(state = routeInitialState, action) {
/* istanbul ignore next */
case LOCATION_CHANGE:
return state.merge({
locationBeforeTransitions: action.payload,
location: action.payload,
});
default:
return state;
@ -39,14 +39,14 @@ function routeReducer(state = routeInitialState, action) {
}
/**
* Creates the main reducer with the asynchronously loaded ones
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(asyncReducers) {
export default function createReducer(injectedReducers) {
return combineReducers({
route: routeReducer,
app: globalReducer,
language: languageProviderReducer,
notification: notificationProviderReducer,
app: appReducer,
...asyncReducers,
...injectedReducers,
});
}

View File

@ -1,5 +1,5 @@
/**
* Create the store with asynchronously loaded reducers
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
@ -9,7 +9,6 @@ import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
const sagaMiddleware = createSagaMiddleware();
const devtools = window.devToolsExtension || (() => noop => noop);
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
@ -22,30 +21,40 @@ export default function configureStore(initialState = {}, history) {
const enhancers = [
applyMiddleware(...middlewares),
devtools(),
];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
// Prevent recomputing reducers for `replaceReducer`
shouldHotReload: false,
})
: compose;
/* eslint-enable */
const store = createStore(
createReducer(),
fromJS(initialState),
compose(...enhancers)
composeEnhancers(...enhancers)
);
// Create hook for async sagas
// Extensions
store.runSaga = sagaMiddleware.run;
store.injectedReducers = {}; // Reducer registry
store.injectedSagas = {}; // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
System.import('./reducers').then((reducerModule) => {
const createReducers = reducerModule.default;
const nextReducers = createReducers(store.asyncReducers);
store.replaceReducer(nextReducers);
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
// Initialize it with no other reducers
store.asyncReducers = {};
return store;
}

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