├── .babelrc ├── .env ├── .eslintrc.js ├── .gitignore ├── README.md ├── afterBody.php ├── cli ├── scripts │ └── componentGenerator.js └── templates │ └── component │ ├── Component.component.html │ ├── Component.component.js │ ├── Component.component.scss │ ├── Component.component.vue │ └── package.json ├── dist ├── bundle.js └── f5e09925eb0dc2c19ad0fc0c47e38e66.svg ├── functions.php ├── index.php ├── lib ├── alt-admin │ ├── README.md │ ├── alt-admin.php │ ├── css │ │ ├── admin-color-scheme.css │ │ ├── admin-footer.css │ │ └── admin-login.css │ └── images │ │ ├── admin-logo.png │ │ └── altdesign.svg ├── endpoints │ ├── addonEndpoints.php │ └── themeEndpoints.php ├── helpers.php └── setup.php ├── package.json ├── postcss.config.js ├── screenshot.jpg ├── server.js ├── src ├── assets │ └── scss │ │ ├── imports │ │ ├── _components.scss │ │ ├── _extends.scss │ │ ├── _fonts.scss │ │ ├── _forms.scss │ │ ├── _global.scss │ │ ├── _mixins.scss │ │ ├── _modifiers.scss │ │ └── _variables.scss │ │ └── main.scss ├── components │ ├── App │ │ ├── App.component.html │ │ ├── App.component.js │ │ ├── App.component.scss │ │ ├── App.component.vue │ │ └── package.json │ ├── Breadcrumbs │ │ ├── Breadcrumbs.component.html │ │ ├── Breadcrumbs.component.js │ │ ├── Breadcrumbs.component.scss │ │ ├── Breadcrumbs.component.vue │ │ └── package.json │ ├── Footer │ │ ├── Footer.component.html │ │ ├── Footer.component.js │ │ ├── Footer.component.scss │ │ ├── Footer.component.vue │ │ └── package.json │ ├── Header │ │ ├── Header.component.html │ │ ├── Header.component.js │ │ ├── Header.component.scss │ │ ├── Header.component.vue │ │ └── package.json │ ├── Menus │ │ ├── AppMenu │ │ │ ├── AppMenu.component.html │ │ │ ├── AppMenu.component.js │ │ │ ├── AppMenu.component.vue │ │ │ └── package.json │ │ └── AppMenuItem │ │ │ ├── AppMenuItem.component.html │ │ │ ├── AppMenuItem.component.js │ │ │ ├── AppMenuItem.component.vue │ │ │ └── package.json │ ├── PageBuilder │ │ ├── PageBuilder.component.html │ │ ├── PageBuilder.component.js │ │ ├── PageBuilder.component.scss │ │ ├── PageBuilder.component.vue │ │ └── package.json │ └── Views │ │ └── Page │ │ ├── Page.component.html │ │ ├── Page.component.js │ │ ├── Page.component.scss │ │ ├── Page.component.vue │ │ └── package.json ├── imports │ ├── components.js │ ├── functions.js │ └── ready.js ├── main.js ├── router │ ├── router.js │ └── routes.js └── vuex │ ├── actions.js │ ├── getters.js │ ├── mutations.js │ ├── state.js │ └── store.js ├── static └── img │ └── logo.svg ├── style.css ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-decorators-legacy", 4 | "transform-runtime" 5 | ], 6 | "presets": [ 7 | [ 8 | "es2015", 9 | { 10 | "modules": false 11 | } 12 | ], 13 | "stage-0" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ENTRY=main.js 2 | OUTPUT=bundle.js 3 | DEV_URL=http://theme.dev/ 4 | PORT=8080 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': 'standard', 3 | 'plugins': [ 4 | 'standard', 5 | 'promise', 6 | 'html' 7 | ], 8 | 'env': { 9 | 'browser': true, 10 | 'es6': true, 11 | 'node': true, 12 | 'mocha': true 13 | }, 14 | 'rules': { 15 | 'no-new': 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | webpackTemp.html 4 | *~ 5 | *.swp 6 | *.swo 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress Single Page Application Theme Boilerplate 2 | ##### A JavaScript SPA WordPress theme boilerplate using Vue and the WordPress REST API. 3 | 4 | 5 | 6 | 7 | 8 | JS Standard Style 9 | 10 | 11 | 12 | ## Install 13 | Install this theme just as you would install any modern theme boilerplate: 14 | 15 | 1. Navigate to your themes folder (usually /wp-content/themes/): 16 | 17 | cd /wp-content/themes/ 18 | 19 | 2. Clone this repo: 20 | 21 | git clone https://github.com/alt-design/wp-spa-boilerplate NAME_OF_DIRECTORY 22 | 23 | 3. Enter the new folder (NAME_OF_DIRECTORY): 24 | 25 | cd NAME_OF_DIRECTORY 26 | 27 | 4. Install dependencies via npm or yarn: 28 | 29 | npm install 30 | or 31 | 32 | yarn 33 | 34 | ## Running and Building 35 | 36 | npm run dev // starts a hot reload dev server (make sure to update your development URL in the .env file before running this) 37 | npm run watch // watches and compiles files to the dist folder, no hot reload though 38 | npm run build // compiles to the dist folder 39 | npm run production // optimises and compresses before compiling to the dist folder (this will disable Vue Devtools) 40 | 41 | ## VueX and Data Structure 42 | All of your pages data is kept in a VueX store, by default the store will look like this: 43 | 44 | { 45 | adminUrl:"http://theme.dev/wp/wp-admin/" 46 | global:false // Global ACF Fields (Options Pages) 47 | theme:"http://theme.dev/app/themes/vue-theme" 48 | name:"Test" 49 | post:Object 50 | post_name:"sample-page" 51 | ID:2 52 | acf:false // ACF Fields 53 | breadcrumbs:Array[2] 54 | 0:Object 55 | permalink:"http://theme.dev/" 56 | title:"Home" 57 | 1:Object 58 | permalink:"http://theme.dev/sample-page/" 59 | title:"Sample Page" 60 | featured_image:null 61 | post_content:"This is an example page..." 62 | post_date:"2017-03-24 12:26:39" 63 | post_excerpt:"" 64 | post_parent:0 65 | post_title:"Sample Page" 66 | post_type:"page" 67 | url:"http://theme.dev" 68 | } 69 | 70 | ## Components 71 | 72 | ### Menu 73 | The menu component is used to retrieve & display WordPress menus: 74 | 75 | 76 | 77 | ##### Props 78 | 79 | - location : string (required) - the menu's registered theme location 80 | - emitOnComplete : string (optional) - an optional Vue.$emit to be called after the menu is loaded 81 | 82 | 83 | ### Breadcrumbs 84 | The breadcrumb component is used to retrieve & display breadcrumbs for the current path: 85 | 86 | 87 | -------------------------------------------------------------------------------- /afterBody.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /cli/scripts/componentGenerator.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const _ = require('lodash') 3 | 4 | const ComponentGenerator = { 5 | 6 | buildItems (args) { 7 | let returnItems = args.splice(2, Infinity) 8 | 9 | for (let [index, currentItem] of returnItems.entries()) { 10 | // Makes array from file path, e.g. '1/2/3' -> [1,2,3] 11 | let itemSplit = currentItem.split('/') 12 | 13 | returnItems[index] = { 14 | // Adds the last array item of itemSplit (the name of the component to be generated) 15 | 'name': itemSplit[itemSplit.length - 1], 16 | 17 | // Adds all the itemSplit array items which are not last (the parent directory/ies) 18 | 'path': './src/components/' + itemSplit.join('/') || '' 19 | } 20 | 21 | // Creates all of the folders 22 | this.generateDirectories(itemSplit) 23 | } 24 | 25 | return returnItems // Returns the modified array of items and their parent directories 26 | }, 27 | 28 | generateDirectories (items) { 29 | let tempDir = './src/components' 30 | for (let folder of items) this.createDir(tempDir += '/' + folder) 31 | console.log(`> Component Directory Generated @ ${tempDir}`) 32 | }, 33 | 34 | createDir (newDir = false) { 35 | if (!fs.existsSync(newDir)) fs.mkdirSync(newDir, '0766') 36 | }, 37 | 38 | createComponents (names) { 39 | for (let name of names) { 40 | this.copyTemplates(name['path'], name['name']) 41 | } 42 | }, 43 | 44 | copyTemplates (dir, name) { 45 | console.log('-------------------------') 46 | console.log('Creating Files') 47 | console.log('-------------------------') 48 | 49 | const fileTypes = ['.component.vue', '.component.js', '.component.html', '.component.scss'] 50 | 51 | for (let file of fileTypes) { 52 | this.copySyncFile(`./CLI/Templates/Component/Component${file}`, `${dir}/${name}${file}`) 53 | this.replaceText(`${dir}/${name}${file}`, name) 54 | 55 | console.log(`> File "${dir}/${name}${file}" created`) 56 | } 57 | 58 | this.copySyncFile(`./CLI/Templates/Component/package.json`, `${dir}/package.json`) 59 | this.replaceText(`${dir}/package.json`, name) 60 | console.log(`> File "${dir}/package.json" created`) 61 | }, 62 | 63 | replaceText (file, name) { 64 | fs.readFile(file, 'utf8', (err, data) => { 65 | if (err) return console.log(err) 66 | let result = data 67 | .replace(/ComponentName/g, name) 68 | .replace(/component-name-kebab/g, _.kebabCase(name)) 69 | 70 | fs.writeFile(file, result, 'utf8', function (err) { 71 | if (err) return console.log(err) 72 | }) 73 | }) 74 | }, 75 | 76 | copySyncFile (src, dest) { 77 | fs.writeFileSync(dest, fs.readFileSync(src)) 78 | }, 79 | 80 | init () { 81 | console.log('> Component Generator Initiated') 82 | this.createComponents(this.buildItems(process.argv)) 83 | } 84 | 85 | } 86 | 87 | ComponentGenerator.init() 88 | -------------------------------------------------------------------------------- /cli/templates/component/Component.component.html: -------------------------------------------------------------------------------- 1 |
2 | This is the {{message}} component 3 |
4 | -------------------------------------------------------------------------------- /cli/templates/component/Component.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ComponentName 3 | * 4 | * @template : ./ComponentName.component.html 5 | * @style : ./ComponentName.component.scss 6 | */ 7 | 8 | export default { 9 | data () { 10 | return { 11 | message: 'ComponentName' 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cli/templates/component/Component.component.scss: -------------------------------------------------------------------------------- 1 | .component-name-kebab { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /cli/templates/component/Component.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /cli/templates/component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-name-kebab", 3 | "version": "1.0.0", 4 | "main": "ComponentName.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /dist/f5e09925eb0dc2c19ad0fc0c47e38e66.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | > 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | > 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | base, array('profile', 'profile-network')) ) { 91 | $wp_styles->registered['colors']->deps[] = 'colors-fresh'; 92 | } 93 | } 94 | 95 | /** 96 | * Single column dashboard 97 | */ 98 | function altScreenLayoutColumns($columns) 99 | { 100 | $columns['dashboard'] = 1; 101 | return $columns; 102 | } 103 | 104 | function altScreenLayoutDashboard() 105 | { 106 | return 1; 107 | } 108 | 109 | /** 110 | * Remove WordPress logo from admin bar 111 | */ 112 | function altRemoveWpLogo() 113 | { 114 | global $wp_admin_bar; 115 | $wp_admin_bar->remove_menu('wp-logo'); 116 | } 117 | 118 | /** 119 | * Replace 'Howdy' message. 120 | */ 121 | function altReplaceHowdy($wp_admin_bar) 122 | { 123 | $my_account = $wp_admin_bar->get_node('my-account'); 124 | $newtitle = str_replace('How are you,', 'Ay'up', $my_account->title); 125 | $wp_admin_bar->add_node(array( 126 | 'id' => 'my-account', 127 | 'title' => $newtitle, 128 | )); 129 | } 130 | 131 | /** 132 | * Custom admin footer 133 | */ 134 | function altCustomAdminFooter() 135 | { 136 | echo '' . get_bloginfo('name') . ', a site by Alt'; 137 | } 138 | 139 | /** 140 | * Custom admin footer version 141 | */ 142 | function altChangeFooterVersion() 143 | { 144 | return ''; 145 | } 146 | 147 | } 148 | 149 | new AltAdminBranding(); -------------------------------------------------------------------------------- /lib/alt-admin/css/admin-color-scheme.css: -------------------------------------------------------------------------------- 1 | html { 2 | background: #f3f3f3; 3 | } 4 | 5 | /****************************** LINKS ******************************/ 6 | a { 7 | color: #0074a2; 8 | } 9 | a:hover, a:active, a:focus { 10 | color: #0099d5; 11 | } 12 | #rightnow a:hover, 13 | #media-upload a.del-link:hover, 14 | div.dashboard-widget-submit input:hover, 15 | .subsubsub a:hover, 16 | .subsubsub a.current:hover, 17 | .ui-tabs-nav a:hover { 18 | color: #0099d5; 19 | } 20 | 21 | /****************************** FORMS ******************************/ 22 | input[type=checkbox]:checked:before { 23 | color: #FF6600; 24 | } 25 | input[type=radio]:checked:before { 26 | background: #FF6600; 27 | } 28 | .wp-core-ui input[type="reset"]:hover, 29 | .wp-core-ui input[type="reset"]:active { 30 | color: #0099d5; 31 | } 32 | 33 | /*************************** LIST TABLES ***************************/ 34 | .wp-core-ui { 35 | } 36 | .wp-core-ui .button-primary { 37 | background: #FF6600; 38 | border-color: #cc5200; 39 | color: white; 40 | -webkit-box-shadow: inset 0 1px 0 #ff944d, 0 1px 0 rgba(0, 0, 0, 0.15); 41 | box-shadow: inset 0 1px 0 #ff944d, 0 1px 0 rgba(0, 0, 0, 0.15); 42 | } 43 | .wp-core-ui .button-primary:hover, 44 | .wp-core-ui .button-primary:focus { 45 | background: #d65600; 46 | border-color: #b34700; 47 | color: white; 48 | -webkit-box-shadow: inset 0 1px 0 #ff8533, 0 1px 0 rgba(0, 0, 0, 0.15); 49 | box-shadow: inset 0 1px 0 #ff8533, 0 1px 0 rgba(0, 0, 0, 0.15); 50 | } 51 | .wp-core-ui .button-primary:active { 52 | background: #d65600; 53 | border-color: #b34700; 54 | color: white; 55 | -webkit-box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5); 56 | box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5); 57 | } 58 | .wp-core-ui .button-primary[disabled], 59 | .wp-core-ui .button-primary:disabled, 60 | .wp-core-ui .button-primary.button-primary-disabled { 61 | color: #d1cbc7 !important; 62 | background: #d65600 !important; 63 | border-color: #b34700 !important; 64 | text-shadow: none !important; 65 | } 66 | .wp-core-ui .wp-ui-primary { 67 | color: #FFFFFF; 68 | background-color: #363b3f; 69 | } 70 | .wp-core-ui .wp-ui-text-primary { 71 | color: #363b3f; 72 | } 73 | .wp-core-ui .wp-ui-highlight { 74 | color: #FFFFFF; 75 | background-color: #FF6600; 76 | } 77 | .wp-core-ui .wp-ui-text-highlight { 78 | color: #FF6600; 79 | } 80 | .wp-core-ui .wp-ui-notification { 81 | color: #FFFFFF; 82 | background-color: #25282b; 83 | } 84 | .wp-core-ui .wp-ui-text-notification { 85 | color: #25282b; 86 | } 87 | .wp-core-ui .wp-ui-text-icon { 88 | color: #f1f2f3; 89 | } 90 | .wrap .add-new-h2:hover, 91 | #add-new-comment a:hover, 92 | .tablenav .tablenav-pages a:hover, 93 | .tablenav .tablenav-pages a:focus { 94 | color: #FFFFFF; 95 | background-color: #363b3f; 96 | } 97 | .view-switch a.current:before { 98 | color: #363b3f; 99 | } 100 | .view-switch a:hover:before { 101 | color: #25282b; 102 | } 103 | .post-com-count:hover:after { 104 | border-top-color: #363b3f; 105 | } 106 | .post-com-count:hover span { 107 | color: #FFFFFF; 108 | background-color: #363b3f; 109 | } 110 | strong .post-com-count:after { 111 | border-top-color: #25282b; 112 | } 113 | strong .post-com-count span { 114 | background-color: #25282b; 115 | } 116 | 117 | /*************************** ADMIN MENU ****************************/ 118 | #adminmenuback, 119 | #adminmenuwrap, 120 | #adminmenu { 121 | background: #363b3f; 122 | } 123 | #adminmenu a { 124 | color: #FFFFFF; 125 | } 126 | #adminmenu div.wp-menu-image:before { 127 | color: #f1f2f3; 128 | } 129 | #adminmenu a:hover, 130 | #adminmenu li.menu-top:hover, 131 | #adminmenu li.opensub > a.menu-top, 132 | #adminmenu li > a.menu-top:focus { 133 | color: #FFFFFF; 134 | background-color: #FF6600; 135 | } 136 | #adminmenu li.menu-top:hover div.wp-menu-image:before, 137 | #adminmenu li.opensub > a.menu-top div.wp-menu-image:before { 138 | color: #FFFFFF; 139 | } 140 | .about-wrap h2 .nav-tab-active, 141 | .nav-tab-active, 142 | .nav-tab-active:hover { 143 | border-bottom-color: #f3f3f3; 144 | } 145 | 146 | /********************** ADMIN MENU (SUB MENU) **********************/ 147 | #adminmenu .wp-submenu, 148 | #adminmenu .wp-has-current-submenu .wp-submenu, 149 | #adminmenu .wp-has-current-submenu.opensub .wp-submenu, 150 | .folded #adminmenu .wp-has-current-submenu .wp-submenu, 151 | #adminmenu a.wp-has-current-submenu:focus + .wp-submenu { 152 | background: #26292c; 153 | } 154 | #adminmenu li.wp-has-submenu.wp-not-current-submenu.opensub:hover:after { 155 | border-right-color: #26292c; 156 | } 157 | #adminmenu .wp-submenu .wp-submenu-head { 158 | color: #c3c4c5; 159 | } 160 | #adminmenu .wp-submenu a, 161 | #adminmenu .wp-has-current-submenu .wp-submenu a, 162 | .folded #adminmenu .wp-has-current-submenu .wp-submenu a, 163 | #adminmenu a.wp-has-current-submenu:focus + .wp-submenu a, 164 | #adminmenu .wp-has-current-submenu.opensub .wp-submenu a { 165 | color: #c3c4c5; 166 | } 167 | 168 | /******************** ADMIN MENU (CURRENT MENU) ********************/ 169 | #adminmenu .wp-submenu a:focus, 170 | #adminmenu .wp-has-current-submenu .wp-submenu a:focus, 171 | .folded #adminmenu .wp-has-current-submenu .wp-submenu a:focus, 172 | #adminmenu a.wp-has-current-submenu:focus + .wp-submenu a:focus, 173 | #adminmenu .wp-has-current-submenu.opensub .wp-submenu a:focus, 174 | #adminmenu .wp-submenu a:hover, 175 | #adminmenu .wp-has-current-submenu .wp-submenu a:hover, 176 | .folded #adminmenu .wp-has-current-submenu .wp-submenu a:hover, 177 | #adminmenu a.wp-has-current-submenu:focus + .wp-submenu a:hover, 178 | #adminmenu .wp-has-current-submenu.opensub .wp-submenu a:hover { 179 | color: #FF6600; 180 | } 181 | #adminmenu .wp-submenu li.current a, 182 | #adminmenu a.wp-has-current-submenu:focus + .wp-submenu li.current a, 183 | #adminmenu .wp-has-current-submenu.opensub .wp-submenu li.current a { 184 | color: #FFFFFF; 185 | } 186 | #adminmenu .wp-submenu li.current a:hover, 187 | #adminmenu a.wp-has-current-submenu:focus + .wp-submenu li.current a:hover, 188 | #adminmenu .wp-has-current-submenu.opensub .wp-submenu li.current a:hover, 189 | #adminmenu .wp-submenu li.current a:focus, 190 | #adminmenu a.wp-has-current-submenu:focus + .wp-submenu li.current a:focus, 191 | #adminmenu .wp-has-current-submenu.opensub .wp-submenu li.current a:focus { 192 | color: #FF6600; 193 | } 194 | ul#adminmenu a.wp-has-current-submenu:after, 195 | ul#adminmenu > li.current > a.current:after { 196 | border-right-color: #f3f3f3; 197 | } 198 | #adminmenu li.current a.menu-top, 199 | #adminmenu li.wp-has-current-submenu a.wp-has-current-submenu, 200 | #adminmenu li.wp-has-current-submenu .wp-submenu .wp-submenu-head, 201 | .folded #adminmenu li.current.menu-top { 202 | color: #FFFFFF; 203 | background: #FF6600; 204 | } 205 | #adminmenu li.wp-has-current-submenu div.wp-menu-image:before { 206 | color: #FFFFFF; 207 | } 208 | #adminmenu .awaiting-mod, 209 | #adminmenu .update-plugins { 210 | color: #FFFFFF; 211 | background: #25282b; 212 | } 213 | #adminmenu li.current a .awaiting-mod, 214 | #adminmenu li a.wp-has-current-submenu .update-plugins, 215 | #adminmenu li:hover a .awaiting-mod, 216 | #adminmenu li.menu-top:hover > a .update-plugins { 217 | color: #FFFFFF; 218 | background: #26292c; 219 | } 220 | 221 | /******************** ADMIN MENU (COLLAPSE MENU) *******************/ 222 | #collapse-menu { 223 | color: #f1f2f3; 224 | } 225 | #collapse-menu:hover { 226 | color: #FFFFFF; 227 | } 228 | #collapse-button div:after { 229 | color: #f1f2f3; 230 | } 231 | #collapse-menu:hover #collapse-button div:after { 232 | color: #FFFFFF; 233 | } 234 | 235 | /**************************** ADMIN BAR ****************************/ 236 | #wpadminbar { 237 | color: #FFFFFF; 238 | background: #FF6600; 239 | } 240 | #wpadminbar .ab-item, 241 | #wpadminbar a.ab-item, 242 | #wpadminbar > #wp-toolbar span.ab-label, 243 | #wpadminbar > #wp-toolbar span.noticon { 244 | color: #FFFFFF; 245 | } 246 | #wpadminbar .ab-icon, 247 | #wpadminbar .ab-icon:before, 248 | #wpadminbar .ab-item:before, 249 | #wpadminbar .ab-item:after { 250 | color: #f1f2f3; 251 | } 252 | #wpadminbar .ab-top-menu > li:hover > .ab-item, 253 | #wpadminbar .ab-top-menu > li.hover > .ab-item, 254 | #wpadminbar .ab-top-menu > li > .ab-item:focus, 255 | #wpadminbar.nojq .quicklinks .ab-top-menu > li > .ab-item:focus, 256 | #wpadminbar-nojs .ab-top-menu > li.menupop:hover > .ab-item, 257 | #wpadminbar .ab-top-menu > li.menupop.hover > .ab-item { 258 | color: #FFFFFF; 259 | background: #26292c; 260 | } 261 | #wpadminbar > #wp-toolbar li:hover span.ab-label, 262 | #wpadminbar > #wp-toolbar li.hover span.ab-label, 263 | #wpadminbar > #wp-toolbar a:focus span.ab-label { 264 | color: #FFFFFF; 265 | } 266 | #wpadminbar li:hover .ab-icon:before, 267 | #wpadminbar li:hover .ab-item:before, 268 | #wpadminbar li:hover .ab-item:after, 269 | #wpadminbar li:hover #adminbarsearch:before { 270 | color: #FFFFFF; 271 | } 272 | 273 | /*********************** ADMIN BAR (SUBMENU) ***********************/ 274 | #wpadminbar .menupop .ab-sub-wrapper { 275 | background: #26292c; 276 | } 277 | #wpadminbar .quicklinks .menupop ul.ab-sub-secondary, 278 | #wpadminbar .quicklinks .menupop ul.ab-sub-secondary .ab-submenu { 279 | background: #4c4c4d; 280 | } 281 | #wpadminbar .ab-submenu .ab-item, 282 | #wpadminbar .quicklinks .menupop ul li a, 283 | #wpadminbar .quicklinks .menupop.hover ul li a, 284 | #wpadminbar-nojs .quicklinks .menupop:hover ul li a { 285 | color: #c3c4c5; 286 | } 287 | #wpadminbar .quicklinks li .blavatar, 288 | #wpadminbar .menupop .menupop > .ab-item:before { 289 | color: #f1f2f3; 290 | } 291 | #wpadminbar .quicklinks .menupop ul li a:hover, 292 | #wpadminbar .quicklinks .menupop ul li a:focus, 293 | #wpadminbar .quicklinks .menupop ul li a:hover strong, 294 | #wpadminbar .quicklinks .menupop ul li a:focus strong, 295 | #wpadminbar .quicklinks .menupop.hover ul li a:hover, 296 | #wpadminbar .quicklinks .menupop.hover ul li a:focus, 297 | #wpadminbar.nojs .quicklinks .menupop:hover ul li a:hover, 298 | #wpadminbar.nojs .quicklinks .menupop:hover ul li a:focus, 299 | #wpadminbar li:hover .ab-icon:before, 300 | #wpadminbar li:hover .ab-item:before, 301 | #wpadminbar li a:focus .ab-icon:before, 302 | #wpadminbar li .ab-item:focus:before, 303 | #wpadminbar li.hover .ab-icon:before, 304 | #wpadminbar li.hover .ab-item:before, 305 | #wpadminbar li:hover .ab-item:after, 306 | #wpadminbar li.hover .ab-item:after, 307 | #wpadminbar li:hover #adminbarsearch:before { 308 | color: #FFFFFF; 309 | } 310 | #wpadminbar .quicklinks li a:hover .blavatar, 311 | #wpadminbar .menupop .menupop > .ab-item:hover:before { 312 | color: #FFFFFF; 313 | } 314 | 315 | /*********************** ADMIN BAR (SEARCH) ************************/ 316 | #wpadminbar #adminbarsearch:before { 317 | color: #f1f2f3; 318 | } 319 | #wpadminbar > #wp-toolbar > #wp-admin-bar-top-secondary > #wp-admin-bar-search #adminbarsearch input.adminbar-input:focus { 320 | color: #FFFFFF; 321 | background: #464d52; 322 | } 323 | #wpadminbar #adminbarsearch .adminbar-input::-webkit-input-placeholder { 324 | color: #FFFFFF; 325 | opacity: 0.7; 326 | } 327 | #wpadminbar #adminbarsearch .adminbar-input:-moz-placeholder { 328 | color: #FFFFFF; 329 | opacity: 0.7; 330 | } 331 | #wpadminbar #adminbarsearch .adminbar-input::-moz-placeholder { 332 | color: #FFFFFF; 333 | opacity: 0.7; 334 | } 335 | #wpadminbar #adminbarsearch .adminbar-input:-ms-input-placeholder { 336 | color: #FFFFFF; 337 | opacity: 0.7; 338 | } 339 | 340 | /********************* ADMIN BAR (MY ACCOUNT) **********************/ 341 | #wpadminbar .quicklinks li#wp-admin-bar-my-account.with-avatar > a img { 342 | border-color: #464d52; 343 | background-color: #464d52; 344 | } 345 | #wpadminbar #wp-admin-bar-user-info .display-name { 346 | color: #FFFFFF; 347 | } 348 | #wpadminbar #wp-admin-bar-user-info a:hover .display-name { 349 | color: #FF6600; 350 | } 351 | #wpadminbar #wp-admin-bar-user-info .username { 352 | color: #c3c4c5; 353 | } 354 | 355 | /***************************** POINTERS ****************************/ 356 | .wp-pointer .wp-pointer-content h3 { 357 | background-color: #FF6600; 358 | } 359 | .wp-pointer .wp-pointer-content h3:before { 360 | color: #FF6600; 361 | } 362 | .wp-pointer.wp-pointer-top .wp-pointer-arrow, 363 | .wp-pointer.wp-pointer-undefined .wp-pointer-arrow { 364 | border-bottom-color: #FF6600; 365 | } 366 | 367 | /************************** MEDIA UPLOADER *************************/ 368 | .media-item .bar, 369 | .media-progress-bar div { 370 | background-color: #FF6600; 371 | } 372 | .details.attachment { 373 | box-shadow: 0 0 0 1px #fff, 0 0 0 5px #FF6600; 374 | } 375 | .attachment.details .check { 376 | background-color: #FF6600; 377 | box-shadow: 0 0 0 1px #fff, 0 0 0 2px #FF6600; 378 | } 379 | 380 | /****************************** THEMES *****************************/ 381 | .theme-browser .theme.active .theme-name, 382 | .theme-browser .theme.add-new-theme:hover:after { 383 | background: #FF6600; 384 | } 385 | .theme-browser .theme.add-new-theme:hover span:after { 386 | color: #FF6600; 387 | } 388 | .theme-overlay .theme-header .close:hover, 389 | .theme-overlay .theme-header .right:hover, 390 | .theme-overlay .theme-header .left:hover { 391 | background: #FF6600; 392 | } 393 | 394 | /************************* JQUERY UI SLIDER ************************/ 395 | .wp-slider .ui-slider-handle, 396 | .wp-slider .ui-slider-handle.ui-state-hover, 397 | .wp-slider .ui-slider-handle.focus { 398 | background: #FF6600; 399 | border-color: #cc5200; 400 | -webkit-box-shadow: inset 0 1px 0 #ff944d, 0 1px 0 rgba(0, 0, 0, 0.15); 401 | box-shadow: inset 0 1px 0 #ff944d, 0 1px 0 rgba(0, 0, 0, 0.15); 402 | } 403 | 404 | /************************ RESPONSIVE TOGGLE ************************/ 405 | div#wp-responsive-toggle a:before { 406 | color: #f1f2f3; 407 | } 408 | .wp-responsive-open div#wp-responsive-toggle a { 409 | border-color: transparent; 410 | background: #FF6600; 411 | } 412 | .star-rating .star { 413 | color: #FF6600; 414 | } 415 | .wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle a { 416 | background: #26292c; 417 | } -------------------------------------------------------------------------------- /lib/alt-admin/css/admin-footer.css: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | ADMIN FOOTER 4 | 5 | **********************************************************************/ 6 | 7 | #wpfooter p { 8 | height: 30px; 9 | line-height: 30px; 10 | margin-bottom: 10px; 11 | } 12 | 13 | #wpfooter #footer-upgrade .alt-footer-logo { 14 | background-image: url('../images/altdesign.svg') !important; 15 | background-repeat: no-repeat; 16 | background-size: cover; 17 | display: block; 18 | height: 30px; 19 | outline: none; 20 | overflow: hidden; 21 | text-indent: 9999px; 22 | width: 51px; 23 | } -------------------------------------------------------------------------------- /lib/alt-admin/css/admin-login.css: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | ADMIN LOGIN 4 | 5 | **********************************************************************/ 6 | 7 | h1 a { 8 | background-image: url('../images/admin-logo.png') !important; 9 | background-size: 320px 110px !important; 10 | height: 110px !important; 11 | width: 320px !important; 12 | } 13 | 14 | .login #nav a, 15 | .login #backtoblog a, 16 | .login .mysp-login-extras a { 17 | color: #333333 !important; 18 | text-shadow: 0 1px 0 #FFFFFF; 19 | } 20 | 21 | .login #nav a:hover, 22 | .login #backtoblog a:hover, 23 | .login .mysp-login-extras a:hover { 24 | color: #FF6600 !important; 25 | text-shadow: 0 1px 0 #FFFFFF; 26 | } 27 | 28 | .wp-core-ui .button-primary { 29 | background: #FF6600 !important; 30 | border-color: #E45A00 !important; 31 | -webkit-box-shadow: inset 0 1px 0 #FF9335, 0 1px 0 rgba(0, 0, 0, .15) !important; 32 | box-shadow: inset 0 1px 0 #FF9335, 0 1px 0 rgba(0, 0, 0, .15) !important; 33 | } 34 | 35 | .wp-core-ui .button-primary.focus, 36 | .wp-core-ui .button-primary.hover, 37 | .wp-core-ui .button-primary:focus, 38 | .wp-core-ui .button-primary:hover { 39 | background: #E45A00 !important; 40 | border-color: #E45A00 !important; 41 | -webkit-box-shadow: inset 0 1px 0 #EE8C45 !important; 42 | box-shadow: inset 0 1px 0 #EE8C45 !important; 43 | } 44 | 45 | #wp-submit { 46 | text-shadow: none !important; 47 | } 48 | -------------------------------------------------------------------------------- /lib/alt-admin/images/admin-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-design/wp-spa-boilerplate/bde4eceabd4d064ef20c0a845a73b84003f086e9/lib/alt-admin/images/admin-logo.png -------------------------------------------------------------------------------- /lib/alt-admin/images/altdesign.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/endpoints/addonEndpoints.php: -------------------------------------------------------------------------------- 1 | 'GET', 13 | // 'callback' => ['SomeClass', 'someMethod'], 14 | // 'args' => [ 15 | // 'some-param' => [ 16 | // 'default' => false 17 | // ] 18 | // ] 19 | // ]); 20 | //}); -------------------------------------------------------------------------------- /lib/endpoints/themeEndpoints.php: -------------------------------------------------------------------------------- 1 | $tax['name'], 72 | 'terms' => wp_get_post_terms($id, $tax['name'], ['fields' => 'names']) 73 | ]; 74 | 75 | } 76 | 77 | return $returnData; 78 | } 79 | 80 | 81 | /** 82 | * Return all posts from any post type 83 | * 84 | * @param $data 85 | * 86 | */ 87 | public static function getArchiveData($data) 88 | { 89 | 90 | $endpoints = new AltThemeEndpoints(); 91 | $posts = []; 92 | $taxonomies = []; 93 | $postIds = get_posts([ 94 | 'fields' => 'ids', 95 | 'post_type' => $data['post_type'], 96 | 'showposts' => $data['amount'] 97 | ]); 98 | $ignoredTaxonomies = [ 99 | 'post_tag', 100 | 'post_format' 101 | ]; 102 | $ignoredTerms = [ 103 | // 'Uncategorized', 104 | // 'Uncategorised' 105 | ]; 106 | 107 | foreach (get_object_taxonomies($data['post_type']) as $tax) { 108 | 109 | if (!in_array($tax, $ignoredTaxonomies)) { 110 | 111 | $terms = []; 112 | 113 | // exclude ignored terms 114 | foreach (get_terms(['fields' => 'names', 'taxonomy' => $tax]) as $term) { 115 | !in_array($term, $ignoredTerms) ? $terms[] = $term : null; 116 | } 117 | 118 | $taxonomies[] = [ 119 | 'name' => $tax, 120 | 'terms' => $terms 121 | ]; 122 | 123 | } 124 | 125 | } 126 | 127 | foreach ($postIds as $postId) { 128 | $posts[] = [ 129 | 'title' => get_the_title($postId), 130 | 'date' => get_the_date('d F', $postId), 131 | 'link' => get_the_permalink($postId), 132 | 'img' => $endpoints->getFeaturedImg($postId), 133 | 'taxonomies' => AltThemeEndpoints::getPostTaxTerm($postId, $taxonomies), 134 | 'acf' => $data['acf'] ? get_field_objects($postId) : false 135 | ]; 136 | } 137 | 138 | echo helpers::altJsonEncode(['taxonomies' => $taxonomies, 'posts' => $posts]); 139 | 140 | die(); 141 | } 142 | 143 | 144 | /** 145 | * Return Featured Image Array 146 | * 147 | * @param $data 148 | * 149 | */ 150 | public function returnFeaturedImg($data) 151 | { 152 | 153 | var_dump(AltThemeEndpoints::getFeaturedImg($data['id'])); 154 | die(); 155 | } 156 | 157 | 158 | /** 159 | * Collect and return the data for a post from is ID 160 | * 161 | * @param $id 162 | * 163 | * @return array|null|string|WP_Post 164 | */ 165 | public function getPostData($id, $isPreview) 166 | { 167 | // Get WordPress post object or post revision object 168 | if (!$isPreview) { 169 | $returnData = get_post($id) ?? 'No post found with that ID'; 170 | } else { 171 | // Get array of post revisions for this ID 172 | $revisions = wp_get_post_revisions($id); 173 | 174 | if (count($revisions)) { 175 | // Get the post data for the latest revision 176 | $revisionsValues = array_values($revisions); 177 | $returnData = array_shift($revisionsValues); 178 | 179 | // Use the latest revisions ID from now on 180 | $id = $returnData->ID; 181 | } else { 182 | $returnData = 'No post revisions found for that ID. Has the post type got revisions enabled?'; 183 | } 184 | } 185 | 186 | // Get any custom fields for this post if ACF is installed 187 | $returnData->acf = (class_exists('acf') && get_field_objects($id)) ? get_field_objects($id) : false; 188 | 189 | // Get te featured image stuff 190 | $returnData->featured_image = AltThemeEndpoints::getFeaturedImg($id); 191 | 192 | // Get the breadcrumbs 193 | $returnData->breadcrumbs = $this->getBreadcrumbs($id); 194 | 195 | // List of post data not to be sent in response 196 | $noThanks = [ 197 | 'post_modified_gmt', 198 | 'post_modified', 199 | 'post_author', 200 | 'pinged', 201 | 'post_modified', 202 | 'to_ping', 203 | 'post_status', 204 | 'post_password', 205 | 'post_mime_type', 206 | 'ping_status', 207 | 'filter', 208 | 'menu_order', 209 | 'guid', 210 | 'comment_status', 211 | 'comment_count', 212 | 'post_content_filtered', 213 | 'post_date_gmt' 214 | ]; 215 | 216 | foreach ($noThanks as $goodBye) { 217 | unset($returnData->{$goodBye}); 218 | } 219 | 220 | return $returnData; 221 | 222 | } 223 | 224 | 225 | /** 226 | * Get breadcrumbs from ID 227 | * 228 | * @param $id 229 | * 230 | * @return array 231 | */ 232 | public function getBreadcrumbs($id) 233 | { 234 | 235 | $breadArr[] = ['title' => get_the_title($id), 'permalink' => get_permalink($id)]; 236 | 237 | $parentId = wp_get_post_parent_id($id); 238 | 239 | // Only prepend to array is has parent post 240 | if ($parentId) { 241 | 242 | do { 243 | array_unshift($breadArr, 244 | ['title' => get_the_title($parentId), 'permalink' => get_permalink($parentId)]); 245 | $parentId = wp_get_post_parent_id($parentId); 246 | } while ($parentId != 0); 247 | 248 | } 249 | 250 | // Prepend post type if not page 251 | $postType = get_post_type($id); 252 | if ($postType !== 'page') { 253 | array_unshift($breadArr, ['title' => ucfirst($postType), 'permalink' => get_home_url() . '/' . $postType]); 254 | } 255 | 256 | array_unshift($breadArr, ['title' => 'Home', 'permalink' => get_home_url() . '/']); 257 | 258 | return $breadArr; 259 | } 260 | 261 | 262 | /** 263 | * Checks the slug passed against post types to see if it matches, 264 | * This means that when we search for the slug, we can check only 265 | * against the correct post type, allowing us to have posts in 266 | * different types with the same slug! 267 | * 268 | * @param $slug 269 | * 270 | * @return array 271 | */ 272 | public function checkType($slug) 273 | { 274 | $matchingPostType = ''; 275 | 276 | if ($slug[0] !== '/') { 277 | $slug = '/' . $slug; 278 | } 279 | 280 | // Retrieves a list of all post types 281 | $allPostTypes = get_post_types('', 'objects', ''); 282 | 283 | // Explode the different parts of the slug and remove blank array parts 284 | $slugParts = array_diff(explode('/', $slug), ['']); 285 | 286 | // Check is second part to slug or if slug is just 1 deep 287 | if (isset($slugParts[2]) && !empty($slugParts[2])) { 288 | 289 | // For each post type 290 | foreach ($allPostTypes as $postType) { 291 | $matchTo = $postType->rewrite['slug'] ? $postType->rewrite['slug'] : $postType->name; 292 | 293 | // if the first part of the slug matches a post type rewrite url 294 | if ($slugParts[1] === $matchTo) { 295 | $matchingPostType = $postType->name; 296 | } 297 | } 298 | 299 | // if no matching post type, assume the slug multipart slug is because it's a child page, return the full slug 300 | if (!$matchingPostType) { 301 | $returnSlug = $slug; 302 | } else { // else return just the last part of the slug by stripping the post types rewrite url 303 | $returnSlug = str_replace('/', '', implode('', explode($matchingPostType, $slug, 2))); 304 | } 305 | 306 | // else if the slug has only one part, like a normal page 307 | } else { 308 | $returnSlug = $slug; 309 | } 310 | 311 | return [ 312 | 'post_type' => $matchingPostType ? $matchingPostType : 'page', 313 | 'slug' => $returnSlug 314 | ]; 315 | } 316 | 317 | 318 | /** 319 | * Returns an Object with everything you need in 320 | * Just pass it an ID or slug! 321 | * 322 | * @param $data 323 | * 324 | */ 325 | public static function queryAll($data) 326 | { 327 | $AltThemeEndpoints = new AltThemeEndpoints(); 328 | $isPreview = false; 329 | 330 | // Page Search Criteria 331 | $psc = $AltThemeEndpoints->checkType($data['slug']); 332 | 333 | // Handle previews and draft pages 334 | if (!empty($data['slug']) && isset($_GET['preview'])) { 335 | $isPreview = true; 336 | 337 | // Insert query params stug in slug into $_GET 338 | $urlParts = parse_url($data['slug']); 339 | parse_str($urlParts['query'], $params); 340 | 341 | foreach ($params as $key => $value) { 342 | if (!isset($_GET[$key])) { 343 | $_GET[$key] = $value; 344 | } 345 | } 346 | 347 | // Get the page ID from the preview_id param 348 | $id = $_GET['preview_id'] ?? $_GET['p']; 349 | } 350 | // If the slug is not empty and isn't "/" (Home page) 351 | elseif (!empty($data['slug'] && $data['slug'] !== '/')) { 352 | $id = get_page_by_path($psc['slug'], OBJECT, $psc['post_type'])->ID; 353 | } // If we have an ID instead of a slug 354 | elseif (!empty($data['id'])) { 355 | $id = $data['id']; 356 | } // Else, it will be the Home page 357 | else { 358 | $id = get_option('page_on_front'); 359 | } 360 | 361 | 362 | // Get and return the content from our narrowed down ID 363 | // Ensures ID is an integer 364 | echo json_encode($AltThemeEndpoints->getPostData(intval($id), $isPreview)); 365 | die(); 366 | } 367 | 368 | public static function getArchives($type, $postType = false) 369 | { 370 | if ($type['type'] == 'date') { 371 | $y = 2009; 372 | $dateArr = []; 373 | $postType = $_GET['postType']; 374 | do{ 375 | if(!empty(get_posts(['post_type' => $postType, 'date_query' => [['year' => $y]]]))){ 376 | $dateArr[] = $y; 377 | } 378 | $y++; 379 | }while($y <= date('Y')); 380 | echo json_encode($dateArr); 381 | die(); 382 | } 383 | 384 | if ($type['type'] == 'categories') { 385 | echo json_encode(get_terms( 386 | [ 387 | 'taxonomy' => 'category', 388 | 'hide_empty' => false 389 | ] 390 | )); 391 | die(); 392 | } 393 | 394 | if ($type['type'] == 'expertise') { 395 | echo json_encode(get_terms( 396 | [ 397 | 'taxonomy' => 'expertise', 398 | 'hide_empty' => false 399 | ] 400 | )); 401 | die(); 402 | } 403 | 404 | echo 'Type not specified. Please add either `date`, `categories` or `expertise`'; 405 | die(); 406 | } 407 | } 408 | 409 | 410 | /** 411 | * Endpoints are registered on rest_api_init 412 | */ 413 | add_action('rest_api_init', function () { 414 | 415 | /** 416 | * Endpoint for getting post data by ID or slug 417 | */ 418 | register_rest_route('alt/v1', '/all', [ 419 | 'methods' => 'GET', 420 | 'callback' => ['AltThemeEndpoints', 'queryAll'], 421 | 'args' => [ 422 | 'slug' => [ 423 | 'default' => false // Pass a slug 424 | ], 425 | 'id' => [ 426 | 'default' => false 427 | ] 428 | ], 429 | ]); 430 | 431 | 432 | /** 433 | * Endpoint for getting ACF options page data 434 | */ 435 | register_rest_route('alt/v1', '/global-acf', [ 436 | 'methods' => 'GET', 437 | 'callback' => ['AltThemeEndpoints', 'getACFGlobalOptions'], 438 | ]); 439 | 440 | 441 | /** 442 | * Endpoint for getting Featured Images 443 | */ 444 | register_rest_route('alt/v1', '/featured-image', [ 445 | 'methods' => 'GET', 446 | 'callback' => ['AltThemeEndpoints', 'returnFeaturedImg'], 447 | 'args' => [ 448 | 'id' => [ 449 | 'default' => false 450 | ] 451 | ] 452 | ]); 453 | 454 | 455 | /** 456 | * Endpoint for getting All posts of a certain post type 457 | */ 458 | register_rest_route('alt/v1', '/archive', [ 459 | 'methods' => 'GET', 460 | 'callback' => ['AltThemeEndpoints', 'getArchiveData'], 461 | 'args' => [ 462 | 'post_type' => [ 463 | 'default' => false 464 | ], 465 | 'amount' => [ 466 | 'default' => -1 467 | ], 468 | 'acf' => [ 469 | 'default' => false 470 | ] 471 | ] 472 | ]); 473 | 474 | /** 475 | * Endpoint for getting All posts of a certain post type 476 | */ 477 | register_rest_route('alt/v1', '/get-archives', [ 478 | 'methods' => 'GET', 479 | 'callback' => ['AltThemeEndpoints', 'getArchives'], 480 | 'args' => [ 481 | 'type' => [ 482 | 'default' => false // Or an ID 483 | ] 484 | ] 485 | ]); 486 | }); 487 | -------------------------------------------------------------------------------- /lib/helpers.php: -------------------------------------------------------------------------------- 1 |

' . __('Warning: ' . wp_get_theme() . ' requires the "WP API Menus". Install it here.') . '

'; 35 | } 36 | 37 | if (!class_exists('acf')) { 38 | echo '

' . __('Warning: ' . wp_get_theme() . ' requires the "Advanced Custom Fields PRO". Download it here.') . '

'; 39 | } 40 | 41 | } 42 | 43 | 44 | /** 45 | * Tell WordPress to manage our page titles (JS will take over after initial load) 46 | * 47 | * 48 | */ 49 | function theme_slug_setup() 50 | { 51 | add_theme_support('title-tag'); 52 | } 53 | 54 | 55 | /** 56 | * Allow cross origin requests, necessary for our webpack dev server 57 | * 58 | * 59 | */ 60 | function add_cors_http_header() 61 | { 62 | header("Access-Control-Allow-Origin: *"); 63 | } 64 | 65 | 66 | /** 67 | * Register Custom Image Sizes and add theme support for post thumbnail 68 | * 69 | * 70 | */ 71 | add_theme_support('post-thumbnails'); 72 | set_post_thumbnail_size(672, 372, true); 73 | add_image_size('max', 1920, 1080, false); 74 | add_image_size('icon', 32, 32, false); 75 | 76 | 77 | /** 78 | * Register Navigation Menu's 79 | * 80 | * 81 | */ 82 | register_nav_menus([ 83 | 'main' => __('Main Navigation'), 84 | 'footer' => __('Footer Navigation'), 85 | ]); 86 | 87 | 88 | /** 89 | * Enqueue Front-end Scripts/Styles 90 | * 91 | * 92 | */ 93 | function alt_enqueue_frontend_scripts_styles() 94 | { 95 | // wp_enqueue_script('jquery', null, null, null, true); 96 | wp_enqueue_script('MainJs', get_template_directory_uri() . '/dist/bundle.js', null, null, true); 97 | wp_enqueue_style('FontAwesome', '//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css', 98 | null, null, null); 99 | } 100 | 101 | 102 | /** 103 | * Register Global Options Page 104 | * 105 | * 106 | */ 107 | if (function_exists('acf_add_options_page')) { 108 | acf_add_options_page('Global Options'); 109 | } 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "webpack", 4 | "watch": "webpack -w", 5 | "dev": "node server.js useDevMiddleware", 6 | "production": "webpack -p", 7 | "component": "node ./cli/scripts/componentGenerator.js" 8 | }, 9 | "devDependencies": { 10 | "autoprefixer": "^6.7.6", 11 | "axios": "^0.15.3", 12 | "babel-core": "^6.20.0", 13 | "babel-loader": "^6.2.9", 14 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 15 | "babel-plugin-transform-runtime": "^6.15.0", 16 | "babel-polyfill": "^6.23.0", 17 | "babel-preset-es2015": "^6.18.0", 18 | "babel-preset-stage-0": "^6.16.0", 19 | "babel-runtime": "^6.20.0", 20 | "css-loader": "^0.26.1", 21 | "dotenv": "^4.0.0", 22 | "eslint": "^3.15.0", 23 | "eslint-config-standard": "^6.2.1", 24 | "eslint-plugin-html": "^2.0.1", 25 | "eslint-plugin-promise": "^3.4.0", 26 | "eslint-plugin-standard": "^2.0.1", 27 | "express": "^4.14.0", 28 | "lodash": "^4.17.4", 29 | "node-sass": "^4.0.0", 30 | "postcss-loader": "^1.3.2", 31 | "postcss-scss": "^0.4.1", 32 | "sass-loader": "^4.0.2", 33 | "style-loader": "^0.13.1", 34 | "vue-hot-reload-api": "^2.0.6", 35 | "vue-html-loader": "^1.2.3", 36 | "vue-loader": "^10.0.2", 37 | "vue-style-loader": "^1.0.0", 38 | "vue-template-compiler": "^2.2.5", 39 | "webpack": "^2.2.1", 40 | "webpack-dev-middleware": "^1.8.4", 41 | "webpack-dev-server": "^1.16.2", 42 | "webpack-hot-middleware": "^2.13.2" 43 | }, 44 | "dependencies": { 45 | "axios": "^0.15.3", 46 | "core-decorators": "^0.15.0", 47 | "jump.js": "^1.0.1", 48 | "normalize.css": "^7.0.0", 49 | "vue": "^2.2.5", 50 | "vue-router": "^2.3.0", 51 | "vuex": "^2.2.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | syntax: require('postcss-scss'), 3 | plugins: [ 4 | require('autoprefixer') 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-design/wp-spa-boilerplate/bde4eceabd4d064ef20c0a845a73b84003f086e9/screenshot.jpg -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Imports 3 | */ 4 | const env = require('dotenv').config() 5 | const http = require('http') 6 | const express = require('express') 7 | const app = express() 8 | const axios = require('axios') 9 | const fs = require('fs') 10 | const webpackDevMiddleware = require('webpack-dev-middleware') 11 | const webpack = require('webpack') 12 | const webpackConfig = require(process.env.WEBPACK_CONFIG ? process.env.WEBPACK_CONFIG : './webpack.config') 13 | const compiler = webpack(webpackConfig) 14 | 15 | /** 16 | * Server Plugins 17 | */ 18 | app.use(webpackDevMiddleware(compiler, {noInfo: true, quiet: true, publicPath: webpackConfig.output.publicPath})) 19 | app.use(require('webpack-hot-middleware')(compiler)) 20 | 21 | /** 22 | * Routes 23 | */ 24 | // Just to stop logging of favicon requests 25 | app.get('/favicon.ico', () => {}) 26 | 27 | // Handle all other requests 28 | app.get('*', (req, res) => { 29 | new Promise(resolve => { 30 | // Send the request to build webpackTemp.html 31 | axios.get(env.parsed.DEV_URL) 32 | 33 | // Once webpackTemp.html has been updated, resolve the promise 34 | fs.watch(__dirname + '/webpackTemp.html', () => { 35 | setTimeout(() => { resolve() }, 250) 36 | }) 37 | }).then(() => { 38 | // Server webpackTemp.html 39 | res.sendFile(__dirname + '/webpackTemp.html') 40 | }) 41 | }) 42 | 43 | /** 44 | * Initiate the server 45 | */ 46 | http.createServer(app).listen(env.parsed.PORT) 47 | -------------------------------------------------------------------------------- /src/assets/scss/imports/_components.scss: -------------------------------------------------------------------------------- 1 | // Global Components SCSS, things like .container etc. -------------------------------------------------------------------------------- /src/assets/scss/imports/_extends.scss: -------------------------------------------------------------------------------- 1 | // Reusable chunks of styling, this is to promote DRYNESS. 2 | 3 | // Font Colors 4 | %color--body { 5 | color: $color--body; 6 | } 7 | 8 | // Line Heights 9 | %line-height--body { 10 | line-height: $line-height--body; 11 | } 12 | 13 | %line-height--title { 14 | line-height: $line-height--title; 15 | } 16 | 17 | %line-height--compact { 18 | line-height: $line-height--compact; 19 | } 20 | 21 | // Hover Effects 22 | %hover--opacity { 23 | transition: .25s; 24 | 25 | &:hover { 26 | opacity: .75; 27 | } 28 | } -------------------------------------------------------------------------------- /src/assets/scss/imports/_fonts.scss: -------------------------------------------------------------------------------- 1 | // Fonts 2 | 3 | h1, h2, h3, h4, h5, h6 { 4 | @extend %line-height--title; 5 | } 6 | 7 | a { 8 | text-decoration: inherit; 9 | color: inherit; 10 | } -------------------------------------------------------------------------------- /src/assets/scss/imports/_forms.scss: -------------------------------------------------------------------------------- 1 | // Forms, inputs, buttons etc. -------------------------------------------------------------------------------- /src/assets/scss/imports/_global.scss: -------------------------------------------------------------------------------- 1 | // General App Styles. 2 | 3 | body { 4 | @extend %line-height--body; 5 | @extend %color--body; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | font-size: 16px; 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/scss/imports/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins. -------------------------------------------------------------------------------- /src/assets/scss/imports/_modifiers.scss: -------------------------------------------------------------------------------- 1 | // Place your modifier classes in here, e.g. .margin-0. -------------------------------------------------------------------------------- /src/assets/scss/imports/_variables.scss: -------------------------------------------------------------------------------- 1 | // Variables. 2 | 3 | // Font Colors 4 | $color--body: #000; 5 | 6 | // Line Heights 7 | $line-height--body: 1.5; 8 | $line-height--title: 1.25; 9 | $line-height--compact: 1; 10 | 11 | // Breakpoints 12 | $breakpoint--xs: 576px; 13 | $breakpoint--s: 768px; 14 | $breakpoint--m: 992px; 15 | $breakpoint--l: 1200px; 16 | -------------------------------------------------------------------------------- /src/assets/scss/main.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Please don't write any styles in this file. 3 | */ 4 | @import "~normalize.css"; 5 | @import "imports/variables"; 6 | @import "imports/mixins"; 7 | @import "imports/extends"; 8 | @import "imports/fonts"; 9 | @import "imports/global"; 10 | @import "imports/forms"; 11 | @import "imports/components"; 12 | @import "imports/modifiers"; 13 | -------------------------------------------------------------------------------- /src/components/App/App.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/components/App/App.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App 3 | * 4 | * @template : ./App.component.html 5 | * @style : ./App.component.scss 6 | */ 7 | 8 | export default { 9 | data () { 10 | return { 11 | message: 'App' 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/App/App.component.scss: -------------------------------------------------------------------------------- 1 | .app { 2 | 3 | &-enter-active, &-leave-active { 4 | transition: opacity .5s 5 | } 6 | 7 | &-enter, &-leave-active { 8 | opacity: 0 9 | } 10 | } -------------------------------------------------------------------------------- /src/components/App/App.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/App/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "main": "App.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Breadcrumbs/Breadcrumbs.component.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/components/Breadcrumbs/Breadcrumbs.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Breadcrumbs 3 | * 4 | * @template : ./Breadcrumbs.component.html 5 | * @style : ./Breadcrumbs.component.scss 6 | */ 7 | 8 | export default { 9 | methods: { 10 | relativeLink (link) { 11 | if (link) return link.replace(this.$store.state.url, '') 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Breadcrumbs/Breadcrumbs.component.scss: -------------------------------------------------------------------------------- 1 | .breadcrumbs { 2 | 3 | &__breadcrumb { 4 | 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /src/components/Breadcrumbs/Breadcrumbs.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Breadcrumbs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "breadcrumbs", 3 | "version": "1.0.0", 4 | "main": "Breadcrumbs.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Footer 3 | * 4 | * @template : ./Footer.component.html 5 | * @style : ./Footer.component.scss 6 | */ 7 | 8 | export default { 9 | data () { 10 | return { 11 | message: 'Footer' 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.component.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | 3 | } -------------------------------------------------------------------------------- /src/components/Footer/Footer.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Footer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "footer", 3 | "version": "1.0.0", 4 | "main": "Footer.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Header/Header.component.html: -------------------------------------------------------------------------------- 1 |
2 | This is the {{message}} component 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /src/components/Header/Header.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Header 3 | * 4 | * @template : ./Header.component.html 5 | * @style : ./Header.component.scss 6 | */ 7 | 8 | export default { 9 | data () { 10 | return { 11 | message: 'Header' 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Header/Header.component.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | 3 | } -------------------------------------------------------------------------------- /src/components/Header/Header.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "header", 3 | "version": "1.0.0", 4 | "main": "Header.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Menus/AppMenu/AppMenu.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/Menus/AppMenu/AppMenu.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AppMenu 3 | * 4 | * @template : ./AppMenu.component.html 5 | * @style : ./AppMenu.component.scss 6 | */ 7 | 8 | export default { 9 | props: { 10 | location: String, 11 | emitOnComplete: String 12 | }, 13 | data () { 14 | return { 15 | items: Object, 16 | fetched: false 17 | } 18 | }, 19 | created () { 20 | this.$http.get('/wp-json/wp-api-menus/v2/menu-locations/' + this.location).then(res => { 21 | if (res.data.length) { 22 | this.items = res.data 23 | } else { 24 | console.error(`Failed to retrieve menu ${this.location}, check the menu location is correct and the menu is not empty.`) 25 | } 26 | }).then(() => { 27 | this.fetched = true 28 | }) 29 | }, 30 | mounted () { 31 | let fetchInterval = setInterval(() => { 32 | if (this.fetched) { 33 | clearInterval(fetchInterval) 34 | this.emitOnComplete && this.$emit(this.emitOnComplete) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Menus/AppMenu/AppMenu.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Menus/AppMenu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-menu", 3 | "version": "1.0.0", 4 | "main": "AppMenu.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Menus/AppMenuItem/AppMenuItem.component.html: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | 8 | 9 | 10 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 29 | 30 |
  • 31 | -------------------------------------------------------------------------------- /src/components/Menus/AppMenuItem/AppMenuItem.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AppMenuItem 3 | * 4 | * @template : ./AppMenuItem.component.html 5 | * @style : ./AppMenuItem.component.scss 6 | */ 7 | 8 | export default { 9 | props: { 10 | item: Object, 11 | location: String, 12 | depth: { 13 | type: Number, 14 | default: 0 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Menus/AppMenuItem/AppMenuItem.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Menus/AppMenuItem/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-menu-item", 3 | "version": "1.0.0", 4 | "main": "AppMenuItem.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /src/components/PageBuilder/PageBuilder.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 |
    8 | -------------------------------------------------------------------------------- /src/components/PageBuilder/PageBuilder.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PageBuilder 3 | * 4 | * @template : ./PageBuilder.component.html 5 | * @style : ./PageBuilder.component.scss 6 | */ 7 | 8 | export default {} 9 | -------------------------------------------------------------------------------- /src/components/PageBuilder/PageBuilder.component.scss: -------------------------------------------------------------------------------- 1 | .page-builder { 2 | 3 | } -------------------------------------------------------------------------------- /src/components/PageBuilder/PageBuilder.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/PageBuilder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "page-builder", 3 | "version": "1.0.0", 4 | "main": "PageBuilder.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Views/Page/Page.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | Logo 4 | 5 |

    You are currently viewing the {{$store.state.post.post_title}} page. Edit this template - 6 | ~/src/components/Views/Page/Page.component.html

    7 | 8 | 9 | 10 |
    11 | -------------------------------------------------------------------------------- /src/components/Views/Page/Page.component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Page 3 | * 4 | * @template : ./Page.component.html 5 | * @style : ./Page.component.scss 6 | */ 7 | 8 | export default {} 9 | -------------------------------------------------------------------------------- /src/components/Views/Page/Page.component.scss: -------------------------------------------------------------------------------- 1 | .page { 2 | 3 | } -------------------------------------------------------------------------------- /src/components/Views/Page/Page.component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Views/Page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "page", 3 | "version": "1.0.0", 4 | "main": "Page.component.vue" 5 | } 6 | -------------------------------------------------------------------------------- /src/imports/components.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.component('page-builder', require('../components/PageBuilder')) 4 | Vue.component('app-header', require('../components/Header')) 5 | Vue.component('app-footer', require('../components/Footer')) 6 | Vue.component('breadcrumbs', require('../components/Breadcrumbs')) 7 | Vue.component('app-menu', require('../components/Menus/AppMenu')) 8 | Vue.component('app-menu-item', require('../components/Menus/AppMenuItem')) 9 | -------------------------------------------------------------------------------- /src/imports/functions.js: -------------------------------------------------------------------------------- 1 | import store from '../vuex/store' 2 | 3 | export default class Functions { 4 | static updateAdminBar () { 5 | const editPage = document.getElementById('wp-admin-bar-edit') 6 | const editPageLink = editPage ? editPage.querySelector('a') : false 7 | editPageLink && editPageLink.setAttribute('href', `${store.state.adminUrl}/post.php?post=${store.state.post.ID}&action=edit`) 8 | } 9 | 10 | static getUrlParams () { 11 | const queryString = window.location.href.split('?')[1] 12 | if (queryString) { 13 | const keyValuePairs = queryString.split('&') 14 | const params = {} 15 | let keyValue 16 | 17 | keyValuePairs.forEach((pair) => { 18 | keyValue = pair.split('=') 19 | params[keyValue[0]] = decodeURIComponent(keyValue[1]).replace('+', ' ') 20 | }) 21 | 22 | return params 23 | } 24 | return false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/imports/ready.js: -------------------------------------------------------------------------------- 1 | import store from '../vuex/store' 2 | import axios from 'axios' 3 | 4 | export default class Ready { 5 | constructor () { 6 | Ready.setGlobalOptions() 7 | Ready.setThemeDir() 8 | Ready.setSiteURL() 9 | Ready.setName() 10 | Ready.setAdminURL() 11 | } 12 | 13 | static setGlobalOptions () { 14 | axios.get(`${Ready.getSiteURL()}/wp-json/alt/v1/global-acf`).then(res => { 15 | store.commit('setGlobalOptions', res.data) 16 | }) 17 | } 18 | 19 | static setAdminURL () { 20 | store.commit('setAdminURL', document.getElementById('a-url').innerHTML) 21 | } 22 | 23 | static setSiteURL () { 24 | store.commit('setSiteURL', Ready.getSiteURL()) 25 | } 26 | 27 | static getSiteURL () { 28 | return document.getElementById('url').innerHTML 29 | } 30 | 31 | static setThemeDir () { 32 | store.commit('setThemeDir', document.getElementById('theme').innerHTML) 33 | } 34 | 35 | static setName () { 36 | store.commit('setName', document.getElementById('name').innerHTML) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import Vue from 'vue' 3 | import './assets/scss/main.scss' 4 | import App from './components/App' 5 | import './imports/components' 6 | import Ready from './imports/ready' 7 | import router from './router/router' 8 | import store from './vuex/store' 9 | 10 | /** 11 | * Vue Setup 12 | */ 13 | Vue.prototype.$http = axios.create({ 14 | baseURL: Ready.getSiteURL() 15 | }) 16 | 17 | /** 18 | * Used to generate relative URL's from absolute 19 | * URL's for use with Vue Router 20 | */ 21 | Vue.prototype.$relativeUrl = url => { 22 | return url.replace(store.state.url, '') 23 | } 24 | 25 | /** 26 | * Main Vue Instance 27 | */ 28 | new Vue({ 29 | el: '#app', 30 | store, 31 | router, 32 | mounted () { 33 | new Ready() 34 | }, 35 | render: h => h(App) 36 | }) 37 | -------------------------------------------------------------------------------- /src/router/router.js: -------------------------------------------------------------------------------- 1 | import Jump from 'jump.js' 2 | import Vue from 'vue' 3 | import VueRouter from 'vue-router' 4 | import Functions from '../imports/Functions' 5 | import store from '../vuex/store' 6 | import routes from './Routes' 7 | 8 | Vue.use(VueRouter) 9 | 10 | // Main Router Instance 11 | const router = new VueRouter({ 12 | routes, 13 | mode: 'history' 14 | }) 15 | 16 | // Run before each route change 17 | router.beforeEach((to, from, next) => { 18 | Jump('html', { 19 | duration: window.pageYOffset > 10 ? 500 : 0, 20 | callback () { 21 | new Promise((resolve) => { 22 | store.commit('updatePost', (to.fullPath ? to.fullPath : 'home')) 23 | resolve() 24 | }) 25 | 26 | next() 27 | } 28 | }) 29 | }) 30 | 31 | // Run after each route change 32 | // router.afterEach((to, from) => { 33 | // }); 34 | 35 | export default router 36 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | import Page from '../components/Views/Page' 2 | 3 | const Routes = [ 4 | { 5 | path: '/', 6 | component: Page, 7 | name: 'Home' 8 | }, 9 | { 10 | path: '/:slug/', 11 | component: Page, 12 | name: 'Page' 13 | }, 14 | { 15 | path: '/:slug/:childSlug/', 16 | component: Page, 17 | name: 'SubPage' 18 | } 19 | ] 20 | 21 | export default Routes 22 | -------------------------------------------------------------------------------- /src/vuex/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Actions 3 | } 4 | -------------------------------------------------------------------------------- /src/vuex/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Getters 3 | } 4 | -------------------------------------------------------------------------------- /src/vuex/mutations.js: -------------------------------------------------------------------------------- 1 | import Http from 'axios' 2 | import Functions from '../imports/Functions' 3 | 4 | export default { 5 | 6 | setGlobalOptions (state, data) { 7 | state.global = data 8 | }, 9 | 10 | setThemeDir (state, string) { 11 | state.theme = string 12 | }, 13 | 14 | setSiteURL (state, string) { 15 | state.url = string 16 | }, 17 | 18 | setAdminURL (state, string) { 19 | state.adminUrl = string 20 | }, 21 | 22 | setName (state, name) { 23 | state.name = name 24 | }, 25 | 26 | setTagLine (state, description) { 27 | state.tagLine = description 28 | }, 29 | 30 | // This mutation is the life of the party 31 | updatePost (state, newPath) { 32 | Http.get(state.url + '/wp-json/alt/v1/all?slug=' + newPath).then(res => { 33 | state.post = res.data 34 | document.title = res.data.post_title + ' - ' + state.name 35 | 36 | Functions.updateAdminBar() 37 | }, res => { 38 | // Error 39 | console.error('Something went wrong with updatePost mutation, here it is:') 40 | console.error(res) 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/vuex/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | adminUrl: '', 3 | global: {}, 4 | theme: '', 5 | name: '', 6 | post: {}, 7 | url: '' 8 | } 9 | -------------------------------------------------------------------------------- /src/vuex/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueX from 'vuex' 3 | import actions from './actions' 4 | import getters from './getters' 5 | import mutations from './mutations' 6 | import state from './state' 7 | 8 | Vue.use(VueX) 9 | 10 | export default new VueX.Store({ 11 | state, 12 | mutations, 13 | actions, 14 | getters 15 | }) 16 | -------------------------------------------------------------------------------- /static/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Theme Name: Vue.js / WP SPA Theme 3 | Author: Sam Mckay / Ben Harvey @ Alt Design 4 | Author URI: https://alt-design.net 5 | Version: I've heard so 6 | */ -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const env = require('dotenv').config() 2 | const webpack = require('webpack') 3 | 4 | // User Settings 5 | const settings = { 6 | entry: [ 7 | 'babel-polyfill', // Babel Polyfill is required by VueX for Promises in IE 8 | `./src/${env.parsed.ENTRY}` 9 | ], 10 | output: { 11 | JavaScript: { 12 | path: __dirname + '/dist/', 13 | publicPath: '/dist/', 14 | filename: env.parsed.OUTPUT 15 | } 16 | }, 17 | scssLoaders: 'style-loader!css-loader!sass-loader!postcss-loader' 18 | } 19 | 20 | // If add the webpack-hot-middleware client for hot reloading if necessary. 21 | process.argv.includes('useDevMiddleware') && settings.entry.unshift('webpack-hot-middleware/client') 22 | 23 | // Webpack 2 Config 24 | module.exports = { 25 | entry: settings.entry, 26 | output: settings.output.JavaScript, 27 | resolve: { 28 | extensions: ['.js', '.vue'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.js$/, 34 | loader: 'babel-loader', 35 | exclude: /node_modules/ 36 | }, 37 | { 38 | test: /\.vue$/, 39 | loader: 'vue-loader', 40 | options: { 41 | loaders: { 42 | scss: settings.scssLoaders 43 | } 44 | } 45 | }, 46 | { 47 | test: /\.scss$/, 48 | loader: settings.scssLoaders 49 | } 50 | ] 51 | }, 52 | plugins: [ 53 | new webpack.ProgressPlugin(), 54 | new webpack.optimize.OccurrenceOrderPlugin(), 55 | new webpack.HotModuleReplacementPlugin(), 56 | new webpack.NoEmitOnErrorsPlugin() 57 | ] 58 | } 59 | --------------------------------------------------------------------------------