├── .watchmanconfig
├── .vscode
└── settings.json
├── assets
├── icon.png
├── splash.png
├── images
│ ├── icon.png
│ ├── splash.png
│ ├── robot-dev.png
│ └── robot-prod.png
└── fonts
│ └── SpaceMono-Regular.ttf
├── redux
├── api
│ ├── index.js
│ ├── reducer.js
│ └── action.js
├── global-state
│ ├── index.js
│ ├── action.js
│ └── reducer.js
├── Provider.js
├── store.js
└── reducers.js
├── babel.config.js
├── .gitignore
├── layouts
├── dimensions.js
├── FrameBox.js
├── LinearBox.js
├── styles.js
├── FlexBox.js
├── ScreenRotate.js
└── ResponsiveBox.js
├── components
├── CategoriesComp.js
├── MenuButton.js
├── StyledText.js
├── LoadingComp.js
├── __tests__
│ ├── StyledText-test.js
│ └── __snapshots__
│ │ └── StyledText-test.js.snap
├── TabBarIcon.js
├── HomeComp.js
├── AppbarComp.js
├── PostsComp.js
├── PostComp.js
├── WpPageComp.js
├── DrawerComp.js
├── SubMenuComp.js
└── MainMenuComp.js
├── constants
├── Layout.js
└── Colors.js
├── .expo-shared
└── assets.json
├── navigation
├── AppNavigator.js
├── AppNavigator.web.js
├── NavigationService.js
├── MainTabNavigator.js
└── DrawerNavigator.js
├── __tests__
├── __snapshots__
│ └── App-test.js.snap
└── App-test.js
├── screens
├── EditorScreen.js
├── StartScreen.js
├── PostScreen.js
├── MenusScreen.js
├── LinksScreen.js
├── PagesScreen.js
├── SettingsScreen.js
├── PostsScreen.js
├── ThemeScreen.js
├── WpPageScreen.js
└── HomeScreen.js
├── PaperProvider.js
├── app.json
├── App.js
├── package.json
├── customTheme.js
├── containers
├── Container.js
├── WpPageContainer.js
├── PostContainer.js
├── CategoriesContainer.js
├── MainMenuContainer.js
├── SubMenuContainer.js
├── HomeContainer.js
├── PostsContainer.js
└── WordPressApi.js
└── README.md
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "terminal.explorerKind": "external"
3 | }
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielAlongE/wordpress-app-react-expo/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielAlongE/wordpress-app-react-expo/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielAlongE/wordpress-app-react-expo/HEAD/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielAlongE/wordpress-app-react-expo/HEAD/assets/images/splash.png
--------------------------------------------------------------------------------
/assets/images/robot-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielAlongE/wordpress-app-react-expo/HEAD/assets/images/robot-dev.png
--------------------------------------------------------------------------------
/redux/api/index.js:
--------------------------------------------------------------------------------
1 | import * as action from './action';
2 | import reducer from './reducer';
3 |
4 | export {action, reducer};
--------------------------------------------------------------------------------
/assets/images/robot-prod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielAlongE/wordpress-app-react-expo/HEAD/assets/images/robot-prod.png
--------------------------------------------------------------------------------
/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielAlongE/wordpress-app-react-expo/HEAD/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/redux/global-state/index.js:
--------------------------------------------------------------------------------
1 | import * as action from './action';
2 | import reducer from './reducer';
3 |
4 | export {action, reducer};
5 | export default action.default;
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 | web-report/
12 |
--------------------------------------------------------------------------------
/layouts/dimensions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Dimensions } from 'react-native';
3 |
4 | const { width, height } = Dimensions.get('window');
5 |
6 | export { width, height };
--------------------------------------------------------------------------------
/components/CategoriesComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View} from 'react-native';
3 | import CategoriesContainer from '../containers/CategoriesContainer';
4 |
5 | export default CategoriesContainer(View);
--------------------------------------------------------------------------------
/components/MenuButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {IconButton} from 'react-native-paper';
3 |
4 |
5 | const MenuButton = ({...rest}) => ();
6 |
7 | export default MenuButton;
--------------------------------------------------------------------------------
/components/StyledText.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text } from 'react-native';
3 |
4 | export function MonoText(props) {
5 | return (
6 |
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/redux/Provider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import store from './store';
4 |
5 | const StoreProvider = ({children}) => {children}
6 |
7 | export default StoreProvider;
--------------------------------------------------------------------------------
/components/LoadingComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ActivityIndicator, Colors } from 'react-native-paper';
3 |
4 | const LoadingComp = ({...rest}) =>
5 |
6 | export default LoadingComp;
--------------------------------------------------------------------------------
/constants/Layout.js:
--------------------------------------------------------------------------------
1 | import { Dimensions } from 'react-native';
2 |
3 | const width = Dimensions.get('window').width;
4 | const height = Dimensions.get('window').height;
5 |
6 | export default {
7 | window: {
8 | width,
9 | height,
10 | },
11 | isSmallDevice: width < 375,
12 | };
13 |
--------------------------------------------------------------------------------
/redux/store.js:
--------------------------------------------------------------------------------
1 | import {createStore, applyMiddleware} from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from './reducers';
4 |
5 | //undefined, compose()
6 |
7 | const store = createStore(rootReducer,
8 | applyMiddleware(thunk),
9 |
10 | );
11 |
12 |
13 | export default store;
--------------------------------------------------------------------------------
/redux/reducers.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from 'redux';
2 |
3 | import {reducer as api} from './api';
4 | import {reducer as globalState} from './global-state';
5 |
6 | //imported reducers comes here
7 | var obj = {
8 | api,
9 | globalState
10 | }
11 |
12 |
13 | export default combineReducers(
14 | { ...obj });
--------------------------------------------------------------------------------
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "0cae4d70c6df3e5e96ee8b5c442b59d55c8ab8deb466992ab9abc523822f2a1b": true,
3 | "e997a5256149a4b76e6bfd6cbf519c5e5a0f1d278a3d8fa1253022b03c90473b": true,
4 | "af683c96e0ffd2cf81287651c9433fa44debc1220ca7cb431fe482747f34a505": true,
5 | "e7fc0741cc6562975a990e3d9ef820571588dab20aba97032df9f00caa9cd57a": true
6 | }
--------------------------------------------------------------------------------
/components/__tests__/StyledText-test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 |
4 | import { MonoText } from '../StyledText';
5 |
6 | it(`renders correctly`, () => {
7 | const tree = renderer.create(Snapshot test!).toJSON();
8 |
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
--------------------------------------------------------------------------------
/components/__tests__/__snapshots__/StyledText-test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `
4 |
14 | Snapshot test!
15 |
16 | `;
17 |
--------------------------------------------------------------------------------
/constants/Colors.js:
--------------------------------------------------------------------------------
1 | const tintColor = '#2f95dc';
2 |
3 | export default {
4 | tintColor,
5 | tabIconDefault: '#ccc',
6 | tabIconSelected: tintColor,
7 | tabBar: '#fefefe',
8 | errorBackground: 'red',
9 | errorText: '#fff',
10 | warningBackground: '#EAEB5E',
11 | warningText: '#666804',
12 | noticeBackground: tintColor,
13 | noticeText: '#fff',
14 | };
15 |
--------------------------------------------------------------------------------
/layouts/FrameBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import styles from './styles';
4 |
5 |
6 |
7 | const FrameBox = ({children,style={}}) => {
8 |
9 | var {frameBox} = styles;
10 |
11 | return ({children});
14 | }
15 |
16 | export default FrameBox;
--------------------------------------------------------------------------------
/layouts/LinearBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import styles from './styles';
4 |
5 |
6 |
7 | const FrameBox = ({children,style={}}) => {
8 |
9 | var {linearBox} = styles;
10 |
11 | return ({children});
14 | }
15 |
16 | export default FrameBox;
--------------------------------------------------------------------------------
/components/TabBarIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Ionicons } from '@expo/vector-icons';
3 |
4 | import Colors from '../constants/Colors';
5 |
6 | export default function TabBarIcon(props) {
7 | return (
8 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/navigation/AppNavigator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createAppContainer, createSwitchNavigator } from 'react-navigation';
3 |
4 | import DrawerNavigator from './DrawerNavigator';
5 |
6 | export default createAppContainer(
7 | createSwitchNavigator({
8 | // You could add another route here for authentication.
9 | // Read more at https://reactnavigation.org/docs/en/auth-flow.html
10 | Main: DrawerNavigator,
11 | })
12 | );
13 |
--------------------------------------------------------------------------------
/layouts/styles.js:
--------------------------------------------------------------------------------
1 | //import React from 'react';
2 | import { StyleSheet } from 'react-native';
3 | import {width, height} from './dimensions';
4 |
5 | const styles = StyleSheet.create({
6 | frameBox:{flex:1, flexDirection:'column', alignItems:'center', justifyContent:'center'},
7 | linearBox:{width},
8 | borderLine:{backgroundColor:'#ccc',margin:1},
9 | row:{flexDirection:'row'},
10 | col:{flexDirection:'column'},
11 | box:{}
12 | });
13 |
14 | export {width, height};
15 | export default styles;
--------------------------------------------------------------------------------
/__tests__/__snapshots__/App-test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`App renders the loading screen 1`] = `
4 |
9 | `;
10 |
11 | exports[`App renders the root without loading screen 1`] = `
12 |
20 |
21 |
22 | `;
23 |
--------------------------------------------------------------------------------
/navigation/AppNavigator.web.js:
--------------------------------------------------------------------------------
1 | import { createBrowserApp } from '@react-navigation/web';
2 | import { createSwitchNavigator } from 'react-navigation';
3 |
4 | import DrawerNavigator from './DrawerNavigator';
5 |
6 | const switchNavigator = createSwitchNavigator({
7 | // You could add another route here for authentication.
8 | // Read more at https://reactnavigation.org/docs/en/auth-flow.html
9 | Main: DrawerNavigator,
10 | });
11 | switchNavigator.path = '';
12 |
13 | export default createBrowserApp(switchNavigator, { history: 'hash' });
14 |
--------------------------------------------------------------------------------
/navigation/NavigationService.js:
--------------------------------------------------------------------------------
1 | // NavigationService.js
2 |
3 | import { NavigationActions } from 'react-navigation';
4 |
5 | let _navigator;
6 |
7 | function setTopLevelNavigator(navigatorRef) {
8 | _navigator = navigatorRef;
9 | }
10 |
11 | function navigate(routeName, params) {
12 | _navigator.dispatch(
13 | NavigationActions.navigate({
14 | routeName,
15 | params,
16 | })
17 | );
18 | }
19 |
20 | // add other navigation functions that you need and export them
21 |
22 | export default {
23 | navigate,
24 | setTopLevelNavigator,
25 | };
--------------------------------------------------------------------------------
/screens/EditorScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import {TestForm} from '../builder/TestForm';
3 | import AppEditor from '../builder/containers/AppEditorContainer';
4 | import { IconButton } from 'react-native-paper';
5 | import { withNavigationFocus } from "react-navigation";
6 |
7 |
8 | function EditorScreen({navigation, isFocused}) {
9 |
10 | if(isFocused){
11 | return ;
12 | }else{
13 | return null;
14 | }
15 | }
16 |
17 |
18 |
19 | export default withNavigationFocus(EditorScreen);
--------------------------------------------------------------------------------
/redux/global-state/action.js:
--------------------------------------------------------------------------------
1 | export const SET_GLOBAL_STATE = 'SET_GLOBAL_STATE';
2 | export const CLEAR_GLOBAL_STATE = 'CLEAR_GLOBAL_STATE';
3 |
4 | export const setState = (obj) => (
5 | {
6 | type: SET_GLOBAL_STATE,
7 | obj
8 | });
9 |
10 | export const clearState = () => (
11 | {
12 | type: CLEAR_GLOBAL_STATE
13 | });
14 |
15 | export const clear = () => dispatch => dispatch(clearState());
16 |
17 | const set = (obj) => dispatch => {
18 | dispatch(setState(obj));
19 |
20 | return Promise.resolve()
21 |
22 |
23 | }
24 |
25 | export default set;
26 |
27 |
--------------------------------------------------------------------------------
/screens/StartScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import {TestForm} from '../builder/TestForm';
3 | import AppManager from '../builder/containers/AppManagerContainer'
4 | //import AppManager from '../components/MainMenuComp';
5 | import { withNavigationFocus } from "react-navigation";
6 |
7 | function SplashScreen({navigation, isFocused}) {
8 |
9 | return ;
10 |
11 | }
12 |
13 | SplashScreen.navigationOptions = {
14 | title: 'Testing Form',
15 | mode: 'modal',
16 | headerMode: 'none',
17 | header: null
18 | };
19 |
20 | export default withNavigationFocus(SplashScreen);
--------------------------------------------------------------------------------
/screens/PostScreen.js:
--------------------------------------------------------------------------------
1 | //import * as WebBrowser from 'expo-web-browser';
2 | import React from 'react';
3 | import { ScrollView, View } from 'react-native';
4 | import PostComp from '../components/PostComp';
5 | import { withNavigationFocus } from "react-navigation";
6 |
7 | function PostScreen({navigation, isFocused}) {
8 |
9 | const id = navigation.getParam('id', 0);
10 |
11 | const args = {navigation, isFocused, id};
12 |
13 | return (
14 |
15 |
16 |
17 | );
18 |
19 | }
20 |
21 | export default withNavigationFocus(PostScreen);
--------------------------------------------------------------------------------
/components/HomeComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Image, ImageBackground} from 'react-native';
3 | import HomeContainer from '../containers/HomeContainer';
4 | import {default as Box} from '../layouts/ResponsiveBox';
5 | import {Button, List, Avatar, Surface, Card, Text, Title, Paragraph } from 'react-native-paper';
6 | import Loading from './LoadingComp';
7 | import HTML from 'react-native-render-html';
8 | import MainMenu from './MainMenuComp'
9 |
10 | const HomeComp = ({some}) => {
11 |
12 | return (
13 |
14 |
15 | This is the Home Page o
16 |
17 | );
18 | }
19 |
20 | export default HomeContainer(HomeComp);
--------------------------------------------------------------------------------
/screens/MenusScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import {TestForm} from '../builder/TestForm';
3 | import AppMenus from '../builder/containers/AppMenusContainer';
4 | import { IconButton } from 'react-native-paper';
5 | import { withNavigationFocus } from "react-navigation";
6 |
7 | function SettingsScreen({navigation, isFocused} ) {
8 |
9 |
10 | if(isFocused){
11 | return ;
12 | }else{
13 | return null;
14 | }
15 | }
16 |
17 | SettingsScreen.navigationOptions = {
18 | title: 'Menus',
19 | tabBarIcon: ({focused, horizontal, tintColor})=>{
20 | return ();
21 | }
22 | };
23 |
24 | export default withNavigationFocus(SettingsScreen);
--------------------------------------------------------------------------------
/screens/LinksScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ScrollView, StyleSheet } from 'react-native';
3 | import { ExpoLinksView } from '@expo/samples';
4 |
5 | export default function LinksScreen() {
6 | return (
7 |
8 | {/**
9 | * Go ahead and delete ExpoLinksView and replace it with your content;
10 | * we just wanted to provide you with some helpful links.
11 | */}
12 |
13 |
14 | );
15 | }
16 |
17 | LinksScreen.navigationOptions = {
18 | title: 'Links',
19 | };
20 |
21 | const styles = StyleSheet.create({
22 | container: {
23 | flex: 1,
24 | paddingTop: 15,
25 | backgroundColor: '#fff',
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/screens/PagesScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import {TestForm} from '../builder/TestForm';
3 | import AppPages from '../builder/containers/AppPagesContainer';
4 | import { IconButton } from 'react-native-paper';
5 | import { withNavigationFocus } from "react-navigation";
6 |
7 |
8 | function PagesScreen({navigation, isFocused}) {
9 |
10 | if(isFocused){
11 | return ;
12 | }else{
13 | return null;
14 | }
15 | }
16 |
17 | PagesScreen.navigationOptions = {
18 | title: 'Pages',
19 | tabBarIcon: ({focused, horizontal, tintColor})=>{
20 | return ();
21 | }
22 | };
23 |
24 |
25 | export default withNavigationFocus(PagesScreen);
--------------------------------------------------------------------------------
/screens/SettingsScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import {TestForm} from '../builder/TestForm';
3 | import { withNavigationFocus } from "react-navigation";
4 | import AppSettings from '../builder/containers/AppSettingsContainer';
5 | import { IconButton } from 'react-native-paper';
6 |
7 |
8 | function SettingsScreen({navigation, isFocused}) {
9 |
10 | if(isFocused){
11 | return ;
12 | }else{
13 | return null;
14 | }
15 | }
16 |
17 | SettingsScreen.navigationOptions = {
18 | title: 'Settings',
19 | tabBarIcon: ({focused, horizontal, tintColor})=>{
20 | return ();
21 | }
22 | };
23 |
24 | export default withNavigationFocus(SettingsScreen);
25 |
--------------------------------------------------------------------------------
/layouts/FlexBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import styles from './styles';
4 |
5 |
6 |
7 | const Row = ({children,style={}}) => {
8 |
9 | var {row} = styles;
10 |
11 | return ({children});
14 | }
15 |
16 | const Col = ({children,style={}}) => {
17 |
18 | var {col} = styles;
19 |
20 | return ({children});
23 | }
24 |
25 | const Box = ({children,style={}, ...rest}) => {
26 |
27 | var {box} = styles;
28 |
29 | return ({children});
34 | }
35 |
36 | export {Row, Col, Box};
--------------------------------------------------------------------------------
/components/AppbarComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View} from 'react-native';
3 | import {Appbar} from 'react-native-paper';
4 | import MainMenu from './MainMenuComp';
5 |
6 | const AppbarComp = ({navigation}) => {
7 | //subtitle={subtitle || ''}
8 | const title = navigation.getParam('title', 'Wordpress App');
9 | return (
10 |
11 |
12 | navigation.toggleDrawer()} />
13 |
14 | {/* navigation.goBack()} />*/}
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | export default AppbarComp;
--------------------------------------------------------------------------------
/screens/PostsScreen.js:
--------------------------------------------------------------------------------
1 | //import * as WebBrowser from 'expo-web-browser';
2 | import React from 'react';
3 | import { ScrollView, View } from 'react-native';
4 | //import MenuButton from '../components/MenuButton';
5 | import PostsComp from '../components/PostsComp';
6 | import { withNavigationFocus } from "react-navigation";
7 |
8 | function PostsScreen({navigation, isFocused}) {
9 |
10 | const categories = navigation.getParam('categories', 0);
11 |
12 | const args = {navigation, isFocused};
13 |
14 | if(categories>0){
15 | args.categories = categories;
16 | }
17 |
18 | return (
19 |
20 |
21 |
22 | );
23 |
24 |
25 |
26 | }
27 |
28 |
29 | export default withNavigationFocus(PostsScreen);
--------------------------------------------------------------------------------
/screens/ThemeScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import {TestForm} from '../builder/TestForm';
3 | import AppTheme from '../builder/containers/AppThemeContainer';
4 | //import AppThemeComp from '../builder/components/AppThemeComp';
5 | import { IconButton } from 'react-native-paper';
6 | import { withNavigationFocus } from "react-navigation";
7 |
8 | function ThemeScreen({navigation, isFocused} ) {
9 |
10 | if(isFocused){
11 | return ;
12 | }else{
13 | return null;
14 | }
15 |
16 |
17 | }
18 |
19 | ThemeScreen.navigationOptions = {
20 | title: 'Theme',
21 | tabBarIcon: ({focused, horizontal, tintColor})=>{
22 | return ();
23 | }
24 | };
25 |
26 | export default withNavigationFocus(ThemeScreen)
--------------------------------------------------------------------------------
/__tests__/App-test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavigationTestUtils from 'react-navigation/NavigationTestUtils';
3 | import renderer from 'react-test-renderer';
4 |
5 | import App from '../App';
6 |
7 | jest.mock('expo', () => ({
8 | AppLoading: 'AppLoading',
9 | }));
10 |
11 | jest.mock('../navigation/AppNavigator', () => 'AppNavigator');
12 |
13 | describe('App', () => {
14 | jest.useFakeTimers();
15 |
16 | beforeEach(() => {
17 | NavigationTestUtils.resetInternalState();
18 | });
19 |
20 | it(`renders the loading screen`, () => {
21 | const tree = renderer.create().toJSON();
22 | expect(tree).toMatchSnapshot();
23 | });
24 |
25 | it(`renders the root without loading screen`, () => {
26 | const tree = renderer.create().toJSON();
27 | expect(tree).toMatchSnapshot();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/PaperProvider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Provider } from 'react-native-paper';
4 | //import NavigationService from './navigation/NavigationService';
5 | import { DefaultTheme } from 'react-native-paper';
6 |
7 |
8 | function PaperProvider({children, theme=DefaultTheme, ...rest}) {
9 |
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 |
17 | }
18 |
19 |
20 | const mapStateToProps = state => {
21 |
22 | const appIndex = state.globalState.currentApp || 0;
23 |
24 | const apps = state.globalState.apps || [];
25 |
26 | const theme = apps && apps[appIndex] && apps[appIndex]['theme'];
27 |
28 | //console.log("PaperProvider", appIndex, theme)
29 |
30 | return ({theme, appIndex});
31 | };
32 |
33 |
34 | export default connect(mapStateToProps)(PaperProvider);
35 |
--------------------------------------------------------------------------------
/components/PostsComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import {View, Image, ImageBackground} from 'react-native';
3 | import PostsContainer from '../containers/PostsContainer';
4 | import {default as Box} from '../layouts/ResponsiveBox';
5 | import {Button, List, Avatar, Surface, Card, Text, Title, Paragraph, View } from 'react-native-paper';
6 | import Loading from './LoadingComp';
7 | //import HTML from 'react-native-render-html';
8 | import {WordPressThumbnailList} from '../builder/components/WordPressPostsComp';
9 |
10 |
11 | const Posts = ({posts=[], fetchMore, isFetching, navigation}) => {
12 |
13 | const args = {posts, isFetching, navigation};
14 |
15 | return (
16 |
17 |
18 | {isFetching ? : }
19 | );
20 | }
21 |
22 | export default PostsContainer(Posts);
--------------------------------------------------------------------------------
/layouts/ScreenRotate.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { ScreenOrientation } from 'expo';
4 |
5 |
6 | export default class ScreenRotate extends React.Component {
7 |
8 |
9 | componentWillMount() {
10 | /* Dimensions.addEventListener('change', ()=>{
11 | if(this.props.onChange){
12 | this.props.onChange();
13 | }
14 | });
15 | */
16 | }
17 |
18 | componentWillUnmount() {
19 | //Dimensions.removeEventListener('change');
20 | }
21 |
22 | componentDidMount() {
23 | //ScreenOrientation.allow(ScreenOrientation.OrientationLock.ALL);
24 | //OrientationLock.ALL
25 | (async function changeScreenOrientation() {
26 | await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.ALL);
27 | })();
28 | }
29 |
30 | render(){
31 | const {children} = this.props;
32 | return(
33 |
34 | {children ? children : null}
35 | )
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "Wordpress App Builder",
4 | "slug": "wordpress-app-builder",
5 | "privacy": "public",
6 | "sdkVersion": "34.0.0",
7 | "platforms": [
8 | "ios",
9 | "android",
10 | "web"
11 | ],
12 | "version": "1.0.0",
13 | "orientation": "portrait",
14 | "icon": "./assets/images/icon.png",
15 | "splash": {
16 | "image": "./assets/images/splash.png",
17 | "resizeMode": "contain",
18 | "backgroundColor": "#ffffff"
19 | },
20 | "updates": {
21 | "fallbackToCacheTimeout": 0
22 | },
23 | "assetBundlePatterns": [
24 | "**/*"
25 | ],
26 | "ios": {
27 | "supportsTablet": true
28 | },
29 | "description": "In seconds your WordPress site becomes a Native App.\nThe WordPress Gutenberg editor is the inspiration behind my quest.\nImagine being able to add and manipulate components just as you would on using a visual editor on WordPress.\n\nCollaborators are welcomed!",
30 | "githubUrl": "https://github.com/DanielAlongE/wordpress-app-react-expo"
31 | }
32 | }
--------------------------------------------------------------------------------
/screens/WpPageScreen.js:
--------------------------------------------------------------------------------
1 | //import * as WebBrowser from 'expo-web-browser';
2 | import React from 'react';
3 | import {
4 | ScrollView,
5 | StyleSheet,
6 | Text,
7 | View,
8 | } from 'react-native';
9 | import MenuButton from '../components/MenuButton';
10 | import WpPageComp from '../components/WpPageComp';
11 |
12 | export default class WpPageScreen extends React.Component {
13 |
14 | static navigationOptions = ({ navigation }) => {
15 |
16 | return ({
17 | headerTitle: navigation.getParam('title', 'Wordpress App'),
18 | headerLeft: ({scene})=>{
19 | var {navigation} = scene.descriptor;
20 | return (navigation.toggleDrawer()} />)},
21 | });
22 | }
23 |
24 |
25 | render(){
26 | const {navigation} = this.props;
27 |
28 | var id = navigation.getParam('id', 0);
29 |
30 | var args = {navigation, id};
31 |
32 | return (
33 |
34 | )
35 | }
36 | }
37 |
38 | const styles = StyleSheet.create({
39 | container: {
40 | flex: 1,
41 | backgroundColor: '#fff',
42 | },
43 |
44 | });
45 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | //import { AppLoading } from 'expo';
2 | //import { Asset } from 'expo-asset';
3 | //import * as Font from 'expo-font';
4 | import React from 'react';
5 | import { AppRegistry, Platform, StatusBar, StyleSheet, View, Text } from 'react-native';
6 | //import { Ionicons } from '@expo/vector-icons';
7 |
8 | import AppNavigator from './navigation/AppNavigator';
9 | import Provider from './redux/Provider';
10 | //import { Provider as PaperProvider } from 'react-native-paper';
11 | //import NavigationService from './navigation/NavigationService';
12 | //import theme from './customTheme';
13 | //import { DefaultTheme } from 'react-native-paper';
14 | import PaperProvider from './PaperProvider';
15 |
16 |
17 | function App() {
18 |
19 |
20 | return (
21 |
22 |
23 | {Platform.OS === 'ios' && }
24 |
25 |
26 |
27 | );
28 |
29 | }
30 |
31 |
32 | export default App;
33 |
34 | AppRegistry.registerComponent('app', () => App);
35 |
36 |
--------------------------------------------------------------------------------
/redux/global-state/reducer.js:
--------------------------------------------------------------------------------
1 | import {SET_GLOBAL_STATE, CLEAR_GLOBAL_STATE} from './action';
2 |
3 | const initialState = {
4 | url:'https://www.premiumtimesng.com', title:'PremiumTimes',
5 | //url:'https://www.thecable.ng', title:'TheCable'
6 | //url: 'https://www.punchng.com'
7 | apps:[
8 | {
9 | url:'https://www.premiumtimesng.com', title:'PremiumTimes', name:'PremiumTimes'
10 | },
11 | {
12 | url:'https://www.thecable.ng', title:'TheCable', name:'TheCable',
13 | menus:{
14 | main_menu:[
15 | {name:"Home", type:'page', id:1, title:'This is the home page', icon:'home'},
16 | {name:'About Us', type:'wp_page', id:2},
17 | {name:'Exchange', type:'wp_page', title:'Ex', id:113154}
18 | ],
19 | sub_menu:[],
20 | custom_menu:[]
21 | }
22 | }
23 | ]
24 | };
25 |
26 | const reducer = (state = initialState, action) => {
27 |
28 | const {type, obj} = action;
29 |
30 | switch (type) {
31 | case SET_GLOBAL_STATE:
32 |
33 | return {...state, ...obj}
34 |
35 | case CLEAR_GLOBAL_STATE:
36 |
37 | return {}
38 |
39 | default:
40 | return state;
41 | }
42 | }
43 |
44 | export default reducer;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject",
9 | "test": "jest --watchAll"
10 | },
11 | "jest": {
12 | "preset": "jest-expo"
13 | },
14 | "dependencies": {
15 | "@expo/samples": "~3.0.3",
16 | "@expo/vector-icons": "^10.0.3",
17 | "@react-navigation/web": "^1.0.0-alpha.9",
18 | "axios": "^0.19.0",
19 | "expo": "^34.0.1",
20 | "expo-asset": "^6.0.0",
21 | "expo-constants": "6.0.0",
22 | "expo-font": "~6.0.0",
23 | "expo-web-browser": "6.0.0",
24 | "moments": "^0.0.2",
25 | "react": "16.8.3",
26 | "react-dom": "^16.8.6",
27 | "react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",
28 | "react-native-gesture-handler": "~1.3.0",
29 | "react-native-paper": "^2.16.0",
30 | "react-native-render-html": "^4.1.2",
31 | "react-native-web": "^0.11.4",
32 | "react-navigation": "^3.11.0",
33 | "react-redux": "^7.1.0",
34 | "redux": "^4.0.4",
35 | "redux-thunk": "^2.3.0"
36 | },
37 | "devDependencies": {
38 | "babel-preset-expo": "^6.0.0",
39 | "jest-expo": "^34.0.0"
40 | },
41 | "private": true
42 | }
43 |
--------------------------------------------------------------------------------
/components/PostComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Image, ImageBackground} from 'react-native';
3 | import PostContainer from '../containers/PostContainer';
4 | import {default as Box} from '../layouts/ResponsiveBox';
5 | import {Button, List, Avatar, Surface, Card, Text, Title, Paragraph } from 'react-native-paper';
6 | //import Loading from './LoadingComp';
7 | import HTML from 'react-native-render-html';
8 |
9 |
10 |
11 | const Post = ({post={},...rest}) => {
12 |
13 | //{key:`post-${key}-${id}`, id, title:purgeHtml(t), content:c, excerpt:purgeHtml(e), date:dateFromNow, media:{thumbnail, medium, full}};
14 |
15 | const {title="Post not found", content, date, media} = post;
16 |
17 | return(
18 |
19 |
20 | {media && }
21 |
22 | {title}
23 | {date && {date}}
24 |
25 |
26 |
27 | {content && }
28 |
29 |
30 |
31 | );
32 |
33 |
34 |
35 | }
36 |
37 | //const Post = ({id})=>I am a post {id}
38 |
39 | export default PostContainer(Post)
--------------------------------------------------------------------------------
/components/WpPageComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Image, ImageBackground} from 'react-native';
3 | import WpPageContainer from '../containers/WpPageContainer';
4 | import {default as Box} from '../layouts/ResponsiveBox';
5 | import {Button, List, Avatar, Surface, Card, Text, Title, Paragraph } from 'react-native-paper';
6 | //import Loading from './LoadingComp';
7 | import HTML from 'react-native-render-html';
8 |
9 |
10 |
11 | const Page = ({page={},...rest}) => {
12 |
13 | //{key:`post-${key}-${id}`, id, title:purgeHtml(t), content:c, excerpt:purgeHtml(e), date:dateFromNow, media:{thumbnail, medium, full}};
14 |
15 | const {title="Page not found", content, date, media} = page;
16 |
17 | return (
18 | {content && }
19 | );
20 | /*
21 | return(
22 |
23 |
24 | {media && }
25 |
26 | {title}
27 | {date && {date}}
28 |
29 |
30 |
31 | {content && }
32 |
33 |
34 |
35 | );
36 | */
37 |
38 |
39 | }
40 |
41 | //const Post = ({id})=>I am a post {id}
42 |
43 | export default WpPageContainer(Page)
--------------------------------------------------------------------------------
/components/DrawerComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import {ScrollView, StyleSheet, Text, View} from 'react-native';
4 | //import { DrawerItems, SafeAreaView } from 'react-navigation';
5 | import { Drawer, List, Button } from 'react-native-paper';
6 | //import NavigationService from '../navigation/NavigationService.js';
7 | import LoadingComp from './LoadingComp';
8 | import theme from '../customTheme';
9 |
10 |
11 |
12 | const DrawerComp = ({categories, navigation, ...rest}) => {
13 |
14 | const {navigate} = navigation;
15 |
16 | const data = categories && categories.data ? categories.data : [];
17 |
18 | let root = data.filter((cat)=>cat.parent === 0);
19 |
20 | //console.log('navigation', rest.navigation.push);
21 |
22 | var menu = root.map((n,i) => {
23 |
24 | const {name, id} = n;
25 |
26 | return (
27 | {
30 | navigation.closeDrawer();
31 | navigate('Posts', {title:name, categories:id});
32 | }}
33 | />)}
34 | );
35 |
36 | const {colors} = theme();
37 |
38 | return (
39 |
40 |
41 |
42 | {data.length===0 ? : menu}
43 |
44 |
45 |
46 | )};
47 |
48 | const mapState = state => ({
49 | categories:state.api.categories,
50 | gState: state.globalState
51 | });
52 |
53 | export default connect(mapState)(DrawerComp);
--------------------------------------------------------------------------------
/components/SubMenuComp.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {ScrollView, StyleSheet, Text, View} from 'react-native';
3 | //import { DrawerItems, SafeAreaView } from 'react-navigation';
4 | import { Drawer, List, Button } from 'react-native-paper';
5 | import LoadingComp from './LoadingComp';
6 | //import theme from '../customTheme';
7 | import { DefaultTheme } from 'react-native-paper';
8 |
9 | import SubMenuContainer from '../containers/SubMenuContainer';
10 |
11 |
12 | const SubMenuComp = ({menu, navigation, theme=DefaultTheme}) => {
13 |
14 | const [active, setActive] = useState(-1)
15 |
16 | const showMenu = menu.map((item, index) => {
17 |
18 | const {key, name, icon, onPress} = item;
19 |
20 | const extra = {}
21 | extra.icon = icon;
22 |
23 | const isActive = index===active;
24 |
25 | return (
26 | {
29 | setActive(index);
30 | navigation.closeDrawer();
31 | onPress();
32 | }}
33 | active={isActive}
34 | {...extra}
35 | />)}
36 | );
37 |
38 | const {colors} = theme;
39 |
40 | return (
41 |
42 |
43 |
44 | {menu.length===0 ? : showMenu}
45 |
46 |
47 |
48 | )};
49 |
50 |
51 | export default SubMenuContainer(SubMenuComp);
--------------------------------------------------------------------------------
/customTheme.js:
--------------------------------------------------------------------------------
1 | import { DefaultTheme, Colors } from 'react-native-paper';
2 | import store from './redux/store';
3 |
4 | /*
5 | primary - primary color for your app, usually your brand color.
6 | accent - secondary color for your app which complements the primary color.
7 | background - background color for pages, such as lists.
8 | surface - background color for elements containing content, such as cards.
9 | text - text color for content.
10 | disabled - color for disabled elements.
11 | placeholder - color for placeholder text, such as input placeholder.
12 | backdrop - color for backdrops of various components such as modals.
13 | primary: string;
14 | background: string;
15 | surface: string;
16 | accent: string;
17 | error: string;
18 | text: string;
19 | disabled: string;
20 | placeholder: string;
21 | backdrop: string;
22 |
23 |
24 | const theme = {
25 | ...DefaultTheme,
26 | // roundness: 10,
27 | colors: {
28 | ...DefaultTheme.colors,
29 | // primary: Colors.lightBlue900, //'#3498db',
30 | // accent: Colors.lightBlue500, //'#f1c40f',
31 | // background: Colors.lightBlue50
32 | },
33 | dark:false
34 | };
35 | */
36 |
37 | const theme = () => {
38 |
39 | const state = store.getState();
40 |
41 | const appIndex = state && state.globalstore && state.globalState.currentApp ? state.globalState.currentApp : 0;
42 |
43 | const apps = state && state.globalstore && state.globalstore.apps || [];
44 |
45 | const theme = apps[appIndex] && apps[appIndex]['theme'] || DefaultTheme;
46 |
47 | //console.log("appIndex", appIndex, "theme from state", theme);
48 |
49 | return theme;
50 | };
51 |
52 |
53 | export default theme;
54 |
--------------------------------------------------------------------------------
/containers/Container.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | //import PropTypes from 'prop-types';
3 | import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 |
6 | //import {URL, clearApi, getApi, addApi, editApi, deleteApi, cancelToken} from '../redux/actions';
7 | //import {history} from '../redux/Store';
8 |
9 |
10 | const CategoriesContainer = (Comp, rest={}) => class extends Component {
11 |
12 |
13 | offset=0;
14 | per_page=100;
15 | categories=0;
16 | //author=0;
17 | search='';
18 | orderby='date';
19 | order='desc';
20 | status='publish';
21 |
22 |
23 | constructor(props){
24 |
25 | super(props);
26 |
27 | this.state = {
28 |
29 | }
30 |
31 | //desc, asc |author, date, id, include, modified, parent, relevance, slug, title
32 | //this.fetchMore = this.fetchMore.bind(this);
33 |
34 |
35 | }
36 |
37 |
38 | fetchMore(){
39 |
40 | }
41 |
42 |
43 | componentWillMount(){
44 | this._isMounted = false;
45 | //set cancelToken
46 | //this.cancelToken = this.props.cancelToken();
47 | }
48 |
49 | componentWillUnmount() {
50 | this._isMounted = false;
51 |
52 | //if(this.cancelToken){ this.cancelToken.cancel('ComponenetWillUnmount');}
53 | }
54 |
55 | componentDidMount() {
56 | this._isMounted = true;
57 |
58 | }
59 |
60 |
61 | render() {
62 |
63 |
64 | return (
65 |
66 | )
67 | }
68 |
69 | }
70 |
71 |
72 | const mapStateToProps = state => (
73 | {
74 | // addressBook:state.api.addressBook,
75 |
76 | });
77 |
78 |
79 | export default compose(
80 | connect(mapStateToProps, {cancelToken}),
81 | CategoriesContainer
82 | );
--------------------------------------------------------------------------------
/containers/WpPageContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | //import PropTypes from 'prop-types';
3 | import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 |
6 | import {getApi, cancelToken} from '../redux/api/action';
7 |
8 | import { WordPressClass } from '../builder/containers/WordPressPostsContainer';
9 |
10 | const WpPageContainer = (Comp, rest={}) => class extends WordPressClass {
11 |
12 | //getPostById
13 |
14 | componentDidMount() {
15 | const {id} = this.props;
16 |
17 | const pageIndex = this.findPageIndex({id});
18 |
19 | console.log({id, pageIndex});
20 |
21 | //page not found in store, will attempt to download it
22 | if(pageIndex === -1){
23 | this.fetchPages({id}).then(res=>console.log("done fetching page: ", id));
24 | console.log('fetching pages id:', id)
25 | }
26 |
27 | // console.log("getSinglePost", )
28 |
29 | }
30 |
31 |
32 | render() {
33 |
34 | const {id} = this.props;
35 |
36 |
37 | const data = this.getSinglePage({id});
38 |
39 | //!!data.content
40 |
41 | const page = data ? this.preparePost(data) : {};
42 |
43 | var args = {id, page};
44 |
45 | return (
46 |
47 | )
48 | }
49 |
50 | }
51 |
52 |
53 | const mapStateToProps = state => {
54 |
55 | const appIndex = state.globalState.currentApp || 0;
56 |
57 | return ({
58 | url: state.globalState.url,
59 | pages:state.api[`pages-${appIndex}`],
60 | // categories:state.api[`categories-${appIndex}`],
61 | // gState:state.globalState,
62 | appIndex
63 |
64 | });
65 | };
66 |
67 |
68 | //
69 |
70 | export default compose(
71 | connect(mapStateToProps, {getApi, cancelToken}),
72 | WpPageContainer
73 | );
--------------------------------------------------------------------------------
/navigation/MainTabNavigator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform } from 'react-native';
3 | import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';
4 |
5 | import TabBarIcon from '../components/TabBarIcon';
6 | import HomeScreen from '../screens/HomeScreen';
7 | import LinksScreen from '../screens/LinksScreen';
8 | import SettingsScreen from '../screens/SettingsScreen';
9 |
10 | const config = Platform.select({
11 | web: { headerMode: 'screen' },
12 | default: {},
13 | });
14 |
15 | const HomeStack = createStackNavigator(
16 | {
17 | Home: HomeScreen,
18 | },
19 | config
20 | );
21 |
22 | HomeStack.navigationOptions = {
23 | tabBarLabel: 'Home',
24 | tabBarIcon: ({ focused }) => (
25 |
33 | ),
34 | };
35 |
36 | HomeStack.path = '';
37 |
38 | const LinksStack = createStackNavigator(
39 | {
40 | Links: LinksScreen,
41 | },
42 | config
43 | );
44 |
45 | LinksStack.navigationOptions = {
46 | tabBarLabel: 'Links',
47 | tabBarIcon: ({ focused }) => (
48 |
49 | ),
50 | };
51 |
52 | LinksStack.path = '';
53 |
54 | const SettingsStack = createStackNavigator(
55 | {
56 | Settings: SettingsScreen,
57 | },
58 | config
59 | );
60 |
61 | SettingsStack.navigationOptions = {
62 | tabBarLabel: 'Settings',
63 | tabBarIcon: ({ focused }) => (
64 |
65 | ),
66 | };
67 |
68 | SettingsStack.path = '';
69 |
70 | const tabNavigator = createBottomTabNavigator({
71 | HomeStack,
72 | LinksStack,
73 | SettingsStack,
74 | });
75 |
76 | tabNavigator.path = '';
77 |
78 | export default tabNavigator;
79 |
--------------------------------------------------------------------------------
/containers/PostContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | //import PropTypes from 'prop-types';
3 | import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 |
6 | import {getApi, cancelToken} from '../redux/api/action';
7 |
8 | import { WordPressClass } from '../builder/containers/WordPressPostsContainer';
9 |
10 | const PostContainer = (Comp, rest={}) => class extends WordPressClass {
11 |
12 | //getPostById
13 |
14 | componentDidMount() {
15 | const {id} = this.props;
16 |
17 | const postIndex = this.findPostIndex({id});
18 |
19 | //post not found in store, will attempt to download it
20 | if(postIndex === -1){
21 | this.fetchPosts({id}).then(res=>console.log('done fetching post id: ', id));
22 | }
23 |
24 |
25 | }
26 |
27 |
28 | render() {
29 |
30 | const {id} = this.props;
31 |
32 | //const allData = posts ? posts.data : [];
33 |
34 | //const index = allData.findIndex(post=>post.id === id);
35 |
36 | //const post = index > -1 ? allData[index] : {};
37 |
38 | //console.log('post ', index)
39 |
40 | const data = this.getSinglePost({id});
41 |
42 | const post = data ? this.preparePost(data) : {};
43 |
44 | var args = {id, post};
45 |
46 | return (
47 |
48 | )
49 | }
50 |
51 | }
52 |
53 |
54 | const mapStateToProps = state => {
55 |
56 | const appIndex = state.globalState.currentApp || 0;
57 |
58 | return ({
59 | url: state.globalState.url,
60 | posts:state.api[`posts-${appIndex}`],
61 | // categories:state.api[`categories-${appIndex}`],
62 | // gState:state.globalState,
63 | appIndex
64 |
65 | });
66 | };
67 |
68 |
69 | //
70 |
71 | export default compose(
72 | connect(mapStateToProps, {getApi, cancelToken}),
73 | PostContainer
74 | );
--------------------------------------------------------------------------------
/components/MainMenuComp.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {ScrollView, StyleSheet, Text, View} from 'react-native';
3 | //import { DrawerItems, SafeAreaView } from 'react-navigation';
4 | import { Drawer, List, Button } from 'react-native-paper';
5 | //import LoadingComp from './LoadingComp';
6 | //import theme from '../customTheme';
7 | import { DefaultTheme } from 'react-native-paper';
8 | import {default as Box} from '../layouts/ResponsiveBox';
9 | import MainMenuContainer from '../containers/MainMenuContainer';
10 |
11 |
12 | const MainMenuComp = ({menu=[], navigation, theme=DefaultTheme}) => {
13 |
14 | const [active, setActive] = useState(-1)
15 |
16 | const showMenu = menu.map((item, index) => {
17 |
18 | const {key, name, icon, onPress} = item;
19 |
20 | const extra = {}
21 | extra.icon = icon;
22 | extra.style = {flex:1}
23 |
24 | //if name not found
25 | if(name==='' && icon){
26 | extra.style.flex = 0.2;
27 | }
28 |
29 | const isActive = index===active;
30 |
31 | return (
32 | {
35 | setActive(index);
36 | navigation.closeDrawer();
37 | onPress();
38 | }}
39 | active={isActive}
40 | {...extra}
41 | />)}
42 | );
43 |
44 | const {colors} = theme;
45 | //style={{flex:1}}
46 | /*
47 |
48 |
49 | */
50 |
51 | if(menu.length===0){
52 | return null;
53 | }
54 |
55 | return (
56 |
57 |
58 |
59 | {showMenu}
60 |
61 |
62 |
63 | )};
64 |
65 | //
66 | export default MainMenuContainer(MainMenuComp);
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Wordpress App Builder
2 | This App seeks to explore the posibility of creating a mobile app version of an existing Wordpress Blog.
3 | This is made easy because of the wordpress rest api.
4 | Here we are using React Native via Expo for ease!
5 |
6 | In seconds your WordPress site becomes a Native App.
7 | The WordPress Gutenberg editor is the inspiration behind my quest.
8 | Imagine being able to add and manipulate components just as you would on using a visual editor on WordPress.
9 |
10 | Collaborators are welcomed!
11 |
12 | ### Here are some sample wordpress site url you can try the app with.
13 |
14 | ```
15 | http://www.premiumtimesng.com/
16 | http://www.punchng.com/
17 | http://www.sunnewsonline.com/
18 | http://thecable.ng/
19 | ```
20 |
21 | ## Screenshots
22 |
23 | 
24 |
25 | 
26 |
27 | 
28 |
29 | 
30 |
31 | 
32 |
33 | 
34 |
35 | 
36 |
37 | 
38 |
39 |
40 | Get live prreview [here](https://exp.host/@danielalonge/wordpress-app-builder)
41 |
42 | ## Install
43 |
44 | ```sh
45 | npm install
46 | ```
47 |
48 | Alternatively you may use `yarn`:
49 |
50 | ```sh
51 | yarn install
52 | ```
53 | ## Available Scripts
54 |
55 | In the project directory, you can run:
56 |
57 | ### `expo start`
58 |
--------------------------------------------------------------------------------
/screens/HomeScreen.js:
--------------------------------------------------------------------------------
1 | //import * as WebBrowser from 'expo-web-browser';
2 | import React from 'react';
3 | import { connect } from 'react-redux';
4 | import {
5 | ScrollView,
6 | StyleSheet,
7 | Text,
8 | View,
9 | } from 'react-native';
10 | import MenuButton from '../components/MenuButton';
11 | //import HomeComp from '../components/HomeComp';
12 | //import PostsComp from '../components/PostsComp';
13 | import theme from '../customTheme';
14 | import ScreenRotate from '../layouts/ScreenRotate';
15 | import { withNavigationFocus } from "react-navigation";
16 | import WordPress from '../builder/components/WordPressPostsComp';
17 | //import WordPress from '../components/HomeComp';
18 |
19 |
20 | class HomeScreen extends React.Component {
21 |
22 | static navigationOptions = ({ navigation }) => {
23 |
24 | return ({
25 | headerTitle: navigation.getParam('title', 'Wordpress App'),
26 | headerLeft: ({scene})=>{
27 | var {navigation} = scene.descriptor;
28 | return (navigation.toggleDrawer()} />)},
29 | });
30 | }
31 |
32 | componentDidUpdate(prevProps) {
33 | if (prevProps.isFocused !== this.props.isFocused) {
34 | // Use the `this.props.isFocused` boolean
35 | // Call any action
36 |
37 | }
38 | //console.log("HomeScreen is Focused", prevProps.isFocused , this.props.isFocused)
39 |
40 | //let state = this.props.navigation.state.params;
41 |
42 | //console.log('HomeScreen',{state});
43 | }
44 |
45 | componentDidMount() {
46 |
47 | //this.props.navigation.navigate('Home', {title:'I hope we are right'});
48 |
49 | }
50 |
51 | render(){
52 |
53 | const {navigation, isFocused} = this.props;
54 |
55 | const app = navigation.state.params || {};
56 |
57 | const {colors} = theme();
58 |
59 | return (
60 |
61 |
62 |
63 |
64 |
65 |
66 | )
67 | }
68 | }
69 |
70 |
71 | export default withNavigationFocus(HomeScreen);
--------------------------------------------------------------------------------
/containers/CategoriesContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | //import PropTypes from 'prop-types';
3 | import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 |
6 | import {getApi, cancelToken} from '../redux/api/action';
7 |
8 |
9 | const CategoriesContainer = (Comp, rest={}) => class extends Component {
10 |
11 |
12 | offset=0;
13 | per_page=50;
14 | categories=0;
15 | //author=0;
16 | search='';
17 | orderby='count';
18 | order='desc';
19 | status='publish';
20 | hide_empty=true
21 |
22 |
23 | constructor(props){
24 |
25 | super(props);
26 |
27 | this.state = {
28 |
29 | }
30 |
31 | //desc, asc |author, date, id, include, modified, parent, relevance, slug, title
32 | //this.fetchMore = this.fetchMore.bind(this);
33 |
34 |
35 | }
36 |
37 |
38 | fetchMore(){
39 | let {per_page, orderby, order, hide_empty} = this;
40 | let {getApi, url} = this.props;
41 | //console.log('static', this);
42 | getApi(`${url}/wp-json/wp/v2/categories`,{per_page, orderby, order, hide_empty},'categories')
43 | .then(res=>console.log('done fetching'));
44 | }
45 |
46 |
47 | componentWillMount(){
48 | this._isMounted = false;
49 | //set cancelToken
50 | //this.cancelToken = this.props.cancelToken();
51 | }
52 |
53 | componentWillUnmount() {
54 | this._isMounted = false;
55 |
56 | //if(this.cancelToken){ this.cancelToken.cancel('ComponenetWillUnmount');}
57 | }
58 |
59 | componentDidMount() {
60 | this._isMounted = true;
61 |
62 | const {categories} = this.props
63 |
64 | if(categories){}else{this.fetchMore()}
65 |
66 | }
67 |
68 |
69 | render() {
70 |
71 | //console.log(this.props.url);
72 |
73 | const {navigation} = this.props;
74 |
75 | var categories = this.props.categories ? this.props.categories.data : [];
76 |
77 |
78 |
79 | const args = {categories};
80 |
81 | if(navigation){
82 | args.navigation = navigation;
83 | }
84 |
85 | return (
86 |
87 | )
88 | }
89 |
90 | }
91 |
92 |
93 | const mapStateToProps = state => (
94 | {
95 | url: state.globalState.url,
96 | categories:state.api.categories,
97 | api: state.api
98 |
99 | });
100 |
101 |
102 | export default compose(
103 | connect(mapStateToProps, {getApi, cancelToken}),
104 | CategoriesContainer
105 | );
--------------------------------------------------------------------------------
/navigation/DrawerNavigator.js:
--------------------------------------------------------------------------------
1 | import {DrawerItems, createDrawerNavigator, createStackNavigator, createSwitchNavigator, createBottomTabNavigator} from 'react-navigation';
2 | //import DrawerComp from '../components/DrawerComp';
3 | import theme from '../customTheme';
4 | import React from 'react';
5 | import {View} from 'react-native';
6 |
7 | import DrawerComp from '../components/SubMenuComp';
8 | import AppbarComp from '../components/AppbarComp';
9 |
10 | import HomeScreen from '../screens/HomeScreen';
11 | import PostsScreen from '../screens/PostsScreen';
12 | import PostScreen from '../screens/PostScreen';
13 | import WpPageScreen from '../screens/WpPageScreen';
14 |
15 | import StartScreen from '../screens/StartScreen';
16 | import SettingsScreen from '../screens/SettingsScreen';
17 | import MenusScreen from '../screens/MenusScreen';
18 | import PagesScreen from '../screens/PagesScreen';
19 | import EditorScreen from '../screens/EditorScreen';
20 | import ThemeScreen from '../screens/ThemeScreen';
21 |
22 | const {colors} = theme();
23 |
24 |
25 | const drawerViewConfig = {
26 | contentComponent: DrawerComp,
27 | contentOptions: {
28 | activeTintColor: colors.backdrop,
29 | activeBackgroundColor: colors.primary,
30 | inactiveTintColor: colors.text,
31 | inactiveBackgroundColor: colors.surface,
32 | style: {},
33 | labelStyle: {}
34 | },
35 | drawerWidth : 300,
36 | drawerType: 'front',// 'front' | 'back' | 'slide';
37 | drawerLockMode: "locked-closed", //'unlocked' | 'locked-closed' | 'locked-open';
38 | edgeWidth: 1,
39 | drawerPosition: 'left'
40 | // hideStatusBar: false,
41 | // overlayColor: "#fff",
42 | };
43 |
44 |
45 | const stackConfig = {mode: 'card', //modal| 'card'
46 | headerBackTitleVisible:false,
47 | headerLayoutPreset: 'center',
48 | cardOverlayEnabled: false,
49 |
50 |
51 | defaultNavigationOptions: ({ navigation }) => {
52 | return ({
53 | header: (
54 |
55 | ),
56 | })
57 | }
58 | };
59 |
60 | const PageEditor = createSwitchNavigator(
61 | {
62 | Editor: {screen: EditorScreen}
63 | }
64 | );
65 |
66 |
67 | const SettingsNavigator = createBottomTabNavigator({
68 | Settings: {screen: SettingsScreen},
69 | Menus: {screen: MenusScreen},
70 | Pages: {screen: PagesScreen},
71 | Theme: {screen: ThemeScreen}
72 |
73 | },{
74 | tabBarOptions: {
75 | activeTintColor: 'tomato',
76 | inactiveTintColor: 'gray',
77 | }
78 | });
79 |
80 |
81 | const HomeNavigator = createStackNavigator({
82 | Splash: {screen: StartScreen},
83 | Home: {screen: HomeScreen},
84 | Posts: {screen: PostsScreen},
85 | Post: {screen: PostScreen},
86 | WpPage: {screen: WpPageScreen},
87 | },
88 | {...stackConfig});
89 |
90 | export default createDrawerNavigator({
91 | App: HomeNavigator,
92 | Settings: SettingsNavigator,
93 | Editor: {screen: PageEditor}
94 | },
95 | {
96 | initialRouteName: "App",
97 |
98 | ...drawerViewConfig,
99 |
100 | });
101 |
--------------------------------------------------------------------------------
/containers/MainMenuContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import PropTypes from 'prop-types';
3 | import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 | //import HomeContainer from './HomeContainer';
6 | import {getApi, cancelToken} from '../redux/api/action';
7 | //import NavigationService from '../navigation/NavigationService.js';
8 | //import purgeHtml from '../builder/containers/_purgeHtml';
9 | import { WordPressClass } from '../builder/containers/WordPressPostsContainer';
10 | import * as dotProp from '../builder/containers/_dotProp';
11 |
12 | const MainMenuContainer = (Comp, rest={}) => class extends WordPressClass {
13 |
14 |
15 | offset=0;
16 | per_page=50;
17 | categories=0;
18 | //author=0;
19 | search='';
20 | orderby='count';
21 | order='desc';
22 | status='publish';
23 | hide_empty=true
24 |
25 |
26 | constructor(props){
27 |
28 | super(props);
29 |
30 | this.state = {
31 |
32 | }
33 |
34 | //desc, asc |author, date, id, include, modified, parent, relevance, slug, title
35 | //this.fetchMore = this.fetchMore.bind(this);
36 |
37 |
38 | }
39 |
40 | getAppMenu(target="main_menu"){
41 |
42 | const {currentApp, apps} = this.props.gState;
43 | const app = apps[currentApp] || {} ;
44 |
45 | return dotProp.get(app, `menus.${target}`, []);//app.menus && app.menus[target] ? app.menus[target] : []
46 | }
47 |
48 |
49 | // super methods
50 |
51 | // fetchMore()
52 |
53 | //fetchCategories(obj={})
54 |
55 | getMenuData(){
56 | const menu = this.getAppMenu();
57 | return this.prepareMenu(menu);
58 | }
59 |
60 | componentWillMount(){
61 | this._isMounted = false;
62 | //set cancelToken
63 | //this.cancelToken = this.props.cancelToken();
64 | }
65 |
66 | componentWillUnmount() {
67 | this._isMounted = false;
68 |
69 | //if(this.cancelToken){ this.cancelToken.cancel('ComponenetWillUnmount');}
70 | }
71 |
72 | componentDidMount() {
73 | this._isMounted = true;
74 |
75 | //console.log("getMenuData", this.getMenuData())
76 |
77 | }
78 |
79 |
80 | render() {
81 |
82 | //console.log(this.props.url);
83 |
84 | const {navigation, theme} = this.props;
85 |
86 |
87 | //var categories = this.props.categories ? this.props.categories.data : [];
88 |
89 |
90 |
91 | const menu = this.getMenuData();
92 |
93 |
94 | const args = {menu, theme};
95 |
96 | if(navigation){
97 | args.navigation = navigation;
98 | }
99 |
100 | return (
101 |
102 | )
103 | }
104 |
105 | }
106 |
107 |
108 |
109 | const mapStateToProps = state => {
110 |
111 | const appIndex = state.globalState.currentApp || 0;
112 | const apps = state.globalState.apps || [];
113 | const theme = apps && apps[appIndex] && apps[appIndex]['theme'];
114 |
115 | return ({
116 | url: state.globalState.url,
117 | // posts:state.api[`posts-${appIndex}`],
118 | categories:state.api[`categories-${appIndex}`],
119 | gState:state.globalState,
120 | appIndex,
121 | theme
122 |
123 | });
124 | };
125 |
126 |
127 | export default compose(
128 | connect(mapStateToProps, {getApi, cancelToken}),
129 | MainMenuContainer
130 | );
--------------------------------------------------------------------------------
/containers/SubMenuContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | //import PropTypes from 'prop-types';
3 | import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 | //import HomeContainer from './HomeContainer';
6 | import {getApi, cancelToken} from '../redux/api/action';
7 | //import NavigationService from '../navigation/NavigationService.js';
8 | //import purgeHtml from '../builder/containers/_purgeHtml';
9 | import { WordPressClass } from '../builder/containers/WordPressPostsContainer';
10 |
11 | const SubMenuContainer = (Comp, rest={}) => class extends WordPressClass {
12 |
13 |
14 | offset=0;
15 | per_page=50;
16 | categories=0;
17 | //author=0;
18 | search='';
19 | orderby='count';
20 | order='desc';
21 | status='publish';
22 | hide_empty=true
23 |
24 |
25 | constructor(props){
26 |
27 | super(props);
28 |
29 | this.state = {
30 |
31 | }
32 |
33 | //desc, asc |author, date, id, include, modified, parent, relevance, slug, title
34 | //this.fetchMore = this.fetchMore.bind(this);
35 |
36 |
37 | }
38 |
39 | getAppMenu(target="sub_menu"){
40 |
41 | const {currentApp, apps} = this.props.gState;
42 | const app = apps[currentApp] || {} ;
43 |
44 | return app.menus && app.menus[target] ? app.menus[target] : []
45 | }
46 |
47 |
48 | // super methods
49 |
50 | // fetchMore()
51 |
52 | //fetchCategories(obj={})
53 |
54 |
55 | getMenuData(){
56 | const app = this.getAppMenu();
57 | var menu;
58 |
59 | if(app.length > 0){
60 | menu = app;
61 | }else{
62 | let data = this.props.categories ? this.props.categories.data : [];
63 |
64 | menu = data.filter((cat)=>cat.parent === 0);
65 | menu = this.prepareCategories(menu);
66 | }
67 |
68 | return this.prepareMenu(menu);
69 | }
70 |
71 | componentWillMount(){
72 | this._isMounted = false;
73 | //set cancelToken
74 | //this.cancelToken = this.props.cancelToken();
75 | }
76 |
77 | componentWillUnmount() {
78 | this._isMounted = false;
79 |
80 | //if(this.cancelToken){ this.cancelToken.cancel('ComponenetWillUnmount');}
81 | }
82 |
83 | componentDidMount() {
84 | this._isMounted = true;
85 |
86 | const {categories} = this.props
87 |
88 | }
89 |
90 |
91 | render() {
92 |
93 | //console.log(this.props.url);
94 |
95 | const {navigation, theme} = this.props;
96 |
97 |
98 | //var categories = this.props.categories ? this.props.categories.data : [];
99 |
100 |
101 |
102 | const menu = this.getMenuData();
103 |
104 |
105 | const args = {menu, theme};
106 |
107 | if(navigation){
108 | args.navigation = navigation;
109 | }
110 |
111 | return (
112 |
113 | )
114 | }
115 |
116 | }
117 |
118 |
119 |
120 | const mapStateToProps = state => {
121 |
122 | const appIndex = state.globalState.currentApp || 0;
123 | const apps = state.globalState.apps || [];
124 | const theme = apps && apps[appIndex] && apps[appIndex]['theme'];
125 |
126 | return ({
127 | url: state.globalState.url,
128 | // posts:state.api[`posts-${appIndex}`],
129 | categories:state.api[`categories-${appIndex}`],
130 | gState:state.globalState,
131 | appIndex,
132 | theme
133 |
134 | });
135 | };
136 |
137 |
138 | export default compose(
139 | connect(mapStateToProps, {getApi, cancelToken}),
140 | SubMenuContainer
141 | );
--------------------------------------------------------------------------------
/redux/api/reducer.js:
--------------------------------------------------------------------------------
1 | import {FETCH_API_REQUEST, FETCH_API_SUCCESS, FETCH_API_FAILURE,
2 | FETCH_API_CLEAR, FETCH_API_DELETE, FETCH_API_ADD,
3 | FETCH_API_PREPEND, FETCH_API_EDIT} from './action';
4 |
5 | const initialState = {};
6 |
7 | const reducer = (state = initialState, action) => {
8 |
9 | const {id, type, payload} = action;
10 |
11 | var oldData = state && state[id] && state[id].data ? state[id].data : [];
12 | var oldOffset = state && state[id] && state[id].offset ? state[id].offset : 0;
13 |
14 |
15 | if(typeof id === 'undefined'){
16 | // console.log('id is undefined!');
17 | }else{
18 | //console.log('id is '+id);
19 |
20 | if(state[id] && state[id].data){
21 | //console.log(state[id], state[id].data);
22 | //console.log('state[id] available');
23 | }else{
24 | state = {...state, [id] : {
25 | isFetching:true,
26 | data: [...oldData],
27 | offset:oldOffset
28 | }
29 | }
30 |
31 | //console.log('state[id] unavailable');
32 | }
33 | }
34 |
35 | switch (type) {
36 | case FETCH_API_REQUEST:
37 |
38 | return {...state, [id] : {
39 | isFetching:true,
40 | data: [...oldData],
41 | offset:oldOffset
42 | }
43 | }
44 |
45 | case FETCH_API_SUCCESS:
46 |
47 | let count, newData;
48 |
49 | if(Array.isArray(payload)){
50 | count = payload.length;
51 | newData = payload;
52 | }else{
53 | count = 1;
54 | newData = [payload];
55 | }
56 |
57 |
58 | return {...state, [id] : {
59 | isFetching:false,
60 | data:[...oldData, ...newData],
61 | offset: (oldOffset+count)
62 | }
63 | }
64 |
65 | case FETCH_API_ADD:
66 |
67 | if(Array.isArray(payload)){
68 | count = payload.length;
69 | newData = payload;
70 | }else{
71 | count = 1;
72 | newData = [payload];
73 | }
74 |
75 |
76 | return {...state, [id] : {
77 | isFetching:false,
78 | data:[...oldData, ...newData],
79 | offset: (oldOffset+count)
80 | }
81 | }
82 |
83 | case FETCH_API_PREPEND:
84 |
85 | if(Array.isArray(payload)){
86 | count = payload.length;
87 | newData = payload;
88 | }else{
89 | count = 1;
90 | newData = [payload];
91 | }
92 |
93 |
94 | return {...state, [id] : {
95 | isFetching:false,
96 | data:[...newData, ...oldData],
97 | offset: (oldOffset+count)
98 | }
99 | }
100 |
101 | case FETCH_API_EDIT:
102 |
103 |
104 | let objIndex = oldData.findIndex((data => data.id === payload.id));
105 |
106 | if(objIndex>-1){
107 | oldData[objIndex] = payload;
108 | }
109 |
110 | console.log(objIndex, payload);
111 |
112 | return {...state, [id] : {
113 | data:[...oldData]
114 | }
115 | }
116 |
117 |
118 | case FETCH_API_FAILURE:
119 |
120 | return {...state, [id] : {
121 | isFetching:false,
122 | error:payload,
123 | data:[...oldData],
124 | offset:oldOffset
125 | }
126 | }
127 |
128 |
129 | case FETCH_API_CLEAR:
130 |
131 | return {...state, [id] : {
132 | isFetching:false,
133 | data:[],
134 | offset:0
135 | }
136 | }
137 |
138 | case FETCH_API_DELETE:
139 |
140 | if(payload){
141 | //count = payload.length;
142 |
143 |
144 | let value = payload.value ? payload.value : payload;
145 | let id = payload.id ? payload.id : 'id';
146 |
147 | //let dataIndex = oldData.findIndex((item => item[id] === value));
148 | newData = oldData.filter(item => (item[id] !== value));
149 | //newData = payload;
150 | }
151 |
152 |
153 | return {...state, [id] : {
154 | isFetching:false,
155 | data:[...newData],
156 | offset: (oldOffset-1)
157 | }
158 | }
159 |
160 | default:
161 | return state;
162 | }
163 | }
164 |
165 | export default reducer;
--------------------------------------------------------------------------------
/containers/HomeContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | //import PropTypes from 'prop-types';
3 | import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 |
6 | import {getApi, cancelToken} from '../redux/api/action';
7 | import set from '../redux/global-state';
8 |
9 | import { WordPressClass } from '../builder/containers/WordPressPostsContainer';
10 |
11 | const HomeContainer = (Comp, rest={}) => class extends WordPressClass {
12 | apiId = 'posts';
13 | offset=0;
14 | per_page=10;
15 | //categories=0;
16 | //author=0;
17 | search='';
18 | orderby='date';
19 | order='desc';
20 | status='publish';
21 | //hide_empty=true
22 |
23 |
24 |
25 | constructor(props){
26 |
27 | super(props, {per_page:1, strange:'I am strange'});
28 |
29 | //this.comp = comp;
30 |
31 | this.state = {
32 |
33 | }
34 |
35 | //desc, asc |author, date, id, include, modified, parent, relevance, slug, title
36 | //this.fetchMore = this.fetchMore.bind(this);
37 |
38 |
39 | }
40 |
41 | //getAvailablePostsId()
42 |
43 | //fetchCategories(obj={})
44 |
45 | //fetchMore()
46 |
47 | //fetchPosts(obj={})
48 |
49 | //getCategories()
50 |
51 |
52 | //oneByOne(objects_array, iterator, callback)
53 |
54 | //fetchPostsByCategory(ids=[])
55 |
56 | //preparePost(post)
57 |
58 | //preparePosts(posts)
59 |
60 | //generateHome()
61 |
62 |
63 | componentWillMount(){
64 | this._isMounted = false;
65 |
66 | }
67 |
68 | init(){
69 | const {appIndex, url, posts} = this.props;
70 |
71 | //let collect = purgeHtml("This is Html $2000 and %50
");
72 | //console.log(`"${collect}"`);
73 |
74 | console.log('home', {appIndex, url});
75 |
76 | //get app config
77 | //this.props.navigation.setParams({ title: gState.title || 'Kilode?' });
78 | if(posts && posts.data){}else{
79 | super.generateHome();
80 | }
81 | }
82 |
83 | componentDidMount() {
84 | this._isMounted = true;
85 |
86 | console.log('Home, componentDidMount')
87 | this.init();
88 | }
89 |
90 | componentWillReceiveProps(nextProps) {
91 | if(nextProps.appIndex !== this.props.appIndex){
92 | this.init();
93 | console.log('Home, componentWillReceiveProps', nextProps.appIndex, this.props.appIndex)
94 | }
95 |
96 | }
97 |
98 | componentDidUpdate(prevProps) {
99 | if (prevProps.isFocused !== this.props.isFocused) {
100 | this.init();
101 | }
102 |
103 |
104 | }
105 |
106 | componentWillUnmount() {
107 | this._isMounted = false;
108 |
109 | //if(this.cancelToken){ this.cancelToken.cancel('ComponenetWillUnmount');}
110 | }
111 |
112 | render() {
113 |
114 | const {fetchMore} = this;
115 |
116 | const {navigation, appIndex, posts} = this.props;
117 |
118 | //console.log('Home-config',gState);
119 |
120 | var args = {fetchMore};
121 |
122 | args.posts = posts && Array.isArray(posts.data) ? this.preparePosts(posts.data,appIndex) : [];
123 |
124 | if(navigation){
125 | args.navigation = navigation;
126 | }
127 |
128 |
129 | return (
130 |
131 | )
132 | }
133 | }
134 |
135 |
136 | const mapStateToProps = state => {
137 |
138 | const appIndex = state.globalState.currentApp || 0;
139 |
140 | return ({
141 | url: state.globalState.url,
142 | posts:state.api[`posts-${appIndex}`],
143 | categories:state.api[`categories-${appIndex}`],
144 | gState:state.globalState,
145 | appIndex
146 |
147 | });
148 | };
149 |
150 | //
151 | export default compose(
152 | connect(mapStateToProps, {set, getApi, cancelToken}),
153 | HomeContainer
154 | );
--------------------------------------------------------------------------------
/containers/PostsContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | //import PropTypes from 'prop-types';
3 | import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 |
6 | import {getApi, cancelToken} from '../redux/api/action';
7 |
8 | import { WordPressClass } from '../builder/containers/WordPressPostsContainer';
9 |
10 |
11 | const PostsContainer = (Comp, rest={}) => class extends WordPressClass {
12 |
13 | apiId = !!rest.id ? rest.id : 'posts';
14 | offset=0;
15 | per_page=10;
16 | //categories=0;
17 | //author=0;
18 | search='';
19 | orderby='date';
20 | order='desc';
21 | status='publish';
22 | //hide_empty=true
23 |
24 | constructor(props){
25 |
26 | super(props);
27 |
28 | this.state = {
29 |
30 | }
31 |
32 | //desc, asc |author, date, id, include, modified, parent, relevance, slug, title
33 | this.fetchMore = this.fetchMore.bind(this);
34 |
35 |
36 | }
37 |
38 |
39 | fetchMore(){
40 | let obj ={};
41 | const {categories} = this.props;
42 |
43 | if(categories){
44 | obj.categories = categories;
45 | }
46 |
47 | this.fetchPosts(obj);
48 | }
49 |
50 |
51 | componentWillMount(){
52 | this._isMounted = false;
53 | //set cancelToken
54 | //this.cancelToken = this.props.cancelToken();
55 | }
56 |
57 | componentWillUnmount() {
58 | this._isMounted = false;
59 |
60 | //if(this.cancelToken){ this.cancelToken.cancel('ComponenetWillUnmount');}
61 | }
62 |
63 | shouldComponentUpdate(nextProps) {
64 | const oldCat = this.props.categories;
65 | const newCat = nextProps.categories;
66 |
67 |
68 | if(oldCat !== newCat){
69 | this.init();
70 | console.log("Posts Cat Changed", oldCat, newCat );
71 | }
72 | return true;
73 | }
74 |
75 | init(){
76 | const {categories, posts, navigation} = this.props;
77 |
78 | if(navigation){
79 | //close Drawer
80 | navigation.closeDrawer();
81 | }
82 |
83 | if(categories){
84 | if(posts){
85 | const check = posts.data.filter(post=>{ return post.categories.includes(categories); });
86 |
87 | if(check.length>=5){
88 | console.log(`post category:${categories} has ${check.length} data`);
89 | }else{
90 | this.fetchMore();
91 | console.log(`post category:${categories} has ${check.length} data`);
92 | }
93 |
94 | }else{
95 | this.fetchMore();
96 | }
97 | }
98 | else if(posts && posts.data){}else{this.fetchMore()}
99 |
100 | }
101 |
102 | componentDidMount() {
103 | this._isMounted = true;
104 |
105 | this.init();
106 | }
107 |
108 |
109 | render() {
110 |
111 | const {fetchMore} = this;
112 |
113 | const {navigation, posts, categories, appIndex} = this.props;
114 |
115 | const args = {fetchMore};
116 |
117 | //this.init();
118 |
119 | if(posts){
120 | //args.posts = posts;
121 | const {data=[]} = posts;
122 |
123 | args.isFetching = posts.isFetching;
124 |
125 | var collection = [];
126 |
127 | if(categories){
128 | args.categories = categories;
129 | collection = data.filter(post=>{ return post.categories.includes(categories); });
130 |
131 | }else{
132 | collection = data;
133 | }
134 |
135 | //prepare post for render
136 | args.posts = Array.isArray(data) ? this.preparePosts(collection, appIndex) : [];
137 |
138 | }
139 |
140 |
141 |
142 |
143 |
144 | if(navigation){
145 | args.navigation = navigation;
146 | }
147 |
148 | return (
149 |
150 | )
151 | }
152 |
153 | }
154 |
155 |
156 |
157 | const mapStateToProps = state => {
158 |
159 | const appIndex = state.globalState.currentApp || 0;
160 |
161 | return ({
162 | url: state.globalState.url,
163 | posts:state.api[`posts-${appIndex}`],
164 | // categories:state.api[`categories-${appIndex}`],
165 | // gState:state.globalState,
166 | appIndex
167 |
168 | });
169 | };
170 |
171 | export default compose(
172 | connect(mapStateToProps, {getApi, cancelToken}),
173 | PostsContainer
174 | );
--------------------------------------------------------------------------------
/layouts/ResponsiveBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Dimensions } from 'react-native';
3 |
4 |
5 | export const deviceIs = () => {
6 | var {width, height} = Dimensions.get('window');
7 | var device;
8 |
9 | if(width <= 520){
10 | device = "small";
11 | }
12 | else if(width <= 1024){
13 | device = "medium";
14 | }
15 | else if(width > 1024){
16 | device = "large";
17 | }
18 |
19 | return device;
20 |
21 | }
22 |
23 | export default class ResBox extends React.Component {
24 |
25 | constructor(props){
26 | super(props);
27 |
28 | this.state = {
29 | style:{},
30 | width:360,
31 | height:640
32 | }
33 | }
34 |
35 | style = {};
36 |
37 | hideBox(){
38 | var hidden = this.props.hidden ? this.props.hidden : '';
39 | var device = deviceIs();
40 |
41 | return hidden.indexOf(device) > -1;
42 | }
43 |
44 | static getScreen(){
45 | const { width, height } = Dimensions.get('window');
46 |
47 | return { width, height }
48 | }
49 |
50 | getDimensions(){
51 | const { width, height } = ResBox.getScreen();//Dimensions.get('window');
52 | this._setState({width, height});
53 | }
54 |
55 | resetBox(){
56 |
57 | //var def = this.state.style;
58 | this.getDimensions();
59 |
60 | //var { style, small, medium, large } = this.props;
61 |
62 | var style = this.props.style ? this.props.style : {};
63 | var small = this.props.small ? this.props.small : {};
64 | var medium = this.props.medium ? this.props.medium : {};
65 | var large = this.props.large ? this.props.large : {};
66 |
67 | var device = deviceIs();
68 |
69 | if(small && device === 'small'){
70 | //this._setState({style:{...style, ...small}});
71 | this.style ={...style, ...small};
72 | }
73 | else if(medium && device === 'medium'){
74 | //this._setState({style:{...style, ...medium}});
75 | this.style = {...style, ...medium};
76 | }
77 | else if(large && device === 'large'){
78 | //this._setState({style:{...style, ...large}});
79 | this.style = {...style, ...large};
80 | }else{
81 | //this._setState({style});
82 | this.style = {...style};
83 | }
84 |
85 | //console.log({device, def});
86 | }
87 |
88 | _setState(obj){
89 | if(this._isMounted){
90 | this.setState({...obj});
91 | }
92 | }
93 |
94 |
95 | componentWillMount() {
96 | this._isMounted = false;
97 |
98 | this.resetBox();
99 |
100 | Dimensions.addEventListener('change', ()=>this.resetBox());
101 |
102 | }
103 |
104 | componentWillUnmount() {
105 | this._isMounted = false;
106 | Dimensions.removeEventListener('change');
107 | }
108 |
109 | componentDidMount() {
110 | this._isMounted = true;
111 |
112 | this.resetBox();
113 | }
114 |
115 | render(){
116 |
117 | //const Comp = this.props.comp ? this.props.comp : View;
118 |
119 | const {children, style:s, small, medium, large, as:Comp=View, ...args} = this.props;
120 |
121 | var { //style,
122 | width, height} = this.state;
123 |
124 | var style = {...this.style};
125 |
126 | //var {maxWidth, maxHeight} = this.props;
127 |
128 | //console.log('before', style)
129 |
130 |
131 | if(style.pWidth){
132 | style.width = width * (style.pWidth/100);
133 | delete style.pWidth;
134 | }
135 |
136 | if(style.pHeight){
137 | style.height = height * (style.pHeight/100);
138 | delete style.pHeight;
139 | }
140 |
141 | if(style.pMargin){
142 | style.margin = width * (style.pMargin/100);
143 | delete style.pMargin;
144 | }
145 |
146 | if(style.pMarginRight){
147 | style.marginRight = width * (style.pMarginRight/100);
148 | delete style.pMarginRight;
149 | }
150 |
151 | if(style.pMarginLeft){
152 | style.marginLeft = width * (style.pMarginLeft/100);
153 | delete style.pMarginLeft;
154 | }
155 |
156 | if(style.pPadding){
157 | style.padding = width * (style.pPadding/100);
158 | delete style.pPadding;
159 | }
160 |
161 |
162 | if(this.hideBox()){
163 | return
164 | }else if(children){
165 | return (
166 |
167 | {children}
168 | )
169 | }else{
170 | //console.log('args', args)
171 | return
172 | }
173 |
174 |
175 | }
176 |
177 |
178 | }
--------------------------------------------------------------------------------
/redux/api/action.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const FETCH_API_REQUEST = 'FETCH_API_REQUEST';
4 | export const FETCH_API_SUCCESS = 'FETCH_API_SUCCESS';
5 | export const FETCH_API_FAILURE = 'FETCH_API_FAILURE';
6 | export const FETCH_API_DELETE = 'FETCH_API_DELETE';
7 | export const FETCH_API_CLEAR = 'FETCH_API_CLEAR';
8 | export const FETCH_API_ADD = 'FETCH_API_ADD';
9 | export const FETCH_API_PREPEND = 'FETCH_API_PREPEND';
10 | export const FETCH_API_EDIT = 'FETCH_API_EDIT';
11 |
12 |
13 | export const fetchApiEdit = (id,json) => (
14 | {
15 | "id":id,
16 | type: FETCH_API_EDIT,
17 | payload: json
18 | });
19 |
20 | export const fetchApiAdd = (id,json) => (
21 | {
22 | "id":id,
23 | type: FETCH_API_ADD,
24 | payload: json
25 | });
26 |
27 | export const fetchApiPrepend = (id,json) => (
28 | {
29 | "id":id,
30 | type: FETCH_API_PREPEND,
31 | payload: json
32 | });
33 |
34 |
35 | export const fetchApiClear = (id) => ({
36 | "id":id,
37 | type: FETCH_API_CLEAR});
38 |
39 |
40 | export const fetchApiRequest = (id) => ({
41 | id,
42 | type: FETCH_API_REQUEST});
43 |
44 | export const fetchApiSuccess = (id,json) => (
45 | {
46 | "id":id,
47 | type: FETCH_API_SUCCESS,
48 | payload: json
49 | });
50 |
51 | export const fetchApiFailure = (id,error) => (
52 | {
53 | "id":id,
54 | type: FETCH_API_FAILURE,
55 | payload: error
56 | });
57 |
58 |
59 | export const fetchApiDelete = (id,payload) => (
60 | {
61 | id,
62 | type: FETCH_API_DELETE,
63 | payload
64 | });
65 |
66 | const argsSerialize = (args)=> {
67 | var params = "";
68 | var i = 0;
69 |
70 | for(let key in args) {
71 |
72 | //add & to the string
73 | if(i>0){
74 | params+='&';
75 | }
76 |
77 | //check if key has a value
78 | if(args[key]===""){
79 | params+=key;
80 | }else{
81 | params+=key+'='+args[key];
82 | }
83 |
84 | i++;
85 | }
86 |
87 | return params;
88 |
89 | }
90 |
91 | export const clearApi = (id) => dispatch => dispatch(fetchApiClear(id));
92 |
93 | export const fetchApi = (url='',args={}, id='one') => {
94 |
95 | var params = argsSerialize(args);
96 |
97 | //async
98 | return dispatch => {
99 | var link = `${url}?${params}`;
100 | console.log(link);
101 |
102 | dispatch(fetchApiRequest(id));
103 |
104 | return fetch(link)
105 | .then(res => res.json()
106 | // , error => {
107 | // console.log('An error occurred.', error)
108 | // dispatch(fetchApiFailure(id, error.message))
109 | // }
110 | ).then(res => {
111 | //console.log(res);
112 | dispatch(fetchApiSuccess(id,res));
113 | return res;
114 | })
115 | .catch(error => {
116 | //console.log(error);
117 | dispatch(fetchApiFailure(id, error.message));
118 | return error;
119 | });
120 | }
121 | }
122 |
123 | export const postApi = (url='', obj={}, id="postApi") => {
124 |
125 | let headers = !!obj.headers ? obj.headers : {};
126 | let data = !!obj.data ? obj.data : {};
127 |
128 | return dispatch => {
129 |
130 | dispatch(fetchApiRequest(id));
131 |
132 | return axios.post(`${URL}${url}`, {...data}, {headers})
133 | .then(res=>{
134 |
135 | dispatch(fetchApiSuccess(id,res.data));
136 |
137 | return res;
138 | }).catch(error => {
139 | dispatch(fetchApiFailure(id, error.message));
140 | return error;
141 | });
142 |
143 | }
144 | }
145 |
146 | export const addApi = (id, json) => dispatch => dispatch(fetchApiAdd(id, json));
147 |
148 | export const prependApi = (id, json) => dispatch => dispatch(fetchApiPrepend(id, json));
149 |
150 | export const editApi = (id, json) => dispatch => dispatch(fetchApiEdit(id, json));
151 |
152 | export const deleteApi = (id, json) => dispatch => dispatch(fetchApiDelete(id, json));
153 |
154 |
155 | export const getApi = (url='', obj={}, id=null, cancel) => {
156 |
157 | //let headers = !!obj.headers ? obj.headers : {};
158 | //url = "https://andela.com/wp-json";
159 | //obj = {};
160 | // Add User-agent
161 | const headers = obj.headers || {};
162 | headers['User-Agent'] = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36`;
163 | headers['Sec-Fetch-Mode'] = 'cors';
164 |
165 | headers['accept'] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3";
166 | headers['accept-encoding'] = 'gzip, deflate, br';
167 | headers['accept-language'] = 'en-US,en;q=0.9';
168 | headers['cookie'] = "";
169 |
170 | let data = obj.data || obj;
171 |
172 | let params = argsSerialize(data);
173 |
174 | return dispatch => {
175 |
176 | id===null || dispatch(fetchApiRequest(id));
177 |
178 | //check for cancelToken
179 | let args = cancel ? {cancelToken: cancel.token} : {};
180 |
181 | //if(obj.headers){}
182 | args.headers = headers;
183 |
184 |
185 | //console.log(`${url}?${params}`,args)
186 |
187 |
188 | return axios.get(`${url}?${params}`, args)
189 | .then(res=>{
190 |
191 | id===null || dispatch(fetchApiSuccess(id,res.data));
192 | return res;
193 | }).catch(error => {
194 | id===null || dispatch(fetchApiFailure(id, error.message));
195 | return error;
196 | });
197 |
198 | }
199 | }
200 |
201 | export const cancelToken = () => {
202 | return dispatch =>{
203 | //create axios cancelToken
204 | var CancelToken = axios.CancelToken;
205 | var source = CancelToken.source();
206 | return source;
207 | }
208 |
209 | }
--------------------------------------------------------------------------------
/containers/WordPressApi.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | //import PropTypes from 'prop-types';
3 | //import {compose} from 'redux';
4 | import { connect } from 'react-redux';
5 |
6 | import {getApi, cancelToken} from '../redux/api/action';
7 | import set from '../redux/global-state';
8 |
9 |
10 | class WordpressApi extends Component {
11 |
12 | apiId = 'home';
13 | offset=0;
14 | per_page=1;
15 | //categories=0;
16 | //author=0;
17 | search='';
18 | orderby='date';
19 | order='desc';
20 | status='publish';
21 | //hide_empty=true
22 |
23 |
24 | constructor(props){
25 | this.props;
26 | }
27 |
28 | //here we will get id of all the posts that have been previously fetched in order to exclude the from future fetches
29 | getAvailablePostsId(){
30 | const {posts} = this.props;
31 |
32 | return new Promise((resolve)=>{
33 | if(posts && posts.data && Array.isArray(posts.data)){
34 | var ids = posts.data.map(post=>post.id);
35 | resolve(ids)
36 | }else{
37 | resolve([])
38 | }
39 |
40 |
41 | });
42 |
43 |
44 | }
45 |
46 | fetchMore(){
47 | let {per_page, orderby, order, apiId} = this;
48 | let {getApi, url} = this.props;
49 | var offset = this.props.api && this.props.api[apiId] && this.props.api[apiId].offset ? this.props.api[apiId].offset : 0;
50 | //console.log('static', this);
51 | getApi(`${url}/wp-json/wp/v2/posts`,{per_page, orderby, offset, order, _embed:''}, apiId)
52 | .then(res=>console.log(apiId, 'done fetching >', offset));
53 | }
54 |
55 | fetchCategories(obj={}){
56 | var {per_page=50, orderby='count', order='desc', hide_empty=true} = obj;
57 | let {getApi, url} = this.props;
58 | //console.log('static', this);
59 | return getApi(`${url}/wp-json/wp/v2/categories`,{per_page, orderby, order, hide_empty},'categories').then(res=>res.data);
60 | }
61 |
62 | fetchPosts(obj={}){
63 | let {per_page, orderby, order} = this;
64 | let {getApi, url} = this.props;
65 |
66 | //get already fetch posts
67 | return this.getAvailablePostsId().then(ids=>{
68 | //console.log(`getAvailablePostsId (${id.length})`, id.join())
69 | if(ids.length>0){
70 | obj.exclude = ids.join();
71 | }
72 |
73 | return getApi(`${url}/wp-json/wp/v2/posts`,{per_page, orderby, order, ...obj, _embed:''}, 'posts');
74 | });
75 | }
76 |
77 | getCategories(){
78 | const {categories} = this.props;
79 |
80 | //use from store if available
81 | if(categories){
82 | return categories.data;
83 | }
84 | else //fetch from rest api
85 | {
86 | return this.fetchCategories().then(res=>{
87 | console.log('done fetching categories');
88 | if(Array.isArray(res)){
89 | //return only root categories
90 | return res.filter(cat=>cat.parent===0)
91 | }else{
92 | return [];
93 | }
94 | });
95 |
96 |
97 | }
98 | }
99 |
100 |
101 | oneByOne(objects_array, iterator, callback) {
102 | var start_promise = objects_array.reduce(function (prom, object) {
103 | return prom.then(function () {
104 | return iterator(object);
105 | });
106 | }, Promise.resolve()); // initial
107 | if(callback){
108 | start_promise.then(callback);
109 | }else{
110 | return start_promise;
111 | }
112 | }
113 |
114 | fetchPostsByCategory(ids=[]){
115 |
116 | if(ids.length>0){
117 |
118 | ids.forEach(category =>{
119 | this.fetchPosts({category}).then(res=>console.log('done fetching: ',typeof res, category));
120 | });
121 |
122 |
123 | }else{
124 |
125 | var index = 0;
126 |
127 | this.getCategories().then(data=>{
128 | console.log('Home - ids', data.length);
129 |
130 |
131 | const fetchPostsIter = (categoryObj)=>{
132 |
133 | return new Promise((resolve)=>{
134 | let categories = categoryObj.id;
135 |
136 | return this.fetchPosts({categories})
137 | .then(res=>{
138 | console.log(`Home ${index} done fetching: `,categoryObj.id);
139 | if(res.data){
140 | console.log( res.data.length);
141 | }
142 | index++;
143 |
144 | resolve(res);
145 | });
146 | });
147 |
148 | }
149 |
150 | //fetch latests stories if available
151 | this.fetchPosts().then(res=>{
152 |
153 | let CategoryList = data.slice(0,5);
154 |
155 | this.oneByOne(CategoryList, fetchPostsIter, (res)=>console.log('done'));
156 |
157 |
158 | });
159 |
160 |
161 |
162 |
163 |
164 | });
165 |
166 |
167 |
168 | //.then(ids=>{
169 | // console.log('Home - ids', ids);
170 | //})
171 |
172 |
173 |
174 | }
175 |
176 | }
177 |
178 | generateHome(){
179 | const {gState} = this.props;
180 |
181 | //check if current_site exists
182 | if(gState.current_site){
183 | const index = gState.current_site;
184 | const config = gState.sites[index];
185 |
186 | if(config && config.home && config.home.categories && Array.isArray(config.home.categories)){
187 | const ids = config.home.categories.map(cat=>cat.id);
188 |
189 | this.fetchPostsByCategory(ids);
190 | }else{
191 | this.fetchPostsByCategory();
192 | console.log("Home - no config found")
193 | }
194 | }else{
195 | this.fetchPostsByCategory();
196 | console.log("Home - no config found")
197 | }
198 | }
199 |
200 |
201 |
202 | }
203 |
204 |
205 | const mapStateToProps = state => (
206 | {
207 | url: state.globalState.url,
208 | posts:state.api.posts,
209 | categories:state.api.categories,
210 | gState:state.globalState
211 |
212 |
213 | });
214 |
215 |
216 |
217 | export default (WordpressApi);
218 | //connect(mapStateToProps, {set, getApi, cancelToken})
219 |
--------------------------------------------------------------------------------