├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── History.md ├── README.md ├── acf-export.json ├── babel.config.js ├── config └── sample-client.config.json ├── jest.config.js ├── package.json ├── public ├── favicon.ico └── index.html ├── scripts ├── serve.js └── start.js ├── server.js ├── src ├── assets │ ├── logo.png │ ├── page_header_bg.png │ └── post_header_bg.png ├── core │ ├── app.js │ ├── graphql │ │ ├── category.gql │ │ ├── menu.gql │ │ ├── page.gql │ │ ├── post.gql │ │ ├── posts.gql │ │ └── user.gql │ ├── plugin │ │ └── index.js │ ├── router.js │ ├── ssr │ │ ├── entry-client.js │ │ └── entry-server.js │ ├── vue-apollo.js │ └── vuex │ │ ├── actions.js │ │ ├── getters.js │ │ └── store.js ├── index.template.html └── themes │ ├── basic │ ├── App.vue │ ├── components │ │ ├── category │ │ │ ├── CategoryList.vue │ │ │ └── containers │ │ │ │ └── CategoryContainer.vue │ │ ├── header │ │ │ └── header.vue │ │ ├── icons │ │ │ └── icons.vue │ │ ├── menu │ │ │ ├── menu.vue │ │ │ ├── menuContainer.vue │ │ │ └── menuLink.vue │ │ ├── page │ │ │ ├── PageHeader.vue │ │ │ ├── layouts │ │ │ │ ├── DefaultPage.vue │ │ │ │ ├── PageWithHeader.vue │ │ │ │ ├── PageWithSidebar.vue │ │ │ │ └── layouts.js │ │ │ └── page.vue │ │ └── post │ │ │ ├── PostContent.vue │ │ │ ├── PostList.vue │ │ │ ├── PostListItem.vue │ │ │ ├── PostSingle.vue │ │ │ └── containers │ │ │ ├── PostContainer.vue │ │ │ └── PostsContainer.vue │ ├── routes │ │ └── index.js │ └── styles │ │ ├── colors.scss │ │ ├── reset.css │ │ └── typography.css │ └── multi-user │ ├── App.vue │ ├── components │ ├── author │ │ ├── AuthorSingle.vue │ │ └── containers │ │ │ └── AuthorContainer.vue │ ├── category │ │ ├── CategoryList.vue │ │ └── containers │ │ │ └── CategoryContainer.vue │ ├── header │ │ └── header.vue │ ├── icons │ │ └── icons.vue │ ├── menu │ │ ├── menu.vue │ │ ├── menuContainer.vue │ │ └── menuLink.vue │ ├── page │ │ ├── PageHeader.vue │ │ ├── layouts │ │ │ ├── DefaultPage.vue │ │ │ ├── PageWithHeader.vue │ │ │ ├── PageWithSidebar.vue │ │ │ └── layouts.js │ │ └── page.vue │ └── post │ │ ├── PostContent.vue │ │ ├── PostList.vue │ │ ├── PostListItem.vue │ │ ├── PostSingle.vue │ │ └── containers │ │ ├── PostContainer.vue │ │ └── PostsContainer.vue │ ├── routes │ └── index.js │ └── styles │ ├── colors.scss │ ├── reset.css │ └── typography.css ├── tests └── unit │ ├── .eslintrc.js │ └── HelloWorld.spec.js ├── vue.config.js ├── yarn-error.log └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | dist/*.js 3 | node_modules/* 4 | **/lib/* 5 | *.gql -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/prettier' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | .vscode/ 6 | config/client.json 7 | config/development.json 8 | config/production.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "7" 5 | script: npm run unit --single-run -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | # V.3.0.0-beta 2 | 3 | - Completely replaced build system using Vue CLI 3.0 (beta release) 4 | 5 | # V.2.1 6 | 7 | - Created a multi-user theme to display author profile pages. 8 | - Add gravatar link helper to plugin 9 | 10 | 11 | # V.2.0 12 | 13 | I have since moved the GraphQL server into its own stand-alone repo called [WordExpress Server](https://github.com/ramsaylanier/WordExpress-Server). V.1.X of this repo included the GraphQL server and schema, etc. If you are migrating from V.1.X, you'll first need to clone the WordExpress Server repo and follow the instructions on setting it up. 14 | 15 | # V.1.0 16 | 17 | Welcome to VuePress -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordExpress Starter - Vue (AKA VuePress) 2 | 3 | > WordPress development using Vue, GraphQL, and Express. 4 | 5 | [Read the Introduction to Vue Press](https://medium.com/@rmmmsy/introducing-vuepress-wordpress-development-with-vue-and-graphql-f5063a97bb69) 6 | 7 | This repo is a starting off point for working with WordExpress using Vue. Its meant to work with [WordExpress Server](https://github.com/ramsaylanier/WordExpress-Server). 8 | 9 | ## Node Version Requirement 10 | 11 | ```bash 12 | node 6.* 13 | node 7.* 14 | ``` 15 | 16 | ## Before Doing Anything Else 17 | 18 | Please make sure you have cloned the [WordExpress Server](https://github.com/ramsaylanier/WordExpress-Server) repo and have followed the instructions for getting it up and running. WordExpress server provides you with a connection to your WordPress database using GraphQL. 19 | 20 | ## Config 21 | 22 | Using the `/config/sample-client.config.json` file as an example, create a `client.json` file. As of now, this the config files just point to the url of where your WordExpress Server is running, and which theme in the `src/themes` directory to use. 23 | 24 | ## Build Setup 25 | 26 | ``` bash 27 | yarn 28 | yarn build 29 | yarn start 30 | ======= 31 | npm install 32 | npm run build 33 | ``` 34 | 35 | ## Development Setup 36 | 37 | ```bash 38 | yarn 39 | yarn serve 40 | ``` 41 | 42 | # Working With WordPress 43 | 44 | ## First Steps 45 | 46 | In a fresh WordPress install, you'll need to do a few basic setup items: 47 | 48 | - Create a page called 'Homepage' 49 | - Create a menu called 'primary-navigation' 50 | - Install Advanced Custom Fields plugin (see below) 51 | 52 | ## Advanced Custom Fields 53 | 54 | VuePress uses some custom post fields. You're best bet is to install the Advanced Custom Fields plugin into your WordPress backend. I've included am `acf-export` JSON file in this repo that you should import. This will give you just a few basic custom page fields that can be used to give your pages custom layout components. 55 | 56 | ## Layout Components 57 | 58 | Currently there are only [three layout components](https://github.com/ramsaylanier/VuePress/tree/master/src/components/page/layouts) - `DefaultPage`, `PageWithHeader`, and `PostList`. In order to set the layout component, simply type the name of the component in the custom field. If there is nothing in the layout component field, `DefaultPage` will be used. 59 | 60 | ### PageWithHeader 61 | 62 | In the backend: 63 | 64 | screen shot 2017-12-19 at 8 54 27 pm 65 | 66 | 67 | Result: 68 | 69 | screen shot 2017-12-19 at 9 06 14 pm 70 | 71 | ### PostList 72 | 73 | In the backend: 74 | 75 | screen shot 2017-12-19 at 9 10 04 pm 76 | 77 | Result: 78 | 79 | screen shot 2017-12-19 at 9 08 38 pm 80 | 81 | ## License 82 | 83 | [MIT](http://opensource.org/licenses/MIT) 84 | -------------------------------------------------------------------------------- /acf-export.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "group_5a32cdd1b53f1", 4 | "title": "Page Fields", 5 | "fields": [ 6 | { 7 | "key": "field_5699475fcd949", 8 | "label": "Layout Component", 9 | "name": "page_layout_component", 10 | "type": "select", 11 | "instructions": "", 12 | "required": 0, 13 | "conditional_logic": 0, 14 | "wrapper": { 15 | "width": "", 16 | "class": "", 17 | "id": "" 18 | }, 19 | "choices": { 20 | "Default": "Default", 21 | "PageWithHeader": "PageWithHeader", 22 | "PostList": "PostList", 23 | "AboutPage": "AboutPage" 24 | }, 25 | "default_value": [ 26 | "Default" 27 | ], 28 | "allow_null": 0, 29 | "multiple": 0, 30 | "ui": 0, 31 | "ajax": 0, 32 | "return_format": "value", 33 | "placeholder": "" 34 | }, 35 | { 36 | "key": "field_5a43baa53bf80", 37 | "label": "Post Type", 38 | "name": "post_type", 39 | "type": "select", 40 | "instructions": "", 41 | "required": 0, 42 | "conditional_logic": [ 43 | [ 44 | { 45 | "field": "field_5699475fcd949", 46 | "operator": "==", 47 | "value": "PostList" 48 | } 49 | ] 50 | ], 51 | "wrapper": { 52 | "width": "", 53 | "class": "", 54 | "id": "" 55 | }, 56 | "choices": { 57 | "post": "Post", 58 | "tutorial": "Tutorial" 59 | }, 60 | "default_value": [ 61 | "post" 62 | ], 63 | "allow_null": 0, 64 | "multiple": 0, 65 | "ui": 0, 66 | "ajax": 0, 67 | "return_format": "value", 68 | "placeholder": "" 69 | }, 70 | { 71 | "key": "field_5a43c2c5ab175", 72 | "label": "Post Item Component", 73 | "name": "post_item_component", 74 | "type": "select", 75 | "instructions": "", 76 | "required": 0, 77 | "conditional_logic": [ 78 | [ 79 | { 80 | "field": "field_5699475fcd949", 81 | "operator": "==", 82 | "value": "PostList" 83 | } 84 | ] 85 | ], 86 | "wrapper": { 87 | "width": "", 88 | "class": "", 89 | "id": "" 90 | }, 91 | "choices": { 92 | "PostListItem": "PostListItem", 93 | "TutorialListItem": "TutorialListItem" 94 | }, 95 | "default_value": [], 96 | "allow_null": 0, 97 | "multiple": 0, 98 | "ui": 0, 99 | "ajax": 0, 100 | "return_format": "value", 101 | "placeholder": "" 102 | }, 103 | { 104 | "key": "field_5a4423fc9061e", 105 | "label": "About Image", 106 | "name": "about_image", 107 | "type": "image", 108 | "instructions": "", 109 | "required": 0, 110 | "conditional_logic": [ 111 | [ 112 | { 113 | "field": "field_5699475fcd949", 114 | "operator": "==", 115 | "value": "AboutPage" 116 | } 117 | ] 118 | ], 119 | "wrapper": { 120 | "width": "", 121 | "class": "", 122 | "id": "" 123 | }, 124 | "return_format": "url", 125 | "preview_size": "thumbnail", 126 | "library": "all", 127 | "min_width": "", 128 | "min_height": "", 129 | "min_size": "", 130 | "max_width": "", 131 | "max_height": "", 132 | "max_size": "", 133 | "mime_types": "" 134 | } 135 | ], 136 | "location": [ 137 | [ 138 | { 139 | "param": "post_type", 140 | "operator": "==", 141 | "value": "page" 142 | } 143 | ] 144 | ], 145 | "menu_order": 0, 146 | "position": "acf_after_title", 147 | "style": "seamless", 148 | "label_placement": "top", 149 | "instruction_placement": "label", 150 | "hide_on_screen": "", 151 | "active": 1, 152 | "description": "" 153 | } 154 | ] -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /config/sample-client.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "wordexpressServerHost": "http://localhost:4000", 3 | "theme": "basic" 4 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ["js", "jsx", "json", "vue"], 3 | transform: { 4 | "^.+\\.vue$": "vue-jest", 5 | ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": 6 | "jest-transform-stub", 7 | "^.+\\.jsx?$": "babel-jest" 8 | }, 9 | moduleNameMapper: { 10 | "^@/(.*)$": "/src/$1" 11 | }, 12 | snapshotSerializers: ["jest-serializer-vue"], 13 | testMatch: [ 14 | "/(tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx))" 15 | ] 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordexpress-starter-kit-vue", 3 | "version": "3.0.0-beta", 4 | "private": false, 5 | "scripts": { 6 | "start": "cross-env NODE_ENV=production node scripts/start", 7 | "serve": "vue-cli-service serve", 8 | "build": "npm run build:server && mv dist/vue-ssr-server-bundle.json bundle && npm run build:client && mv bundle dist/vue-ssr-server-bundle.json", 9 | "lint": "vue-cli-service lint", 10 | "build:client": "vue-cli-service build", 11 | "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build", 12 | "test:unit": "vue-cli-service test:unit", 13 | "ssr:serve": "vue-cli-service ssr:serve", 14 | "ssr:build": "vue-cli-service ssr:build", 15 | "ssr:start": "cross-env NODE_ENV=production vue-cli-service ssr:serve --mode production" 16 | }, 17 | "dependencies": { 18 | "apollo-link-http": "^1.5.4", 19 | "config": "^1.30.0", 20 | "graphql": "^14.0.0-rc.1", 21 | "gsap": "^2.0.1", 22 | "highlightjs": "^9.10.0", 23 | "isomorphic-fetch": "^2.2.1", 24 | "vue": "^2.5.16", 25 | "vue-apollo": "^3.0.0-beta.10", 26 | "vue-resource": "^1.5.1", 27 | "vue-router": "^3.0.1", 28 | "vue-server-renderer": "^2.5.16", 29 | "vuex": "^3.0.1", 30 | "vuex-router-sync": "^5.0.0", 31 | "wordexpress-tools": "^1.3.0" 32 | }, 33 | "devDependencies": { 34 | "@akryum/vue-cli-plugin-ssr": "^0.1.2", 35 | "@vue/cli-plugin-babel": "^3.0.0-beta.15", 36 | "@vue/cli-plugin-eslint": "^3.0.0-beta.15", 37 | "@vue/cli-plugin-unit-jest": "^3.0.0-beta.15", 38 | "@vue/cli-service": "^3.0.0-beta.15", 39 | "@vue/eslint-config-prettier": "^3.0.0-beta.16", 40 | "@vue/test-utils": "^1.0.0-beta.16", 41 | "babel-core": "7.0.0-bridge.0", 42 | "babel-jest": "^23.0.1", 43 | "cross-env": "^5.1.6", 44 | "eslint": "^5.6.1", 45 | "eslint-loader": "^2.1.1", 46 | "eslint-plugin-html": "^4.0.3", 47 | "eslint-plugin-vue": "^4.7.1", 48 | "graphql-tag": "^2.9.2", 49 | "lodash.merge": "^4.6.1", 50 | "node-sass": "^4.9.0", 51 | "sass-loader": "^7.0.1", 52 | "vue-cli-plugin-apollo": "^0.16.0", 53 | "vue-template-compiler": "^2.5.16", 54 | "webpack": "^4.20.2", 55 | "webpack-node-externals": "^1.7.2" 56 | }, 57 | "browserslist": [ 58 | "> 1%", 59 | "last 2 versions", 60 | "not ie <= 8" 61 | ], 62 | "bugs": { 63 | "url": "https://github.com/ramsaylanier/VuePress/issues" 64 | }, 65 | "homepage": "https://github.com/ramsaylanier/VuePress#readme", 66 | "author": "ramsay lanier ", 67 | "keywords": [ 68 | "vue", 69 | "vuex", 70 | "vue-router", 71 | "webpack", 72 | "starter", 73 | "server-side", 74 | "boilerplate", 75 | "wordpress" 76 | ], 77 | "repository": { 78 | "type": "git", 79 | "url": "git+https://github.com/ramsaylanier/VuePress" 80 | }, 81 | "description": "WordPress with Vue", 82 | "license": "MIT" 83 | } 84 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramsaylanier/wordexpress-starter-vue/3fa7189ebd6a13b96db7c3871b90bbc52ec0ec9e/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | WordExpress Starter Vue 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /scripts/serve.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require("child_process"); 2 | const serve = spawn("npx", ["vue-cli-service", "serve"]); 3 | 4 | const log = console.log; 5 | const errAndExit = err => { 6 | console.log(err); 7 | process.exit(1); 8 | }; 9 | 10 | serve.on("data", log); 11 | serve.on("error", errAndExit); 12 | serve.on("close", log); 13 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | const app = require("../server"); 2 | 3 | const port = process.env.PORT || 3000; 4 | 5 | app.listen(port, () => { 6 | console.log(`server started at localhost:${port}`); 7 | }); 8 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const express = require("express"); 4 | var proxy = require("http-proxy-middleware"); 5 | const { createBundleRenderer } = require("vue-server-renderer"); 6 | 7 | const devServerBaseURL = process.env.DEV_SERVER_BASE_URL || "http://localhost"; 8 | const devServerPort = process.env.DEV_SERVER_PORT || 8080; 9 | 10 | const app = express(); 11 | 12 | function createRenderer(bundle, options) { 13 | return createBundleRenderer( 14 | bundle, 15 | Object.assign(options, { 16 | runInNewContext: false 17 | }) 18 | ); 19 | } 20 | 21 | let renderer; 22 | const templatePath = path.resolve(__dirname, "./src/index.template.html"); 23 | 24 | const bundle = require("./dist/vue-ssr-server-bundle.json"); 25 | const template = fs.readFileSync(templatePath, "utf-8"); 26 | const clientManifest = require("./dist/vue-ssr-client-manifest.json"); 27 | renderer = createRenderer(bundle, { 28 | template, 29 | clientManifest 30 | }); 31 | 32 | if (process.env.NODE_ENV !== "production") { 33 | app.use( 34 | "/js/main*", 35 | proxy({ 36 | target: `${devServerBaseURL}/${devServerPort}`, 37 | changeOrigin: true, 38 | pathRewrite: function(path) { 39 | return path.includes("main") ? "/main.js" : path; 40 | }, 41 | prependPath: false 42 | }) 43 | ); 44 | 45 | app.use( 46 | "/*hot-update*", 47 | proxy({ 48 | target: `${devServerBaseURL}/${devServerPort}`, 49 | changeOrigin: true 50 | }) 51 | ); 52 | 53 | app.use( 54 | "/sockjs-node", 55 | proxy({ 56 | target: `${devServerBaseURL}/${devServerPort}`, 57 | changeOrigin: true, 58 | ws: true 59 | }) 60 | ); 61 | } 62 | 63 | app.use("/js", express.static(path.resolve(__dirname, "./dist/js"))); 64 | app.use("/css", express.static(path.resolve(__dirname, "./dist/css"))); 65 | 66 | app.get("*", (req, res) => { 67 | res.setHeader("Content-Type", "text/html"); 68 | 69 | const context = { 70 | title: "WordExpress Starter Vue", // default title 71 | url: req.url 72 | }; 73 | 74 | renderer.renderToString(context, (err, html) => { 75 | if (err) { 76 | if (err.url) { 77 | res.redirect(err.url); 78 | } else { 79 | // Render Error Page or Redirect 80 | res.status(500).end("500 | Internal Server Error"); 81 | console.error(`error during render : ${req.url}`); 82 | console.error(err.stack); 83 | } 84 | } 85 | res.status(context.HTTPStatus || 200); 86 | res.send(html); 87 | }); 88 | }); 89 | 90 | module.exports = app; 91 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramsaylanier/wordexpress-starter-vue/3fa7189ebd6a13b96db7c3871b90bbc52ec0ec9e/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/page_header_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramsaylanier/wordexpress-starter-vue/3fa7189ebd6a13b96db7c3871b90bbc52ec0ec9e/src/assets/page_header_bg.png -------------------------------------------------------------------------------- /src/assets/post_header_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramsaylanier/wordexpress-starter-vue/3fa7189ebd6a13b96db7c3871b90bbc52ec0ec9e/src/assets/post_header_bg.png -------------------------------------------------------------------------------- /src/core/app.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import createStore from "./vuex/store"; 3 | import createRouter from "./router.js"; 4 | import { sync } from "vuex-router-sync"; 5 | import { createProvider } from "./vue-apollo.js"; 6 | import VueResource from "vue-resource"; 7 | import WordExpressPlugin from "./plugin"; 8 | import { WordExpressShortcodes, WordExpressHelpers } from "wordexpress-tools"; 9 | 10 | // THEME 11 | import App from "@/App.vue"; 12 | import routes from "@/routes/index.js"; 13 | 14 | Vue.use(VueResource); 15 | Vue.use(WordExpressPlugin, { 16 | shortcodes: WordExpressShortcodes, 17 | helpers: WordExpressHelpers 18 | }); 19 | 20 | export function createApp(context) { 21 | const apolloProvider = createProvider({ ssr: context.ssr }).provide(); 22 | const router = createRouter(routes); 23 | const store = createStore(); 24 | sync(store, router); 25 | const app = new Vue({ 26 | el: "#app", 27 | store, 28 | router, 29 | provide: apolloProvider, 30 | ...App, 31 | render: h => h(App) 32 | }); 33 | return { app, router, store, apolloProvider }; 34 | } 35 | -------------------------------------------------------------------------------- /src/core/graphql/category.gql: -------------------------------------------------------------------------------- 1 | query Category($term_id: Int!){ 2 | category(term_id: $term_id){ 3 | name 4 | slug 5 | posts{ 6 | id 7 | post_name 8 | post_date 9 | post_title 10 | post_meta{ 11 | meta_value 12 | meta_key 13 | } 14 | categories{ 15 | term_id 16 | name 17 | } 18 | thumbnail{ 19 | src 20 | sizes{ 21 | size 22 | file 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/core/graphql/menu.gql: -------------------------------------------------------------------------------- 1 | query Menu($menu: String!) { 2 | menus(name: $menu) { 3 | items { 4 | id 5 | order 6 | navitem{ 7 | id 8 | post_title 9 | post_name 10 | post_meta{ 11 | meta_value 12 | meta_key 13 | } 14 | } 15 | children{ 16 | id 17 | linkedId 18 | navitem{ 19 | post_title 20 | post_name 21 | } 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/core/graphql/page.gql: -------------------------------------------------------------------------------- 1 | query Page($name: String){ 2 | post(name: $name){ 3 | id 4 | post_title 5 | post_content 6 | thumbnail{ 7 | src 8 | } 9 | post_meta{ 10 | meta_key 11 | meta_value 12 | } 13 | layout{ 14 | meta_value 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/core/graphql/post.gql: -------------------------------------------------------------------------------- 1 | query Post($name: String) { 2 | post(name: $name){ 3 | id 4 | post_name 5 | post_parent 6 | post_content 7 | post_title 8 | post_date 9 | thumbnail{ 10 | src 11 | sizes{ 12 | size 13 | file 14 | } 15 | } 16 | post_meta{ 17 | meta_value 18 | meta_key 19 | } 20 | author{ 21 | user_nicename 22 | display_name 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/core/graphql/posts.gql: -------------------------------------------------------------------------------- 1 | query Posts($post_type: [String], $limit: Int, $skip: Int, $order: OrderInput) { 2 | posts(post_type: $post_type, limit: $limit, skip: $skip, order: $order){ 3 | id 4 | post_name 5 | post_title 6 | post_date 7 | post_meta{ 8 | meta_value 9 | meta_key 10 | } 11 | categories{ 12 | term_id 13 | name 14 | } 15 | author{ 16 | user_nicename 17 | display_name 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/core/graphql/user.gql: -------------------------------------------------------------------------------- 1 | query User($name: String) { 2 | user(name: $name){ 3 | display_name 4 | user_email 5 | posts{ 6 | post_name 7 | post_title 8 | post_date 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/core/plugin/index.js: -------------------------------------------------------------------------------- 1 | const WordExpressPlugin = { 2 | install(Vue, options) { 3 | const { shortcodes, helpers } = options; 4 | Vue.prototype.$parseContent = function(content) { 5 | return helpers.parseContent(content, shortcodes); 6 | }; 7 | 8 | Vue.prototype.$getThumbnail = function(thumbnail, size) { 9 | return helpers.getThumbnail(thumbnail, size); 10 | }; 11 | 12 | Vue.prototype.$formatDate = function(date) { 13 | return helpers.formatDate(date); 14 | }; 15 | 16 | Vue.prototype.$renderEmbed = function(embed) { 17 | return helpers.renderEmbed(embed); 18 | }; 19 | 20 | Vue.prototype.$getAvatarLink = function(email, size) { 21 | return helpers.getAvatarLink(email, size); 22 | }; 23 | } 24 | }; 25 | 26 | export default WordExpressPlugin; 27 | -------------------------------------------------------------------------------- /src/core/router.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | 4 | Vue.use(Router); 5 | 6 | export function createRouter(routes) { 7 | return new Router({ 8 | mode: "history", 9 | routes 10 | }); 11 | } 12 | 13 | export default createRouter; 14 | -------------------------------------------------------------------------------- /src/core/ssr/entry-client.js: -------------------------------------------------------------------------------- 1 | import { loadAsyncComponents } from "@akryum/vue-cli-plugin-ssr/client"; 2 | import { createApp } from "../app"; 3 | 4 | createApp({ 5 | async beforeApp({ router }) { 6 | const components = await loadAsyncComponents({ router }); 7 | console.log(components); 8 | }, 9 | 10 | afterApp({ app, store }) { 11 | store.replaceState(window.__INITIAL_STATE__); 12 | app.$mount("#app"); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /src/core/ssr/entry-server.js: -------------------------------------------------------------------------------- 1 | import "isomorphic-fetch"; 2 | import { createApp } from "../app"; 3 | 4 | export default context => { 5 | return new Promise(async (resolve, reject) => { 6 | const { app, router, store, apolloProvider } = await createApp(); 7 | 8 | router.push(context.url); 9 | 10 | router.onReady(() => { 11 | const matchedComponents = router.getMatchedComponents(); 12 | 13 | if (!matchedComponents.length) { 14 | // eslint-disable-next-line prefer-promise-reject-errors 15 | return reject({ code: 404 }); 16 | } 17 | 18 | Promise.all([ 19 | // Async data 20 | ...matchedComponents.map(Component => { 21 | if (Component.asyncData) { 22 | return Component.asyncData({ 23 | store, 24 | route: router.currentRoute 25 | }); 26 | } 27 | }), 28 | // Apollo prefetch 29 | apolloProvider.prefetchAll( 30 | { 31 | route: router.currentRoute 32 | }, 33 | matchedComponents 34 | ) 35 | ]).then(() => { 36 | // After all preFetch hooks are resolved, our store is now 37 | // filled with the state needed to render the app. 38 | // When we attach the state to the context, and the `template` option 39 | // is used for the renderer, the state will automatically be 40 | // serialized and injected into the HTML as `window.__INITIAL_STATE__`. 41 | context.state = store.state; 42 | 43 | // Apollo 44 | context.apolloState = apolloProvider.getStates(); 45 | }); 46 | 47 | resolve(app); 48 | }, reject); 49 | }); 50 | }; 51 | -------------------------------------------------------------------------------- /src/core/vue-apollo.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueApollo from "vue-apollo"; 3 | import { 4 | createApolloClient, 5 | restartWebsockets 6 | } from "vue-cli-plugin-apollo/graphql-client"; 7 | import config from "config"; 8 | 9 | const { wordexpressServerHost } = config; 10 | 11 | // Install the vue plugin 12 | Vue.use(VueApollo); 13 | 14 | // Name of the localStorage item 15 | const AUTH_TOKEN = "apollo-token"; 16 | 17 | // Http endpoint 18 | const httpEndpoint = wordexpressServerHost; 19 | 20 | // Files URL root 21 | export const filesRoot = 22 | process.env.VUE_APP_FILES_ROOT || 23 | httpEndpoint.substr(0, httpEndpoint.indexOf("/graphql")); 24 | 25 | Object.defineProperty(Vue.prototype, "$filesRoot", { 26 | get: () => filesRoot 27 | }); 28 | 29 | // Config 30 | const defaultOptions = { 31 | // You can use `https` for secure connection (recommended in production) 32 | httpEndpoint, 33 | // You can use `wss` for secure connection (recommended in production) 34 | // Use `null` to disable subscriptions 35 | wsEndpoint: null, 36 | // LocalStorage token 37 | tokenName: AUTH_TOKEN, 38 | // Enable Automatic Query persisting with Apollo Engine 39 | persisting: false, 40 | // Use websockets for everything (no HTTP) 41 | // You need to pass a `wsEndpoint` for this to work 42 | websocketsOnly: false, 43 | // Is being rendered on the server? 44 | ssr: true 45 | 46 | // Override default http link 47 | // link: myLink 48 | 49 | // Override default cache 50 | // cache: myCache 51 | 52 | // Override the way the Authorization header is set 53 | // getAuth: (tokenName) => ... 54 | 55 | // Additional ApolloClient options 56 | // apollo: { ... } 57 | 58 | // Client local data (see apollo-link-state) 59 | // clientState: { resolvers: { ... }, defaults: { ... } } 60 | }; 61 | 62 | // Call this in the Vue app file 63 | export function createProvider(options = {}) { 64 | // Create apollo client 65 | const { apolloClient, wsClient } = createApolloClient({ 66 | ...defaultOptions, 67 | ...options 68 | }); 69 | apolloClient.wsClient = wsClient; 70 | 71 | // Create vue apollo provider 72 | const apolloProvider = new VueApollo({ 73 | defaultClient: apolloClient, 74 | defaultOptions: { 75 | $query: { 76 | // fetchPolicy: 'cache-and-network', 77 | } 78 | }, 79 | errorHandler(error) { 80 | // eslint-disable-next-line no-console 81 | console.log( 82 | "%cError", 83 | "background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;", 84 | error.message 85 | ); 86 | } 87 | }); 88 | 89 | return apolloProvider; 90 | } 91 | 92 | // Manually call this when user log in 93 | export async function onLogin(apolloClient, token) { 94 | localStorage.setItem(AUTH_TOKEN, token); 95 | if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient); 96 | try { 97 | await apolloClient.resetStore(); 98 | } catch (e) { 99 | // eslint-disable-next-line no-console 100 | console.log("%cError on cache reset (login)", "color: orange;", e.message); 101 | } 102 | } 103 | 104 | // Manually call this when user log out 105 | export async function onLogout(apolloClient) { 106 | localStorage.removeItem(AUTH_TOKEN); 107 | if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient); 108 | try { 109 | await apolloClient.resetStore(); 110 | } catch (e) { 111 | // eslint-disable-next-line no-console 112 | console.log("%cError on cache reset (logout)", "color: orange;", e.message); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/core/vuex/actions.js: -------------------------------------------------------------------------------- 1 | export const activeMenuName = ({ commit }, name) => 2 | commit("ACTIVE_MENU_NAME", { name: name }); 3 | -------------------------------------------------------------------------------- /src/core/vuex/getters.js: -------------------------------------------------------------------------------- 1 | export const getActiveMenuName = state => state.getActiveMenuName; 2 | -------------------------------------------------------------------------------- /src/core/vuex/store.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import * as actions from "./actions"; 4 | import * as getters from "./getters"; 5 | 6 | Vue.use(Vuex); 7 | 8 | const state = { 9 | activeMenuName: null 10 | }; 11 | 12 | const mutations = { 13 | ACTIVE_MENU_NAME: (state, name) => { 14 | state.activeMenuName = name; 15 | } 16 | }; 17 | 18 | function createStore() { 19 | return new Vuex.Store({ 20 | state, 21 | actions, 22 | mutations, 23 | getters 24 | }); 25 | } 26 | 27 | export default createStore; 28 | -------------------------------------------------------------------------------- /src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ title }} 11 | {{{ renderResourceHints() }}} 12 | {{{ renderStyles() }}} 13 | 14 | 15 | 16 | {{{ renderState() }}} 17 | {{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}} 18 | {{{ renderScripts() }}} 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/themes/basic/App.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 45 | 46 | 73 | -------------------------------------------------------------------------------- /src/themes/basic/components/category/CategoryList.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 53 | 54 | 73 | -------------------------------------------------------------------------------- /src/themes/basic/components/category/containers/CategoryContainer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 36 | -------------------------------------------------------------------------------- /src/themes/basic/components/header/header.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | 23 | 49 | -------------------------------------------------------------------------------- /src/themes/basic/components/icons/icons.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /src/themes/basic/components/menu/menu.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | 23 | 41 | -------------------------------------------------------------------------------- /src/themes/basic/components/menu/menuContainer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 30 | -------------------------------------------------------------------------------- /src/themes/basic/components/menu/menuLink.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 36 | -------------------------------------------------------------------------------- /src/themes/basic/components/page/PageHeader.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 44 | -------------------------------------------------------------------------------- /src/themes/basic/components/page/layouts/DefaultPage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 42 | -------------------------------------------------------------------------------- /src/themes/basic/components/page/layouts/PageWithHeader.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 39 | 40 | 80 | -------------------------------------------------------------------------------- /src/themes/basic/components/page/layouts/PageWithSidebar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 37 | 38 | 80 | -------------------------------------------------------------------------------- /src/themes/basic/components/page/layouts/layouts.js: -------------------------------------------------------------------------------- 1 | import DefaultPage from "./DefaultPage"; 2 | import PageWithHeader from "./PageWithHeader"; 3 | import PageWithSidebar from "./PageWithSidebar"; 4 | import PostList from "../../post/PostList"; 5 | 6 | export default { 7 | DefaultPage, 8 | PageWithHeader, 9 | PageWithSidebar, 10 | PostList 11 | }; 12 | -------------------------------------------------------------------------------- /src/themes/basic/components/page/page.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 76 | 77 | 84 | -------------------------------------------------------------------------------- /src/themes/basic/components/post/PostContent.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 40 | 41 | 141 | -------------------------------------------------------------------------------- /src/themes/basic/components/post/PostList.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 54 | 55 | 74 | -------------------------------------------------------------------------------- /src/themes/basic/components/post/PostListItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | 22 | 65 | -------------------------------------------------------------------------------- /src/themes/basic/components/post/PostSingle.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | 31 | 54 | -------------------------------------------------------------------------------- /src/themes/basic/components/post/containers/PostContainer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 36 | -------------------------------------------------------------------------------- /src/themes/basic/components/post/containers/PostsContainer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 37 | -------------------------------------------------------------------------------- /src/themes/basic/routes/index.js: -------------------------------------------------------------------------------- 1 | import Page from "../components/page/page"; 2 | import Post from "../components/post/containers/PostContainer"; 3 | import Category from "../components/category/containers/CategoryContainer"; 4 | 5 | const routes = [ 6 | { 7 | path: "/", 8 | name: "Home", 9 | component: Page 10 | }, 11 | { 12 | path: "/:name", 13 | name: "Page", 14 | component: Page 15 | }, 16 | { 17 | path: "/post/:postname", 18 | name: "Post", 19 | component: Post 20 | }, 21 | { 22 | path: "/category/:id", 23 | name: "Category", 24 | component: Category 25 | } 26 | ]; 27 | 28 | export default routes; 29 | -------------------------------------------------------------------------------- /src/themes/basic/styles/colors.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #B60FE0; 2 | $secondary-color: #acf3a7; 3 | $accent-color: #F53C76; 4 | $secondary-accent-color: #FF594F; 5 | $dark-color: #380436; 6 | $light-grey-color: #f3f3f3; 7 | 8 | $colors: $primary-color, $secondary-color, $accent-color, $secondary-accent-color -------------------------------------------------------------------------------- /src/themes/basic/styles/reset.css: -------------------------------------------------------------------------------- 1 | /** * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) * http://cssreset.com */html, body, div, span, applet, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote, pre,a, abbr, acronym, address, big, cite, code,del, dfn, em, img, ins, kbd, q, s, samp,small, strike, strong, sub, sup, tt, var,b, u, i, center,dl, dt, dd, ol, ul, li,fieldset, form, label, legend,table, caption, tbody, tfoot, thead, tr, th, td,article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary,time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline;}/* HTML5 display-role reset for older browsers */article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block;}body { line-height: 1;}ol, ul { list-style: none;}blockquote, q { quotes: none;}blockquote:before, blockquote:after,q:before, q:after { content: ''; content: none;}table { border-collapse: collapse; border-spacing: 0;} 2 | -------------------------------------------------------------------------------- /src/themes/basic/styles/typography.css: -------------------------------------------------------------------------------- 1 | html {font-size: 1.125em;} 2 | 3 | body { 4 | font-family: var(--copy-font); 5 | font-weight: 400; 6 | line-height: 1.45; 7 | } 8 | 9 | h1, h2, h3, h4, h5 { 10 | margin: 1.414em 0 0.5em; 11 | line-height: 1.2; 12 | font-weight: 700; 13 | font-family: var(--heading-font); 14 | color: var(--dark-color); 15 | } 16 | 17 | h1 { 18 | margin-top: 0; 19 | font-size: 3.998em; 20 | } 21 | 22 | h2 {font-size: 2.827em;} 23 | 24 | h3 {font-size: 1.999em;} 25 | 26 | h4 {font-size: 1.414em;} 27 | 28 | small, .font_small {font-size: 0.707em;} 29 | 30 | hr { 31 | border: 1px solid; 32 | margin: -1px 0; 33 | } 34 | 35 | p { 36 | margin-bottom: 1.3em; 37 | font-family: var(--copy-font); 38 | } 39 | 40 | b, strong, em, small, code { 41 | line-height: 1; 42 | } 43 | 44 | sup { 45 | line-height: 0; 46 | position: relative; 47 | vertical-align: baseline; 48 | top: -0.5em; 49 | } 50 | 51 | pre{ 52 | font-family: var(--mono-font); 53 | font-size: 1rem; 54 | padding: 1rem; 55 | border: 1px solid darken(white, 5%); 56 | background-color: darken(white, 10%); 57 | font-size: .8rem; 58 | overflow: auto; 59 | margin-bottom: 1.5rem; 60 | } 61 | 62 | pre p{ 63 | font-family: var(--mono-font); 64 | margin: 0; 65 | } 66 | 67 | sub { 68 | bottom: -0.25em; 69 | } 70 | 71 | em{ 72 | font-style: italic; 73 | } 74 | 75 | strong{ 76 | font-weight: 700; 77 | } 78 | 79 | figcaption{ 80 | font-size: .9rem; 81 | font-style: italic; 82 | } 83 | 84 | .sans{ 85 | font-family: var(--heading-font); 86 | } 87 | 88 | .serif{ 89 | font-family: var(--copy-font); 90 | } 91 | 92 | .black{ 93 | font-weight: 900; 94 | } 95 | 96 | .bold{ 97 | font-weight: 700; 98 | } 99 | 100 | .thin{ 101 | font-weight: 100; 102 | } 103 | 104 | .uppercase{ 105 | text-transform: uppercase; 106 | } 107 | 108 | .center{ 109 | text-align: center; 110 | } 111 | -------------------------------------------------------------------------------- /src/themes/multi-user/App.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 45 | 46 | 73 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/author/AuthorSingle.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 63 | 97 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/author/containers/AuthorContainer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/category/CategoryList.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 53 | 54 | 73 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/category/containers/CategoryContainer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 36 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/header/header.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | 23 | 49 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/icons/icons.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/menu/menu.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | 23 | 41 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/menu/menuContainer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 30 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/menu/menuLink.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 36 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/page/PageHeader.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 40 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/page/layouts/DefaultPage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 42 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/page/layouts/PageWithHeader.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 41 | 42 | 82 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/page/layouts/PageWithSidebar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 37 | 38 | 80 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/page/layouts/layouts.js: -------------------------------------------------------------------------------- 1 | import DefaultPage from "./DefaultPage"; 2 | import PageWithHeader from "./PageWithHeader"; 3 | import PageWithSidebar from "./PageWithSidebar"; 4 | import PostList from "../../post/PostList"; 5 | 6 | export default { 7 | DefaultPage, 8 | PageWithHeader, 9 | PageWithSidebar, 10 | PostList 11 | }; 12 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/page/page.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 77 | 78 | 85 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/post/PostContent.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 40 | 41 | 141 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/post/PostList.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 56 | 57 | 76 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/post/PostListItem.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 27 | 28 | 69 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/post/PostSingle.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 40 | 41 | 90 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/post/containers/PostContainer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 36 | -------------------------------------------------------------------------------- /src/themes/multi-user/components/post/containers/PostsContainer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 38 | -------------------------------------------------------------------------------- /src/themes/multi-user/routes/index.js: -------------------------------------------------------------------------------- 1 | import Page from "../components/page/page"; 2 | import Post from "../components/post/containers/PostContainer"; 3 | import Category from "../components/category/containers/CategoryContainer"; 4 | import Author from "../components/author/AuthorSingle"; 5 | 6 | const routes = [ 7 | { 8 | path: "/", 9 | name: "Home", 10 | component: Page 11 | }, 12 | { 13 | path: "/:name", 14 | name: "Page", 15 | component: Page 16 | }, 17 | { 18 | path: "/post/:postname", 19 | name: "Post", 20 | component: Post 21 | }, 22 | { 23 | path: "/category/:id", 24 | name: "Category", 25 | component: Category 26 | }, 27 | { 28 | path: "/author/:name", 29 | name: "Author", 30 | component: Author 31 | } 32 | ]; 33 | 34 | export default routes; 35 | -------------------------------------------------------------------------------- /src/themes/multi-user/styles/colors.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #B60FE0; 2 | $secondary-color: #acf3a7; 3 | $accent-color: #F53C76; 4 | $secondary-accent-color: #FF594F; 5 | $dark-color: #380436; 6 | $light-grey-color: #f3f3f3; 7 | 8 | $colors: $primary-color, $secondary-color, $accent-color, $secondary-accent-color -------------------------------------------------------------------------------- /src/themes/multi-user/styles/reset.css: -------------------------------------------------------------------------------- 1 | /** * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) * http://cssreset.com */html, body, div, span, applet, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote, pre,a, abbr, acronym, address, big, cite, code,del, dfn, em, img, ins, kbd, q, s, samp,small, strike, strong, sub, sup, tt, var,b, u, i, center,dl, dt, dd, ol, ul, li,fieldset, form, label, legend,table, caption, tbody, tfoot, thead, tr, th, td,article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary,time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline;}/* HTML5 display-role reset for older browsers */article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block;}body { line-height: 1;}ol, ul { list-style: none;}blockquote, q { quotes: none;}blockquote:before, blockquote:after,q:before, q:after { content: ''; content: none;}table { border-collapse: collapse; border-spacing: 0;} 2 | -------------------------------------------------------------------------------- /src/themes/multi-user/styles/typography.css: -------------------------------------------------------------------------------- 1 | html {font-size: 1.125em;} 2 | 3 | body { 4 | font-family: var(--copy-font); 5 | font-weight: 400; 6 | line-height: 1.45; 7 | } 8 | 9 | h1, h2, h3, h4, h5 { 10 | margin: 1.414em 0 0.5em; 11 | line-height: 1.2; 12 | font-weight: 700; 13 | font-family: var(--heading-font); 14 | color: var(--dark-color); 15 | } 16 | 17 | h1 { 18 | margin-top: 0; 19 | font-size: 3.998em; 20 | } 21 | 22 | h2 {font-size: 2.827em;} 23 | 24 | h3 {font-size: 1.999em;} 25 | 26 | h4 {font-size: 1.414em;} 27 | 28 | small, .font_small {font-size: 0.707em;} 29 | 30 | hr { 31 | border: 1px solid; 32 | margin: -1px 0; 33 | } 34 | 35 | p { 36 | margin-bottom: 1.3em; 37 | font-family: var(--copy-font); 38 | } 39 | 40 | b, strong, em, small, code { 41 | line-height: 1; 42 | } 43 | 44 | sup { 45 | line-height: 0; 46 | position: relative; 47 | vertical-align: baseline; 48 | top: -0.5em; 49 | } 50 | 51 | pre{ 52 | font-family: var(--mono-font); 53 | font-size: 1rem; 54 | padding: 1rem; 55 | border: 1px solid darken(white, 5%); 56 | background-color: darken(white, 10%); 57 | font-size: .8rem; 58 | overflow: auto; 59 | margin-bottom: 1.5rem; 60 | } 61 | 62 | pre p{ 63 | font-family: var(--mono-font); 64 | margin: 0; 65 | } 66 | 67 | sub { 68 | bottom: -0.25em; 69 | } 70 | 71 | em{ 72 | font-style: italic; 73 | } 74 | 75 | strong{ 76 | font-weight: 700; 77 | } 78 | 79 | figcaption{ 80 | font-size: .9rem; 81 | font-style: italic; 82 | } 83 | 84 | .sans{ 85 | font-family: var(--heading-font); 86 | } 87 | 88 | .serif{ 89 | font-family: var(--copy-font); 90 | } 91 | 92 | .black{ 93 | font-weight: 900; 94 | } 95 | 96 | .bold{ 97 | font-weight: 700; 98 | } 99 | 100 | .thin{ 101 | font-weight: 100; 102 | } 103 | 104 | .uppercase{ 105 | text-transform: uppercase; 106 | } 107 | 108 | .center{ 109 | text-align: center; 110 | } 111 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | }, 5 | rules: { 6 | 'import/no-extraneous-dependencies': 'off' 7 | } 8 | } -------------------------------------------------------------------------------- /tests/unit/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from "@vue/test-utils"; 2 | import HelloWorld from "@/components/HelloWorld.vue"; 3 | 4 | describe("HelloWorld.vue", () => { 5 | it("renders props.msg when passed", () => { 6 | const msg = "new message"; 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }); 10 | expect(wrapper.text()).toMatch(msg); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const VueSSRServerPlugin = require("vue-server-renderer/server-plugin"); 2 | const VueSSRClientPlugin = require("vue-server-renderer/client-plugin"); 3 | const nodeExternals = require("webpack-node-externals"); 4 | const merge = require("lodash.merge"); 5 | const { theme } = require("./config/client.json"); 6 | const path = require("path"); 7 | 8 | console.log(`Current Theme: ${theme}`); 9 | 10 | const TARGET_NODE = process.env.WEBPACK_TARGET === "node"; 11 | 12 | const createApiFile = TARGET_NODE 13 | ? "./create-api-server.js" 14 | : "./create-api-client.js"; 15 | 16 | const target = TARGET_NODE ? "server" : "client"; 17 | 18 | module.exports = { 19 | pluginOptions: { 20 | ssr: { 21 | entry: target => `./src/core/ssr/entry-${target}` 22 | } 23 | }, 24 | configureWebpack: () => ({ 25 | entry: `./src/core/ssr/entry-${target}`, 26 | target: TARGET_NODE ? "node" : "web", 27 | node: TARGET_NODE ? undefined : false, 28 | plugins: [ 29 | TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin() 30 | ], 31 | externals: TARGET_NODE 32 | ? nodeExternals({ 33 | whitelist: /\.css$/ 34 | }) 35 | : undefined, 36 | output: { 37 | libraryTarget: TARGET_NODE ? "commonjs2" : undefined 38 | }, 39 | optimization: { 40 | splitChunks: undefined 41 | }, 42 | resolve: { 43 | alias: { 44 | "create-api": createApiFile, 45 | "@": path.resolve(__dirname, `./src/themes/${theme}`), 46 | assets: path.resolve(__dirname, "./src/assets"), 47 | core: path.resolve(__dirname, "./src/core"), 48 | config: path.resolve(__dirname, "./config/client.json") 49 | } 50 | } 51 | }), 52 | chainWebpack: config => { 53 | config.module 54 | .rule("vue") 55 | .use("vue-loader") 56 | .tap(options => 57 | merge(options, { 58 | optimizeSSR: true 59 | }) 60 | ); 61 | } 62 | }; 63 | --------------------------------------------------------------------------------