├── src
├── utils
│ ├── cache.js
│ ├── router.js
│ ├── cache-crawler.js
│ ├── store.js
│ └── shared.js
├── svgs
│ ├── arrow-left.svg
│ ├── arrow-right.svg
│ ├── logo.svg
│ └── logo-funkhaus.svg
├── views
│ ├── FrontPage.vue
│ ├── Default.vue
│ └── Archive.vue
├── components
│ ├── SiteHeader.vue
│ ├── gutenberg
│ │ └── Content.vue
│ └── SvgImage.vue
├── styles
│ ├── _base.scss
│ ├── _transitions.scss
│ ├── _vars.scss
│ └── _easings.scss
├── main.js
└── App.vue
├── blocks
├── dist
│ ├── blocks.css
│ └── blocks.js
├── src
│ ├── _index.scss
│ ├── prebuilt
│ │ ├── index.js
│ │ ├── text.js
│ │ └── image.js
│ ├── index.js
│ └── utils
│ │ └── builder.js
├── .babelrc
└── blocks.config.js
├── screenshot.png
├── static
├── images
│ ├── favicon.png
│ ├── icon-touch.png
│ └── logo.svg
├── fonts
│ └── fonts.css
├── css
│ ├── admin.css
│ └── login.css
└── js
│ └── admin.js
├── acf
├── acf-imports.php
└── locations
│ └── page-grandparent.php
├── backstop_data
├── engine_scripts
│ └── chromy
│ │ ├── onBefore.js
│ │ ├── onReady.js
│ │ └── clickAndHoverHelper.js
└── bitmaps_reference
│ ├── VUEPRESS_Front_Page_0_document_1_phone.png
│ └── VUEPRESS_Front_Page_0_document_0_desktop.png
├── style.css
├── .gitignore
├── functions
├── custom-vuehaus-templates.php
├── images.php
├── blocks.php
├── shortcodes.php
├── router.php
├── vh-functions.php
├── meta-boxes.php
├── README.md
├── wp-functions.php
├── rest-easy-filters.php
└── developer-role.php
├── parts
├── font-loader.php
├── ga-tracking.php
├── og-tags.php
└── schema.php
├── LICENSE
├── backstop.json
├── index.php
├── functions.php
├── webpack.config.js
├── package.json
└── README.md
/src/utils/cache.js:
--------------------------------------------------------------------------------
1 | export default {}
2 |
--------------------------------------------------------------------------------
/blocks/dist/blocks.css:
--------------------------------------------------------------------------------
1 | .fh-custom-block {
2 | color: #000000;
3 | }
4 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/funkhaus/vuehaus/HEAD/screenshot.png
--------------------------------------------------------------------------------
/static/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/funkhaus/vuehaus/HEAD/static/images/favicon.png
--------------------------------------------------------------------------------
/static/images/icon-touch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/funkhaus/vuehaus/HEAD/static/images/icon-touch.png
--------------------------------------------------------------------------------
/blocks/src/_index.scss:
--------------------------------------------------------------------------------
1 | @import 'src/styles/vars';
2 |
3 | .fh-custom-block {
4 | color: $black;
5 | }
6 |
--------------------------------------------------------------------------------
/blocks/src/prebuilt/index.js:
--------------------------------------------------------------------------------
1 | import text from './text'
2 | import image from './image'
3 |
4 | export default {
5 | text,
6 | image
7 | }
8 |
--------------------------------------------------------------------------------
/acf/acf-imports.php:
--------------------------------------------------------------------------------
1 | ' + scenario.label)
3 | require('./clickAndHoverHelper')(chromy, scenario)
4 | }
5 |
--------------------------------------------------------------------------------
/src/svgs/arrow-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/svgs/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/views/FrontPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
17 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | Theme Name: Vuehaus
3 | Theme URI: http://funkhaus.us
4 | Description: A Vue theme for WordPress. Depends on "Rest Easy" plugin by Funkhaus to properly work.
5 | Author: Funkhaus
6 | Author URI: http://www.funkhaus.us
7 | Text Domain: vuehaus
8 | Version: 2.0.0
9 | */
10 |
--------------------------------------------------------------------------------
/src/components/SiteHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
10 |
11 |
16 |
--------------------------------------------------------------------------------
/src/views/Default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # build/bundle files
6 | static/build*
7 | static/bundle*
8 | *.map
9 | bundleStats.json
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 |
16 | node_modules
17 | .DS_Store
18 | .deploy.config.js
19 | .deployrc.json
20 |
21 | # backstop testing
22 | backstop_data/*
23 | !backstop_data/bitmaps_reference
24 | !backstop_data/engine_scripts
25 |
--------------------------------------------------------------------------------
/src/styles/_base.scss:
--------------------------------------------------------------------------------
1 | @import 'src/styles/vars';
2 |
3 | body {
4 | background-color: $white;
5 | margin: 0;
6 | -webkit-font-smoothing: antialiased !important;
7 | text-rendering: optimizeLegibility !important;
8 | }
9 | button {
10 | appearance: none;
11 | border: none;
12 | background-color: transparent;
13 | cursor: pointer;
14 | outline: none;
15 | margin: 0;
16 | }
17 |
--------------------------------------------------------------------------------
/blocks/src/index.js:
--------------------------------------------------------------------------------
1 | import buildBlock from './utils/builder'
2 |
3 | // Example block
4 | // buildBlock({
5 | // name: 'Title and Image',
6 | // slug: 'title-and-image',
7 | // description: 'A title and an image.',
8 | // class: 'title-and-image',
9 | // content: [
10 | //
Title ,
11 | // { name: 'content', type: 'text' },
12 | // Image ,
13 | // { name: 'image', type: 'image' }
14 | // ]
15 | // })
16 |
--------------------------------------------------------------------------------
/functions/custom-vuehaus-templates.php:
--------------------------------------------------------------------------------
1 |
2 | WebFontConfig = {
3 | // google: {
4 | // families: []
5 | // },
6 | // typekit: {
7 | // id: ''
8 | // },
9 | // custom: {
10 | // families: [],
11 | // urls: ['/static/fonts/fonts.css']
12 | // }
13 | };
14 |
15 | (function(d) {
16 | var wf = d.createElement('script'), s = d.scripts[0];
17 | wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js';
18 | wf.async = true;
19 | s.parentNode.insertBefore(wf, s);
20 | })(document);
21 |
22 |
--------------------------------------------------------------------------------
/backstop_data/engine_scripts/chromy/clickAndHoverHelper.js:
--------------------------------------------------------------------------------
1 | module.exports = function(chromy, scenario) {
2 | var hoverSelector = scenario.hoverSelector
3 | var clickSelector = scenario.clickSelector
4 | var postInteractionWait = scenario.postInteractionWait // selector [str] | ms [int]
5 |
6 | if (hoverSelector) {
7 | chromy
8 | .wait(hoverSelector)
9 | .rect(hoverSelector)
10 | .result(function(rect) {
11 | chromy.mouseMoved(rect.left, rect.top)
12 | })
13 | }
14 |
15 | if (clickSelector) {
16 | chromy.wait(clickSelector).click(clickSelector)
17 | }
18 |
19 | if (postInteractionWait) {
20 | chromy.wait(postInteractionWait)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/static/fonts/fonts.css:
--------------------------------------------------------------------------------
1 | /*
2 | Load fonts in this file using @font-face
3 | Example:
4 | @font-face {
5 | font-family: 'FontName';
6 | src: url('FontName.eot');
7 | src: url('FontName.eot?#iefix') format('embedded-opentype'),
8 | url('FontName.woff2') format('woff2'),
9 | url('FontName.woff') format('woff'),
10 | url('FontName.ttf') format('truetype');
11 | font-style: normal;
12 | font-weight: 400;
13 | }
14 |
15 | Font Weights:
16 | 100 - Thin
17 | 200 - Extra Light (Ultra Light)
18 | 300 - Light
19 | 400 - Normal
20 | 500 - Medium
21 | 600 - Semi Bold (Demi Bold)
22 | 700 - Bold
23 | 800 - Extra Bold (Ultra Bold)
24 | 900 - Black (Heavy)
25 | */
26 |
27 |
--------------------------------------------------------------------------------
/blocks/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false,
7 | "targets": {
8 | "browsers": [
9 | "last 2 Chrome versions",
10 | "last 2 Firefox versions",
11 | "last 2 Safari versions",
12 | "last 2 iOS versions",
13 | "last 1 Android version",
14 | "last 1 ChromeAndroid version",
15 | "ie 11"
16 | ]
17 | }
18 | }
19 | ]
20 | ],
21 | "plugins": [
22 | [
23 | "transform-react-jsx",
24 | {
25 | "pragma": "wp.element.createElement"
26 | }
27 | ],
28 | "transform-object-rest-spread"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/parts/ga-tracking.php:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
24 |
25 |
--------------------------------------------------------------------------------
/src/styles/_transitions.scss:
--------------------------------------------------------------------------------
1 | // Fade
2 | .fade-enter,
3 | .fade-leave-to {
4 | opacity: 0;
5 | }
6 | .fade-enter-active,
7 | .fade-leave-active {
8 | transition: opacity 0.4s;
9 | }
10 |
11 | // Slide left & slide right
12 | .slide-right-enter,
13 | .slide-left-leave-to {
14 | transform: translateX(-100%);
15 | }
16 | .slide-left-enter,
17 | .slide-right-leave-to {
18 | transform: translateX(100%);
19 | }
20 | .slide-right-enter-active,
21 | .slide-right-leave-active,
22 | .slide-left-enter-active,
23 | .slide-left-leave-active {
24 | transition: transform 0.4s;
25 | }
26 |
27 | // Slide up & slide down
28 | .slide-up-enter,
29 | .slide-down-leave-to {
30 | transform: translateY(100%);
31 | }
32 | .slide-down-enter,
33 | .slide-up-leave-to {
34 | transform: translateY(-100%);
35 | }
36 | .slide-up-enter-active,
37 | .slide-up-leave-active,
38 | .slide-down-enter-active,
39 | .slide-down-leave-active {
40 | transition: transform 0.4s;
41 | }
42 |
--------------------------------------------------------------------------------
/src/styles/_vars.scss:
--------------------------------------------------------------------------------
1 | // Sass constants
2 | $white: #ffffff;
3 | $black: #000000;
4 | $font-family: 'Helvetica';
5 | $desktop-padding: 50px;
6 | $mobile-padding: 20px;
7 | $header-height: 80px;
8 |
9 | // Breakpoints
10 | $gt-cinema: 'only screen and (min-width: 1800px)';
11 | $lt-desktop: 'only screen and (max-width: 1100px)';
12 | $lt-phone: 'only screen and (max-width: 750px)';
13 | $lt-phone-landscape: 'only screen and (max-width: 750px) and (orientation: landscape)';
14 | $ipad: 'only screen and (min-device-width : 768px) and (max-device-width : 1024px)';
15 |
16 | // mixins
17 | @mixin fill($position: absolute) {
18 | position: $position;
19 | bottom: 0;
20 | right: 0;
21 | left: 0;
22 | top: 0;
23 | }
24 | @mixin cover {
25 | background-repeat: no-repeat;
26 | background-position: center;
27 | background-size: cover;
28 | }
29 | @mixin contain {
30 | background-repeat: no-repeat;
31 | background-position: center;
32 | background-size: contain;
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Funkhaus
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/backstop.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "VUEPRESS",
3 | "viewports": [
4 | {
5 | "label": "desktop",
6 | "width": 1200,
7 | "height": 672
8 | },
9 | {
10 | "label": "phone",
11 | "width": 320,
12 | "height": 480
13 | }
14 | ],
15 | "onBeforeScript": "chromy/onBefore.js",
16 | "onReadyScript": "chromy/onReady.js",
17 | "readySelector": ".container",
18 | "scenarios": [
19 | {
20 | "label": "Front Page",
21 | "url": "http://example.local"
22 | }
23 | ],
24 | "paths": {
25 | "bitmaps_reference": "backstop_data/bitmaps_reference",
26 | "bitmaps_test": "backstop_data/bitmaps_test",
27 | "engine_scripts": "backstop_data/engine_scripts",
28 | "html_report": "backstop_data/html_report",
29 | "ci_report": "backstop_data/ci_report"
30 | },
31 | "misMatchThreshold": 1.0,
32 | "report": ["browser"],
33 | "engine": "chrome",
34 | "asyncCaptureLimit": 5,
35 | "asyncCompareLimit": 50,
36 | "debug": false,
37 | "debugWindow": false
38 | }
39 |
--------------------------------------------------------------------------------
/blocks/src/prebuilt/text.js:
--------------------------------------------------------------------------------
1 | const { RichText } = wp.editor
2 |
3 | export default {
4 | attributes: child => {
5 | return {
6 | type: 'array',
7 | source: 'children',
8 | selector: `.${child.name}`
9 | }
10 | },
11 | edit: (props, child) => {
12 | const { attributes, className, setAttributes } = props
13 | const content = attributes[child.name]
14 |
15 | const classes = `${child.name} ${className}`
16 |
17 | function onChangeContent(newContent) {
18 | setAttributes({ [child.name]: newContent })
19 | }
20 |
21 | return (
22 |
28 | )
29 | },
30 | save: (props, child) => {
31 | // data attributes passed from editor
32 | const { attributes } = props
33 |
34 | // get content and classes relevant to this particular block
35 | const content = attributes[child.name]
36 | const classes = `${child.name}`
37 |
38 | return (
39 |
40 | )
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/views/Archive.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
21 |
22 |
23 |
24 |
54 |
55 |
57 |
--------------------------------------------------------------------------------
/blocks/blocks.config.js:
--------------------------------------------------------------------------------
1 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
2 | let webpack = require('webpack'),
3 | NODE_ENV = process.env.NODE_ENV || 'development',
4 | webpackConfig = {
5 | entry: ['./blocks/src/index.js', './blocks/src/_index.scss'],
6 | output: {
7 | path: __dirname,
8 | filename: './dist/blocks.js'
9 | },
10 | module: {
11 | loaders: [
12 | {
13 | test: /.js$/,
14 | loader: 'babel-loader',
15 | exclude: /node_modules/
16 | },
17 | {
18 | test: /.scss$/,
19 | loader: ExtractTextPlugin.extract({
20 | fallbackLoader: 'style-loader',
21 | loader: 'css-loader!sass-loader'
22 | })
23 | }
24 | ]
25 | },
26 | plugins: [
27 | new webpack.DefinePlugin({
28 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV)
29 | }),
30 | new ExtractTextPlugin('./dist/blocks.css')
31 | ]
32 | }
33 | if ('production' === NODE_ENV) {
34 | webpackConfig.plugins.push(new webpack.optimize.UglifyJsPlugin())
35 | }
36 |
37 | module.exports = webpackConfig
38 |
--------------------------------------------------------------------------------
/src/styles/_easings.scss:
--------------------------------------------------------------------------------
1 | $easeInSine: cubic-bezier(0.47, 0, 0.745, 0.715);
2 | $easeOutSine: cubic-bezier(0.39, 0.575, 0.565, 1);
3 | $easeInOutSine: cubic-bezier(0.39, 0.575, 0.565, 1);
4 |
5 | $easeInQuad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
6 | $easeOutQuad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
7 | $easeInOutQuad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
8 |
9 | $easeInCubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
10 | $easeOutCubic: cubic-bezier(0.215, 0.61, 0.355, 1);
11 | $easeInOutCubic: cubic-bezier(0.215, 0.61, 0.355, 1);
12 |
13 | $easeInQuart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
14 | $easeOutQuart: cubic-bezier(0.165, 0.84, 0.44, 1);
15 | $easeInOutQuart: cubic-bezier(0.165, 0.84, 0.44, 1);
16 |
17 | $easeInQuint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
18 | $easeOutQuint: cubic-bezier(0.23, 1, 0.32, 1);
19 | $easeInOutQuint: cubic-bezier(0.23, 1, 0.32, 1);
20 |
21 | $easeInExpo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
22 | $easeOutExpo: cubic-bezier(0.19, 1, 0.22, 1);
23 | $easeInOutExpo: cubic-bezier(0.19, 1, 0.22, 1);
24 |
25 | $easeInCirc: cubic-bezier(0.6, 0.04, 0.98, 0.335);
26 | $easeOutCirc: cubic-bezier(0.075, 0.82, 0.165, 1);
27 | $easeInOutCirc: cubic-bezier(0.075, 0.82, 0.165, 1);
28 |
29 | $easeInBack: cubic-bezier(0.6, -0.28, 0.735, 0.045);
30 | $easeOutBack: cubic-bezier(0.175, 0.885, 0.32, 1.275);
31 | $easeInOutBack: cubic-bezier(0.68, -0.55, 0.265, 1.55);
32 |
33 | $easeInOutFast: cubic-bezier(1, 0, 0, 1);
34 |
35 | $authenticMotion: cubic-bezier(0.4, 0, 0.2, 1);
36 |
--------------------------------------------------------------------------------
/functions/blocks.php:
--------------------------------------------------------------------------------
1 | 'vuehaus-block-style',
27 | 'editor_script' => 'vuehaus-block-registration',
28 | )
29 | );
30 |
31 | // Add this theme to the custom block categories
32 | add_filter( 'block_categories', function( $categories ) {
33 | return array_merge(
34 | $categories,
35 | array(
36 | array(
37 | 'slug' => 'custom-fh',
38 | 'title' => __( get_bloginfo('name') ),
39 | ),
40 | )
41 | );
42 | }, 10, 2 );
43 | }
44 | add_action('admin_init', 'register_vuehaus_blocks');
45 |
--------------------------------------------------------------------------------
/src/components/gutenberg/Content.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
57 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
10 |
11 | prefix="og: http://ogp.me/ns#">
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | >
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/functions.php:
--------------------------------------------------------------------------------
1 | ''
14 | ), $atts));
15 |
16 | // Props to pass to Vue component
17 | $props = 'title="' . $title . '"';
18 | $content = custom_filter_shortcode_text($content);
19 |
20 | return ''. $content .' ';
21 | }
22 | //add_shortcode( 'shortcode-name', 'custom_shortcode_function' );
23 |
24 |
25 | /*
26 | * Creates a an [svg-image] shortcode so a user can add SVGs into the editor correctly
27 | */
28 | function add_svg_image_shortcode( $atts ) {
29 |
30 | extract(shortcode_atts(array(
31 | 'src' => ''
32 | ), $atts));
33 |
34 | $props = 'src="' . $src . '"';
35 |
36 | return ' ';
37 | }
38 | //add_shortcode( 'svg-image', 'add_svg_image_shortcode' );
39 |
40 |
41 | /**
42 | * Utility function to clean up the way WordPress auto formats text in a shortcode.
43 | *
44 | * @param string $text A stirng of HTML text
45 | */
46 | function custom_filter_shortcode_text($text = '') {
47 | // Replace all the poorly formatted P tags that WP adds by default.
48 | $tags = array("", "
");
49 | $text = str_replace($tags, "\n", $text);
50 |
51 | // Remove any BR tags
52 | $tags = array(" ", " ", " ");
53 | $text = str_replace($tags, "", $text);
54 |
55 | // Add back in the P and BR tags again, this will remove empty ones
56 | return apply_filters('the_content', $text);
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/SvgImage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
70 |
--------------------------------------------------------------------------------
/src/svgs/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/static/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/blocks/src/utils/builder.js:
--------------------------------------------------------------------------------
1 | // prep prebuilt components
2 | import prebuilt from '../prebuilt'
3 |
4 | const { registerBlockType } = wp.blocks
5 | const { RichText } = wp.editor
6 |
7 | export default (opts = {}) => {
8 | const settings = {
9 | // defaults
10 | slug: 'block-slug',
11 | name: 'Example',
12 | description: '',
13 | icon: 'format-gallery',
14 | content: [],
15 | class: 'block-class',
16 |
17 | // passed values
18 | ...opts
19 | }
20 |
21 | // build attributes according to content children
22 | const attributes = {}
23 | settings.content.map(child => {
24 | if (child.type && prebuilt[child.type]) {
25 | attributes[child.name] = prebuilt[child.type].attributes(child)
26 | }
27 | })
28 |
29 | // register desired block
30 | registerBlockType(`custom-fh/${settings.slug}`, {
31 | title: settings.name,
32 | description: settings.description,
33 | icon: settings.icon,
34 | category: 'custom-fh',
35 |
36 | // use attributes defined above
37 | attributes,
38 |
39 | // Editor
40 | edit(props) {
41 | const { attributes, className, setAttributes } = props
42 | const classes = `${settings.class} fh-custom-block`
43 |
44 | const output = settings.content
45 | .map(child => {
46 | if (child.type && prebuilt[child.type]) {
47 | return prebuilt[child.type].edit(props, child)
48 | }
49 |
50 | return child
51 | })
52 | .filter(x => x)
53 |
54 | return {output}
55 | },
56 |
57 | // On save
58 | save(props) {
59 | const output = settings.content
60 | .map(child => {
61 | if (child.type && prebuilt[child.type]) {
62 | return prebuilt[child.type].save(props, child)
63 | }
64 |
65 | return null
66 | })
67 | .filter(x => x)
68 |
69 | return {output}
70 | }
71 | })
72 | }
73 |
--------------------------------------------------------------------------------
/parts/og-tags.php:
--------------------------------------------------------------------------------
1 | post_content) );
28 | }
29 | }
30 |
31 | // Remove any links, tags or line breaks from summary
32 | $summary = strip_tags($summary);
33 | $summary = esc_attr($summary);
34 | $summary = preg_replace('!\s+!', ' ', $summary);
35 |
36 | /*
37 | * Build permalink URL
38 | */
39 | $url = get_permalink($post->ID);
40 | if( is_front_page() ) {
41 | $url = get_bloginfo('url');
42 | }
43 | ?>
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/acf/locations/page-grandparent.php:
--------------------------------------------------------------------------------
1 | 'page'
18 | ));
19 | if (!empty($groups)) {
20 | foreach(array_keys($groups) as $group_title) {
21 | $posts = acf_extract_var($groups, $group_title);
22 | foreach(array_keys($posts) as $post_id) {
23 | $posts[$post_id] = acf_get_post_title($posts[$post_id]);
24 | };
25 | $choices = $posts;
26 | }
27 | }
28 | // end of copy from ACF
29 | return $choices;
30 | }
31 | add_filter('acf/location/rule_types', 'acf_location_rules_page_grandparent');
32 |
33 |
34 | function acf_location_rules_match_page_grandparent($match, $rule, $options) {
35 | // this code is with inspiration from
36 | // acf_location::rule_match_page_parent()
37 | // with addition of adding grandparent check
38 | if(!$options['post_id']) {
39 | return false;
40 | }
41 | $post_grandparent = 0;
42 | $post = get_post($options['post_id']);
43 | if ($post->post_parent) {
44 | $parent = get_post($post->post_parent);
45 | if ($parent->post_parent) {
46 | $post_grandparent = $parent->post_parent;
47 | }
48 | }
49 | if (isset($options['page_parent']) && $options['page_parent']) {
50 | $parent = get_post($options['page_parent']);
51 | if ($parent->post_parent) {
52 | $post_grandparent = $parent->post_parent;
53 | }
54 | }
55 | if (!$post_grandparent) {
56 | return false;
57 | }
58 | if ($rule['operator'] == "==") {
59 | $match = ($post_grandparent == $rule['value']);
60 | } elseif ($rule['operator'] == "!=") {
61 | $match = ($post_grandparent != $rule['value']);
62 | }
63 | return $match;
64 | }
65 | add_filter('acf/location/rule_match/page_grandparent', 'acf_location_rules_match_page_grandparent', 10, 3);
66 |
--------------------------------------------------------------------------------
/static/css/admin.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Style Dashboard
3 | * By Drew Baker - 2011
4 | * drew@funkhuasdesign.com
5 | *
6 | */
7 |
8 | /*
9 | * Logo Icon
10 | */
11 | /* This should be 16px X 16px */
12 | #wpwrap #wpadminbar #wp-admin-bar-wp-logo > .ab-item {
13 | background-image: url(../images/favicon.png);
14 | background-position: center center;
15 | background-repeat: no-repeat;
16 | background-size: 26px auto;
17 | background-color: #ffffff;
18 | }
19 | #wpwrap #wpadminbar #wp-admin-bar-wp-logo > .ab-item span {
20 | visibility: hidden;
21 | }
22 | body:not(.is-developer) #toplevel_page_edit-post_type-acf-field-group {
23 | display: none;
24 | }
25 |
26 | /*
27 | * Post/Page Meta Boxs
28 | */
29 | .custom-meta label {
30 | width: 280px;
31 | display: inline-block;
32 | text-align: right;
33 | }
34 | .custom-meta input {
35 | width: calc(100% - 320px);
36 | max-width: 800px;
37 | height: 25px;
38 | vertical-align: middle;
39 | }
40 | .custom-meta .for-textarea {
41 | vertical-align: top;
42 | }
43 | .custom-meta textarea {
44 | width: calc(100% - 320px);
45 | max-width: 800px;
46 | }
47 | .custom-meta input[type='checkbox'],
48 | .custom-meta input[type='radio'] {
49 | height: 16px;
50 | width: 16px;
51 | display: inline-block;
52 | vertical-align: middle;
53 | }
54 | /* Image Uploader */
55 | .custom-meta .custom-image-container img {
56 | width: 100%;
57 | height: auto;
58 | }
59 | .custom-meta.custom-image-uploader p:last-child {
60 | margin-bottom: 1px;
61 | }
62 |
63 | /*
64 | * Turn off menus
65 | */
66 | /*
67 | #menu-posts {
68 | display: none;
69 | }
70 | #menu-media {
71 | display: none;
72 | }
73 | #menu-links {
74 | display: none;
75 | }
76 | #menu-pages {
77 | display: none;
78 | }
79 | #menu-appearance {
80 | display: none;
81 | }
82 | #menu-plugins {
83 | display: none;
84 | }
85 | #menu-users {
86 | display: none;
87 | }
88 | #menu-tools {
89 | display: none;
90 | }
91 | #menu-settings {
92 | display: none;
93 | }
94 | */
95 | #menu-comments {
96 | display: none;
97 | }
98 | /* body:not(.is-developer) #toplevel_page_edit-post_type-acf-field-group {
99 | display: none;
100 | } */
101 |
--------------------------------------------------------------------------------
/functions/router.php:
--------------------------------------------------------------------------------
1 | 'VueComponent',
18 | // '/path/:var' => 'ComponentWithVar'
19 | // '/path/*/:var' => 'WildcardAndVar'
20 | // path_from_dev_id('dev-id') => 'DefinedByDevId',
21 | // path_from_dev_id('dev-id', '/append-me') => 'DevIdPathPlusAppendedString',
22 |
23 | /*
24 | // Common Funkhaus setups
25 | // Directors - redirect to first child
26 | path_from_dev_id('directors') => array(
27 | 'redirect' => get_child_of_dev_id_path('directors')
28 | ),
29 | // Director detail
30 | path_from_dev_id('directors', '/:director') => 'DirectorDetail',
31 | // Reel grid
32 | path_from_dev_id('directors', '/:director/:reel') => 'ReelGrid',
33 | // Video detail
34 | path_from_dev_id('directors', '/:director/:reel/:video') => 'VideoDetail',
35 | // About
36 | path_from_dev_id('about') => 'About',
37 | // Contact
38 | path_from_dev_id('contact') => 'Contact',
39 | // Contact region - redirect to parent
40 | path_from_dev_id('contact', '/:region') => array(
41 | 'redirect' => path_from_dev_id('contact')
42 | ),
43 | */
44 |
45 | // Probably unchanging
46 | '' => 'FrontPage',
47 | '/' . $category_base => 'Archive',
48 | '/(\\d+)/:slug' => array(
49 | 'name' => 'SinglePost',
50 | 'component' => 'Default'
51 | ), // If Permalinks set to "/%post_id%/%postname%/" then this will be a single Blog post
52 | '*' => array(
53 | 'name' => 'Fallback',
54 | 'component' => 'Default'
55 | )
56 |
57 | );
58 |
59 | $jsonData['routes'] = array_merge( get_custom_template_routes(), $programmed_routes );
60 |
61 | return $jsonData;
62 | }
63 | add_filter('rez_build_all_data', 'add_routes_to_json');
64 |
--------------------------------------------------------------------------------
/src/utils/router.js:
--------------------------------------------------------------------------------
1 | /* global jsonData, _get */
2 |
3 | import VueRouter from 'vue-router'
4 | import store from 'src/utils/store'
5 | import Vue from 'vue'
6 |
7 | Vue.use(VueRouter)
8 |
9 | // load all templates in folder
10 | const templates = require.context('src/views', true)
11 |
12 | // build routing table
13 | const routeTable = []
14 | Object.keys(jsonData.routes).forEach(path => {
15 | let routeObject = jsonData.routes[path]
16 |
17 | if (typeof routeObject === 'string') {
18 | routeObject = {
19 | component: routeObject
20 | }
21 | }
22 |
23 | routeObject.path = path
24 |
25 | routeObject.name = routeObject.name || routeObject.component
26 |
27 | // get specified component, fallback to default
28 | let component = templates(`./Default.vue`)
29 | if (templates.keys().indexOf(`./${routeObject.component}.vue`) > -1) {
30 | component = templates(`./${routeObject.component}.vue`)
31 | }
32 |
33 | routeObject.component = component
34 |
35 | // push new route entry to table
36 | routeTable.push(routeObject)
37 | })
38 |
39 | // these two values will determine where we'll scroll on our new page
40 | let targetScroll = null
41 | let savedScroll = null
42 |
43 | const router = new VueRouter({
44 | mode: 'history',
45 | routes: routeTable,
46 | scrollBehavior() {
47 | return targetScroll || { x: 0, y: 0 }
48 | }
49 | })
50 |
51 | router.beforeEach((to, from, next) => {
52 | // Check to see if we're going from page A to B to A again
53 | const firstPagePath = _get(store, 'state.referral.path', '')
54 | const toPath = _get(to, 'path', '')
55 |
56 | if (firstPagePath && toPath && firstPagePath == toPath) {
57 | // if we are, make our target scroll the position we saved from the first time on A
58 | targetScroll = savedScroll
59 | } else {
60 | // if we're not, default to the top of the page
61 | targetScroll = { x: 0, y: 0 }
62 | }
63 |
64 | // Save scroll from previous page
65 | savedScroll = { x: window.scrollX, y: window.scrollY }
66 |
67 | // Replace query data
68 | if (to.path !== from.path) {
69 | store.dispatch('LOAD_AND_REPLACE_QUERYDATA', { path: to.path })
70 | }
71 |
72 | // Update referral route
73 | if (from.name !== null) {
74 | store.commit('UPDATE_REFERRAL_ROUTE', from)
75 | }
76 | next()
77 | })
78 |
79 | export default router
80 |
--------------------------------------------------------------------------------
/blocks/src/prebuilt/image.js:
--------------------------------------------------------------------------------
1 | const { MediaUpload } = wp.editor
2 | const { Button } = wp.components
3 |
4 | export default {
5 | // attributes
6 | attributes: child => {
7 | return {
8 | mediaID: {
9 | type: 'number'
10 | },
11 | mediaURL: {
12 | type: 'string',
13 | source: 'attribute',
14 | selector: 'img',
15 | attribute: 'src'
16 | },
17 | width: {
18 | type: 'number'
19 | },
20 | height: {
21 | type: 'number'
22 | }
23 | }
24 | },
25 |
26 | // editing
27 | edit: (props, child) => {
28 | const { setAttributes, attributes } = props
29 |
30 | // try to find correct attributes, default to empty object
31 | const atts = attributes[child.name] || {}
32 | const { mediaID, mediaURL } = atts
33 |
34 | // selecting the image
35 | function onSelectImage(newImage) {
36 | // console.log(newImage)
37 | setAttributes({
38 | [child.name]: {
39 | mediaURL: newImage.url,
40 | mediaID: newImage.id,
41 | width: newImage.width,
42 | height: newImage.height
43 | }
44 | })
45 | }
46 |
47 | // edit block
48 | return (
49 | (
54 |
60 | {!mediaID ? 'Upload Image' : }
61 |
62 | )}
63 | />
64 | )
65 | },
66 | save: (props, child) => {
67 | const { attributes } = props
68 |
69 | // ignore if we don't have the correct content
70 | if (!attributes || !attributes[child.name]) {
71 | return ''
72 | }
73 |
74 | const { mediaID, mediaURL, width, height } = attributes[child.name]
75 |
76 | if (mediaURL) {
77 | return (
78 |
85 | )
86 | }
87 |
88 | return ''
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/utils/cache-crawler.js:
--------------------------------------------------------------------------------
1 | import cache from 'src/utils/cache'
2 | import store from 'src/utils/store'
3 | import _get from 'lodash/get'
4 |
5 | // flatten n-dimensional array
6 | // https://stackoverflow.com/a/15030117/3856675
7 | function flatten(arr) {
8 | return arr.reduce(function(flat, toFlatten) {
9 | return flat.concat(
10 | Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten
11 | )
12 | }, [])
13 | }
14 |
15 | class CacheCrawler {
16 | constructor() {
17 | this.candidates = []
18 | this.fetching = false
19 | this.index = 0
20 | }
21 |
22 | onNewPage() {
23 | // reset index
24 | this.index = 0
25 |
26 | // links in all site menus
27 | const menus = _get(store, 'state.site.menus', [])
28 | const menuCandidates = menus.map(menu =>
29 | menu.items.map(item => (item.isExternal ? null : item.relativePath))
30 | )
31 | // links in the loop
32 | const loop = _get(store, 'state.loop', [])
33 | const loopCandidates = loop.map(page => {
34 | const output = [page.relativePath]
35 | if (page.related.children && page.related.children.length) {
36 | output.push(
37 | page.related.children.map(child => child.relativePath)
38 | )
39 | }
40 | return output
41 | })
42 |
43 | // list of locations where we want to look for links
44 | const candidateQueue = [menuCandidates, loopCandidates]
45 | this.candidates = flatten(candidateQueue)
46 |
47 | if (!this.fetching) {
48 | this.goFetch()
49 | }
50 | }
51 |
52 | async goFetch() {
53 | this.fetching = true
54 |
55 | if (this.index > this.candidates.length - 1) {
56 | // we've cycled through all available candidates, so exit for now
57 | this.fetching = false
58 | return
59 | }
60 |
61 | const path = this.candidates[this.index++]
62 |
63 | // conditions when we should ignore this path
64 | const ignorePath =
65 | !path ||
66 | typeof path != 'string' ||
67 | !path.length ||
68 | path.startsWith('#')
69 |
70 | if (!ignorePath && !cache[path]) {
71 | // Allows for query strings in path
72 | const connector = path.includes('?') ? '&' : '?'
73 |
74 | const response = await fetch(
75 | `${path}${connector}contentType=json`,
76 | {
77 | credentials: 'same-origin'
78 | }
79 | ).then(res => (res.ok ? res.json() : false))
80 |
81 | // avoid caching non-200 responses
82 | if (response) {
83 | cache[path] = response
84 | }
85 | }
86 |
87 | this.goFetch()
88 | }
89 | }
90 |
91 | export default new CacheCrawler()
92 |
--------------------------------------------------------------------------------
/static/css/login.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Style login page
3 | * By Drew Baker - 2011
4 | * drew@funkhausdesign.com
5 | *
6 | */
7 |
8 | /*
9 | * Import fonts
10 | */
11 | /* @import url("../fonts/fonts.css"); */
12 |
13 | /*
14 | * Globals
15 | */
16 | :root {
17 | --main-color: white;
18 | --accent-color: black;
19 | --text-color: black;
20 | --hover-text-color: #00a0d2;
21 | }
22 | html {
23 | background: var(--main-color);
24 | }
25 | body.login {
26 | font-size: 14px;
27 | background: var(--main-color);
28 | line-height: 1.4;
29 | padding: 0;
30 | margin: 0;
31 | border: none;
32 | width: 100%;
33 | }
34 | a {
35 | outline: none;
36 | }
37 |
38 | /*
39 | * Login Banner Image
40 | */
41 | #login h1 a {
42 | background-image: url(../images/logo.svg);
43 | background-position: center center;
44 | margin: 0;
45 | background-size: auto;
46 | width: auto;
47 | }
48 |
49 | /*
50 | * Login Form
51 | */
52 | #loginform {
53 | box-shadow: none;
54 | background: var(--main-color);
55 | }
56 | #loginform label {
57 | color: var(--text-color);
58 | }
59 | #loginform input[type='text'],
60 | #loginform input[type='password'] {
61 | background: #f2f2f2;
62 | border: 1px solid #cccccc;
63 | font-family: inherit;
64 | padding: 5px 8px;
65 | box-sizing: border-box;
66 | }
67 | #loginform input[type='checkbox'],
68 | #loginform input[type='radio'] {
69 | vertical-align: baseline;
70 | }
71 |
72 | /*
73 | * Login Submit Button
74 | */
75 | #wp-submit {
76 | background: var(--accent-color);
77 | color: var(--main-color);
78 | border: none;
79 | text-shadow: none;
80 | box-shadow: none;
81 | text-transform: uppercase;
82 | border: 2px solid var(--accent-color);
83 | height: initial;
84 | padding-top: 2px;
85 | }
86 | #wp-submit:hover,
87 | #wp-submit:focus {
88 | background: var(--main-color);
89 | color: var(--accent-color);
90 | border: 2px solid var(--text-color);
91 | }
92 |
93 | /*
94 | * "Lost password" link
95 | */
96 | #nav {
97 | text-shadow: none;
98 | }
99 | .login #nav a {
100 | text-decoration: none;
101 | padding: 0 1px;
102 | text-shadow: none;
103 | color: var(--text-color);
104 | text-decoration: underline;
105 | }
106 | .login #nav a:hover,
107 | .login #nav a:focus {
108 | color: var(--hover-text-color) !important;
109 | font-weight: normal;
110 | text-shadow: none;
111 | }
112 |
113 | /*
114 | * "Back to blog" link header
115 | */
116 | #backtoblog {
117 | display: none;
118 | }
119 |
120 | .login .message,
121 | .login #login_error {
122 | background-color: var(--main-color);
123 | color: var(--text-color);
124 | }
125 | .login #login_error a {
126 | color: var(--text-color);
127 | }
128 | .login #login_error a:hover,
129 | .login #login_error a:focus {
130 | color: var(--hover-text-color);
131 | }
132 |
--------------------------------------------------------------------------------
/src/utils/store.js:
--------------------------------------------------------------------------------
1 | /* global jsonData */
2 | import Vuex from 'vuex'
3 | import Vue from 'vue'
4 | import cache from 'src/utils/cache'
5 | import _get from 'lodash/get'
6 | import CacheCrawler from 'src/utils/cache-crawler'
7 |
8 | // add vuex
9 | Vue.use(Vuex)
10 |
11 | export default new Vuex.Store({
12 | state: {
13 | site: jsonData.site,
14 | meta: jsonData.meta,
15 | loop: jsonData.loop,
16 | loaded: true,
17 | menuOpened: false,
18 | referral: null,
19 | currentlyFetching: null
20 | },
21 | mutations: {
22 | REPLACE_QUERYDATA: (state, data) => {
23 | state.site = data.site
24 | state.meta = data.meta
25 | state.loop = data.loop
26 |
27 | // reboot cache-crawler
28 | CacheCrawler.onNewPage()
29 |
30 | return state
31 | },
32 | SET_LOADED: (state, loaded) => {
33 | state.loaded = loaded || false
34 | },
35 | OPEN_MENU: state => {
36 | state.menuOpened = true
37 | },
38 | CLOSE_MENU: state => {
39 | state.menuOpened = false
40 | },
41 | UPDATE_REFERRAL_ROUTE: (state, referral) => {
42 | state.referral = referral
43 | },
44 | SET_CURRENTLY_FETCHING: (state, { path }) => {
45 | state.currentlyFetching = path
46 | }
47 | },
48 | actions: {
49 | LOAD_AND_REPLACE_QUERYDATA: async (context, payload) => {
50 | const path = payload.path || ''
51 |
52 | // flag that we're looking for the given path
53 | context.commit('SET_CURRENTLY_FETCHING', { path })
54 |
55 | // no cache? set it
56 | if (!cache[payload.path]) {
57 | context.commit('SET_LOADED', false)
58 | cache[path] = fetch(`${path}?contentType=json`, {
59 | credentials: 'same-origin'
60 | }).then(r => r.json())
61 | }
62 |
63 | // wait for data
64 | const data = await cache[path]
65 |
66 | // if we're still waiting for the data from this path, replace it
67 | if (context.state.currentlyFetching == path) {
68 | context.commit('REPLACE_QUERYDATA', data)
69 | }
70 |
71 | // notify that we're finished loading
72 | context.commit('SET_LOADED', true)
73 | }
74 | },
75 | getters: {
76 | loading: state => {
77 | return !state.loaded
78 | },
79 | post: state => {
80 | // Return an empty object if we're still loading
81 | if (!state.loaded) {
82 | return {}
83 | }
84 |
85 | // This is a "post" in the sense of a WordPress post - the first result of the Loop
86 | return _get(state.loop, '[0]', {})
87 | },
88 | referralPath: state => {
89 | return _get(state.referral, 'fullPath', '')
90 | }
91 | }
92 | })
93 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | /* global jsonData */
2 | import App from './App.vue'
3 | import Vue from 'vue'
4 | import cache from 'src/utils/cache'
5 | import _kebabCase from 'lodash/kebabCase'
6 |
7 | // Register components in src/
8 | // ===============================
9 | const components = require.context('src/components', true)
10 | components.keys().map(component => {
11 | // turn './ComponentName.vue' into 'component-name'
12 | const componentName = _kebabCase(
13 | component.replace(/^\.\//, '').replace(/\.vue$/, '')
14 | )
15 | // register new component globally
16 | Vue.component(componentName, components(component))
17 | })
18 |
19 | // Register outside components
20 | // ===============================
21 |
22 | // Register fh-components
23 | // Vue.component('a-div', require('fh-components/a-div'))
24 | // Vue.component('flex-text', require('fh-components/flex-text'))
25 | Vue.component('hamburger-button', require('fh-components/hamburger-button'))
26 | Vue.component('image-loader', require('fh-components/image-loader'))
27 | Vue.component('load-on-view', require('fh-components/load-on-view'))
28 | // Vue.component('mailing-list', require('fh-components/mailing-list'))
29 | // Vue.component('menu-item', require('fh-components/wp-menu-item'))
30 | Vue.component('responsive-image', require('fh-components/responsive-image'))
31 | // Vue.component('reveal-footer', require('fh-components/reveal-footer'))
32 | // Vue.component('scroll-to', require('fh-components/scroll-to'))
33 | // Vue.component('slide-show', require('fh-components/slide-show'))
34 | // Vue.component('split-text', require('fh-components/split-text'))
35 | Vue.component('svg-image', require('src/components/SvgImage.vue'))
36 | // Vue.component('sticky-wrap', require('fh-components/sticky-wrap'))
37 | // Vue.component('text-typer', require('fh-components/text-typer'))
38 | // Vue.component('velocity-animate', require('fh-components/velocity-animate'))
39 | // Vue.component('video-stage', require('fh-components/video-stage'))
40 | Vue.component('wp-content', require('fh-components/wp-content'))
41 | Vue.component('wp-menu', require('fh-components/wp-menu'))
42 |
43 | // Register directives
44 | // import animated from 'fh-components/v-animated'
45 | // Vue.directive('animated', animated)
46 | // import draggable from 'fh-components/v-draggable'
47 | // Vue.directive('draggable', draggable)
48 | // import fullHeight from 'fh-components/v-full-height'
49 | // Vue.directive('full-height', fullHeight)
50 | // import inView from 'fh-components/v-in-view'
51 | // Vue.directive('in-view', inView)
52 | // import interact from 'fh-components/v-interact'
53 | // Vue.directive('interact', interact)
54 | // import keyDown from 'fh-components/v-keydown'
55 | // Vue.directive('keydown', keyDown)
56 | // import reverseHover from 'fh-components/v-reverse-hover'
57 | // Vue.directive('reverse-hover', reverseHover)
58 |
59 | // save initial page cache - dumped onto page as `jsonData` by Rest-Easy
60 | cache[window.location.pathname] = Promise.resolve(jsonData)
61 |
62 | // boot app
63 | new Vue(App)
64 |
--------------------------------------------------------------------------------
/src/svgs/logo-funkhaus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
99 |
100 |
105 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
2 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
3 | const resolve = file => path.resolve(__dirname, file)
4 | const webpack = require('webpack')
5 | const path = require('path')
6 |
7 | const isProd = process.env.NODE_ENV === 'production'
8 |
9 | const config = {
10 | entry: ['whatwg-fetch', 'string.prototype.includes', './src/main'],
11 | output: {
12 | path: resolve('static'),
13 | filename: isProd ? 'bundle.js' : 'bundle.dev.js'
14 | },
15 | module: {
16 | rules: [
17 | {
18 | test: /\.vue$/,
19 | loader: 'vue-loader',
20 | options: {
21 | loaders: {
22 | scss: ExtractTextPlugin.extract({
23 | use: isProd
24 | ? 'css-loader?minimize&url=false!sass-loader?minimize'
25 | : 'css-loader?{"sourceMap":true}&url=false!sass-loader?{"sourceMap":true}',
26 | fallback: 'vue-style-loader'
27 | })
28 | },
29 | extractCSS: true,
30 | preserveWhitespace: false,
31 | postcss: [
32 | require('autoprefixer')({
33 | browsers: ['last 5 versions']
34 | })
35 | ]
36 | }
37 | },
38 | {
39 | test: /\.js$/,
40 | loader: 'babel-loader',
41 | exclude: /node_modules/
42 | },
43 | {
44 | test: /\.svg$/,
45 | loader: 'svg-inline-loader?removeSVGTagAttrs=false'
46 | },
47 | {
48 | test: /\.(png|woff|woff2|eot|ttf)$/,
49 | loader: 'url-loader',
50 | options: {
51 | limit: 10000,
52 | name: '[name].[ext]?[hash]'
53 | }
54 | },
55 | {
56 | test: /\.css$/,
57 | use: ExtractTextPlugin.extract({
58 | use: isProd
59 | ? 'css-loader?minimize&url=false'
60 | : 'css-loader?url=false',
61 | fallback: 'vue-style-loader'
62 | })
63 | }
64 | ]
65 | },
66 | resolve: {
67 | modules: [
68 | path.resolve(__dirname, 'app'),
69 | resolve('./'),
70 | 'node_modules'
71 | ],
72 | alias: {
73 | vue$: isProd ? 'vue/dist/vue.min.js' : 'vue/dist/vue.esm.js'
74 | }
75 | },
76 | performance: {
77 | maxEntrypointSize: 500000,
78 | maxAssetSize: 500000,
79 | hints: isProd ? 'warning' : false
80 | },
81 | plugins: [
82 | new ExtractTextPlugin({
83 | filename: 'bundle.css'
84 | }),
85 | new webpack.ProvidePlugin({
86 | _get: ['lodash/get']
87 | })
88 | ]
89 | }
90 |
91 | // production only
92 | if (isProd) {
93 | config.plugins = config.plugins.concat([new UglifyJsPlugin()])
94 |
95 | // dev only
96 | } else {
97 | config.devtool = '#source-map'
98 | config.plugins = config.plugins.concat([
99 | new webpack.EnvironmentPlugin({
100 | NODE_ENV: 'development'
101 | })
102 | ])
103 | }
104 |
105 | module.exports = config
106 |
--------------------------------------------------------------------------------
/functions/vh-functions.php:
--------------------------------------------------------------------------------
1 | 'vuehaus', // Unique ID for hashing notices for multiple instances of TGMPA.
15 | 'default_path' => '', // Default absolute path to bundled plugins.
16 | 'menu' => 'tgmpa-install-plugins', // Menu slug.
17 | 'parent_slug' => 'themes.php', // Parent menu slug.
18 | 'capability' => 'edit_theme_options', // Capability needed to view plugin install page, should be a capability associated with the parent menu used.
19 | 'has_notices' => true, // Show admin notices or not.
20 | 'dismissable' => true, // If false, a user cannot dismiss the nag message.
21 | 'dismiss_msg' => '', // If 'dismissable' is false, this message will be output at top of nag.
22 | 'is_automatic' => false, // Automatically activate plugins after installation or not.
23 | 'message' => '', // Message to output right before the plugins table.
24 | );
25 |
26 | $plugins = array(
27 | array(
28 | 'name' => 'Rest-Easy',
29 | 'slug' => 'rest-easy',
30 | 'source' => 'https://github.com/funkhaus/Rest-Easy/archive/master.zip',
31 | 'version' => $latest_rest_easy
32 | ),
33 | array(
34 | 'name' => 'Nested Pages',
35 | 'slug' => 'wp-nested-pages',
36 | 'required' => false
37 | ),
38 | array(
39 | 'name' => 'Focushaus',
40 | 'slug' => 'focushaus',
41 | 'source' => 'https://github.com/funkhaus/focushaus/archive/master.zip',
42 | 'version' => $latest_focushaus
43 | )
44 | );
45 |
46 | tgmpa( $plugins, $config );
47 | }
48 |
49 | // Register required & recommended plugins
50 | add_action( 'tgmpa_register', 'vuehaus_register_required_plugins' );
51 |
52 |
53 | function get_custom_template_routes(){
54 |
55 | // Find all pages with custom_vuehaus_template set
56 | $args = array(
57 | 'posts_per_page' => -1,
58 | 'orderby' => 'menu_order',
59 | 'order' => 'ASC',
60 | 'meta_key' => 'custom_vuehaus_template',
61 | 'meta_value' => '',
62 | 'post_type' => array('post', 'page')
63 | );
64 | $all_pages_with_custom_templates = get_posts($args);
65 |
66 | // Filter out pages with vuehaus custom template set to Default
67 | $filtered_pages_with_custom_templates = array_filter(
68 | $all_pages_with_custom_templates,
69 | function( $page ){
70 | return $page->custom_vuehaus_template !== 'Default';
71 | }
72 | );
73 |
74 | // Build out router for pages with custom templates
75 | $custom_template_routes = array();
76 | foreach( $filtered_pages_with_custom_templates as $page ){
77 | $page_url = get_permalink($page);
78 | $custom_template_routes[wp_make_link_relative($page_url)] = $page->custom_vuehaus_template;
79 | }
80 |
81 | return $custom_template_routes;
82 | }
83 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuehaus",
3 | "version": "3.0.0",
4 | "description": "Build smooth, responsive Wordpress sites with Vue.",
5 | "main": "src/main.js",
6 | "scripts": {
7 | "dev": "npm run clean && webpack --watch",
8 | "build": "npm run clean && cross-env NODE_ENV=production webpack",
9 | "test": "backstop test",
10 | "clean": "rimraf static/bundle.*",
11 | "deploy": "fh-deploy",
12 | "format": "prettier --write 'src/**/*+(.js|.vue|.scss)'",
13 | "analyze": "webpack --profile --json > bundleStats.json && webpack-bundle-analyzer bundleStats.json",
14 | "precommit": "pretty-quick --staged",
15 | "block-dev": "cross-env BABEL_ENV=default webpack --config blocks/blocks.config.js --watch",
16 | "block-build": "cross-env BABEL_ENV=default NODE_ENV=production webpack --config blocks/blocks.config.js"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git://github.com/funkhaus/vuepress.git"
21 | },
22 | "babel": {
23 | "presets": [
24 | "env",
25 | "stage-0"
26 | ],
27 | "plugins": [
28 | "transform-runtime"
29 | ]
30 | },
31 | "prettier": {
32 | "tabWidth": 4,
33 | "semi": false,
34 | "singleQuote": true
35 | },
36 | "files": [
37 | "blocks/**/*.*",
38 | "functions/**/*.*",
39 | "parts/**/*.*",
40 | "static/**/*.*",
41 | "functions.php",
42 | "index.php",
43 | "style.css",
44 | "screenshot.png",
45 | "screenshot.jpg"
46 | ],
47 | "author": "Funkhaus",
48 | "license": "ISC",
49 | "dependencies": {
50 | "auto-blur": "^1.1.1",
51 | "case": "^1.5.4",
52 | "colors": "^1.1.2",
53 | "fh-components": "^1.2.90",
54 | "imagesloaded": "^4.1.3",
55 | "jquery": "^3.1.1",
56 | "lodash": "^4.17.4",
57 | "scroll-to-element": "^2.0.0",
58 | "string.prototype.includes": "^1.0.0",
59 | "velocity-animate": "^1.5.0",
60 | "vue": "^2.5.13",
61 | "vue-loader": "^11.1.4",
62 | "vue-router": "^2.8.1",
63 | "vuex": "^2.5.0",
64 | "whatwg-fetch": "^2.0.3"
65 | },
66 | "devDependencies": {
67 | "babel-core": "^6.26.0",
68 | "babel-loader": "^6.2.10",
69 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
70 | "babel-plugin-transform-react-jsx": "^6.24.1",
71 | "babel-plugin-transform-runtime": "^6.15.0",
72 | "babel-preset-env": "^1.6.1",
73 | "babel-preset-stage-0": "^6.24.1",
74 | "backstopjs": "^3.8.4",
75 | "compression-webpack-plugin": "^2.0.0",
76 | "cross-env": "^5.2.0",
77 | "css-loader": "^0.26.4",
78 | "extract-text-webpack-plugin": "^2.1.0",
79 | "fh-deploy": "^1.1.5",
80 | "file-loader": "^0.9.0",
81 | "fs-extra": "^5.0.0",
82 | "husky": "^0.14.3",
83 | "node-sass": "^4.7.2",
84 | "optimize-css-assets-webpack-plugin": "^2.0.0",
85 | "postcss-import": "^9.1.0",
86 | "postcss-loader": "^1.3.3",
87 | "postcss-url": "^7.3.0",
88 | "prettier": "^1.14.3",
89 | "pretty-quick": "^1.4.1",
90 | "rimraf": "^2.6.2",
91 | "sass-loader": "^6.0.6",
92 | "style-loader": "^0.13.1",
93 | "svg-inline-loader": "^0.7.1",
94 | "uglifyjs-webpack-plugin": "^1.2.5",
95 | "url-loader": "^1.1.2",
96 | "vue-template-compiler": "^2.5.13",
97 | "webpack": "^2.2.1",
98 | "webpack-bundle-analyzer": "^2.11.1"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/parts/schema.php:
--------------------------------------------------------------------------------
1 | 'http://schema.org',
12 | '@type' => 'WebPage',
13 | 'headline' => get_bloginfo('description'),
14 | 'url' => get_bloginfo('url'),
15 | 'thumbnailUrl' => get_template_directory_uri() . '/screenshot.' . $extension
16 | );
17 |
18 | // helper to generate schema Person object
19 | function schema_generate_person($name){
20 | return array(
21 | '@type' => 'Person',
22 | 'name' => $name
23 | );
24 | }
25 |
26 | // helper to generate schema Organization object
27 | function schema_generate_org($name = null, $logo = null){
28 | return array(
29 | '@type' => 'Organization',
30 | 'name' => $name ? $name : get_bloginfo('name'),
31 | 'logo' => $logo ? $logo : get_template_directory_uri() . '/images/logo.svg'
32 | );
33 | }
34 |
35 | // set some helpful vars
36 | $image_id = false;
37 | $image_url = false;
38 | $image = wp_get_attachment_image_src($image_id, 'social-preview');
39 | if( $image ){
40 | $image_id = get_post_thumbnail_id( $post );
41 | $image_url = reset( $image );
42 | }
43 | $author = get_user_by('ID', $post->post_author);
44 | $queried_object = get_queried_object();
45 | $excerpt = get_the_excerpt($post);
46 |
47 | // build on schema conditionally
48 | switch (true) {
49 |
50 | // is non-home page
51 | case !is_front_page() and is_page():
52 | $schema['headline'] = get_the_title($post);
53 | $schema['url'] = get_the_permalink($post);
54 | $schema['thumbnailUrl'] = $image_url;
55 | $schema['description'] = $excerpt ? $excerpt : get_bloginfo('description');
56 | break;
57 |
58 | // is single post
59 | case is_single():
60 | $schema['@type'] = 'NewsArticle';
61 | $schema['headline'] = get_the_title($post);
62 | $schema['url'] = get_the_permalink($post);
63 | $schema['thumbnailUrl'] = $image_url;
64 | $schema['image'] = $image_url;
65 | $schema['dateCreated'] = get_the_date('Y-m-d');
66 | $schema['datePublished'] = get_the_date('Y-m-d');
67 | $schema['articleSection'] = '';
68 | $schema['description'] = get_the_excerpt($post);
69 | $schema['creator'] = $author ? schema_generate_person($author->display_name) : schema_generate_org();
70 | $schema['author'] = $author ? schema_generate_person($author->display_name) : schema_generate_org();
71 | $schema['publisher'] = schema_generate_org();
72 | $schema['keywords'] = wp_get_post_categories($post->ID, array(
73 | 'fields' => 'names'
74 | ));
75 | break;
76 |
77 | // all archive pages
78 | case is_archive():
79 | $schema['@type'] = 'Blog';
80 |
81 | // is a term archive
82 | if ( property_exists($queried_object, 'term_id') ){
83 | $schema['headline'] = term_description($queried_object->term_id);
84 | $schema['url'] = get_term_link($queried_object->term_id);
85 | $schema['articleSection'] = $queried_object->name;
86 | }
87 | break;
88 |
89 | // blog archive, if different than front page
90 | case is_home() and !is_front_page():
91 | $schema['@type'] = 'Blog';
92 | $schema['url'] = get_the_permalink(get_option('page_for_posts'));
93 | break;
94 |
95 | }
96 |
97 | ?>
98 |
99 |
100 |
103 |
104 |
--------------------------------------------------------------------------------
/static/js/admin.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var funkhausAdmin = {
3 |
4 | init: function() {
5 | funkhausAdmin.showNavMenuUrls();
6 | funkhausAdmin.showAttachmentIds();
7 | funkhausAdmin.autoRefresh();
8 | //funkhausAdmin.secondFeaturedImageUploader();
9 | },
10 |
11 | showNavMenuUrls: function(){
12 |
13 | // Show the URL of menu items when hovering over wp_nav_menu box
14 | var url;
15 | jQuery('#nav-menu-meta').on('mouseenter', 'label.menu-item-title', function(){
16 | // On mouse in
17 | url = jQuery(this).siblings('input.menu-item-url').val();
18 | jQuery(this).attr('title', url);
19 | }
20 | );
21 |
22 | },
23 |
24 | showAttachmentIds: function(){
25 |
26 | // Show the attachment URLs on hover of attachment grid blocks
27 | jQuery(document).on('mouseenter', '.media-modal .attachment, .media-frame .attachment', function(){
28 | var id = jQuery(this).data('id');
29 | if(id) {
30 | jQuery(this).attr('title', 'Attachment ID: ' + id);
31 | }
32 | });
33 |
34 | }
35 |
36 | autoRefresh: function(){
37 |
38 | // In the admin backend when a new parent is selected from the dropdown, automatically trigger 'save draft'
39 | jQuery('.wp-admin #pageparentdiv #parent_id').change(function(e){
40 | funkhausAdmin.saveDraft();
41 | });
42 |
43 | },
44 |
45 | saveDraft: function(){
46 | var $count = 0;
47 |
48 | // Save Draft post after 500ms
49 | function loopCount(){
50 | if ( !jQuery('#save-action input[type="submit"]').is(':disabled') ) {
51 | jQuery('#save-action input[type="submit"]').trigger('click');
52 | } else if ($count < 10) {
53 | setTimeout(function() {
54 | $count++;
55 | loopCount();
56 | }, 500);
57 | }
58 | }
59 | loopCount();
60 | },
61 |
62 | secondFeaturedImageUploader: function(){
63 | // Cache vars to stay in scope
64 | var frame;
65 | var $metaBox = jQuery('.custom-image-uploader');
66 | var $addImgLink = $metaBox.find('.upload-custom-image');
67 | var $delImgLink = $metaBox.find('.delete-custom-image');
68 | var $imgContainer = $metaBox.find('.custom-image-container');
69 | var $imgIdInput = $metaBox.find('.custom-image-id');
70 |
71 | // ADD IMAGE LINK
72 | $addImgLink.on( 'click', function(e){
73 |
74 | e.preventDefault();
75 |
76 | // If the media frame already exists, reopen it.
77 | if ( frame ) {
78 | frame.open();
79 | return;
80 | }
81 |
82 | // Create a new media frame
83 | frame = wp.media({
84 | title: 'Select image to use',
85 | button: {
86 | text: 'Use this image'
87 | },
88 | multiple: false // Set to true to allow multiple files to be selected
89 | });
90 |
91 | // What to do when image is selected in the media frame, and the save button is clicked
92 | frame.on('select', function() {
93 |
94 | // Get media attachment details from the frame state
95 | var attachment = frame.state().get('selection').first().toJSON();
96 |
97 | // Send the attachment URL to our custom image input field.
98 | $imgContainer.append(' ');
99 |
100 | // Send the attachment id to our hidden input
101 | $imgIdInput.val(attachment.id);
102 |
103 | // Hide the add image link
104 | $addImgLink.addClass('hidden');
105 |
106 | // Unhide the remove image link
107 | $delImgLink.removeClass('hidden');
108 | });
109 |
110 | // Finally, open the modal on click
111 | frame.open();
112 | });
113 |
114 |
115 | // DELETE IMAGE LINK
116 | $delImgLink.on('click', function(e){
117 |
118 | e.preventDefault();
119 |
120 | // Clear out the preview image
121 | $imgContainer.html('');
122 |
123 | // Un-hide the add image link
124 | $addImgLink.removeClass('hidden');
125 |
126 | // Hide the delete image link
127 | $delImgLink.addClass('hidden');
128 |
129 | // Delete the image id from the hidden input
130 | $imgIdInput.val('');
131 |
132 | });
133 | }
134 | };
135 | jQuery(document).ready(function(){
136 | funkhausAdmin.init();
137 | });
138 |
--------------------------------------------------------------------------------
/src/utils/shared.js:
--------------------------------------------------------------------------------
1 | import scrollToElement from 'scroll-to-element'
2 |
3 | /* global _get */
4 |
5 | /*
6 | * This file is used to house small pure utilities that we use frequesntly.
7 | * You can import these from anywhere in the src folder like this:
8 | * import { utilOne, utilTwo } from 'src/utils/shared'
9 | */
10 |
11 | // Description: Take array of items and sort equally across n new arrays
12 | // -- Returns: sorted buckets (array of arrays)
13 | export const sortBuckets = (items, count = 2) => {
14 | const buckets = Array(count)
15 | .fill(0)
16 | .map(v => [])
17 | let pointer = 0
18 |
19 | items.forEach(item => {
20 | buckets[pointer].push(item)
21 | pointer = (pointer + 1) % count
22 | })
23 |
24 | return buckets
25 | }
26 |
27 | // Description: Easily preload an image by URL or full attachment object
28 | // -- Returns: Promise that resolves to image DOM element
29 | export const preloadImg = (img, size = 'full') => {
30 | return new Promise((resolve, reject) => {
31 | // assume URL provided
32 | let src = img
33 |
34 | // if object provided, assume it's
35 | // a rest-easy serialized attachment
36 | if (typeof img === 'object') {
37 | src = _get(img, `sizes[${size}].url`, '')
38 | }
39 |
40 | // ensure we have a URL and an image constructor
41 | if (!src || !Image) return reject()
42 |
43 | // make image object and set callbacks
44 | const imageEl = new Image()
45 | imageEl.onload = () => resolve(imageEl)
46 | imageEl.onerror = reject
47 | return (imageEl.src = src)
48 | })
49 | }
50 |
51 | // Description: Easily preload a video file by URL or full attachment object
52 | // -- Returns: Promise that resolves to video DOM element
53 | export const preloadVid = vid => {
54 | return new Promise((resolve, reject) => {
55 | // assume URL provided
56 | let src = vid
57 |
58 | // if object provided, assume it's
59 | // a rest-easy serialized attachment
60 | if (typeof vid === 'object') {
61 | src = _get(vid, `meta.custom_video_url`) || ''
62 | }
63 |
64 | // ensure we have a URL and a document object
65 | if (!src || !document) return reject()
66 |
67 | // make DOM element and set callbacks
68 | const vidEl = document.createElement('video')
69 | vidEl.addEventListener('canplay', () => resolve(vidEl))
70 | vidEl.addEventListener('error', reject)
71 | return (vidEl.src = src)
72 | })
73 | }
74 |
75 | // Description: Tiny helper function to promise-ify a timeout
76 | // -- Returns: Promise
77 | export const wait = (time = 1000) => {
78 | return new Promise(resolve => {
79 | return setTimeout(resolve, time)
80 | })
81 | }
82 |
83 | export const buildShareLinks = opts => {
84 | const url = opts.url || ''
85 | const text = opts.text || ''
86 | const title = opts.title || ''
87 |
88 | return {
89 | facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
90 | url
91 | )}`,
92 | twitter: `http://twitter.com/share?text=${encodeURIComponent(
93 | text.substring(0, 280)
94 | )}&url=${encodeURIComponent(url)}`,
95 | tumblr: `http://www.tumblr.com/share/link?url=${encodeURIComponent(
96 | url
97 | )}`,
98 | reddit: `http://www.reddit.com/submit?url=${url}&title=${encodeURIComponent(
99 | title
100 | )}`,
101 | email: `mailto:?subject=${encodeURIComponent(
102 | title
103 | )}&body=${encodeURIComponent(text)}%0D%0A %0D%0A${encodeURIComponent(
104 | url
105 | )}`,
106 | linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(
107 | url
108 | )}&title=${encodeURIComponent(title)}&summary=${encodeURIComponent(
109 | text
110 | )}`,
111 | pinterest: `http://pinterest.com/pin/create/button/?url=${encodeURIComponent(
112 | url
113 | )}&description=${encodeURIComponent(text)}`,
114 | reddit: `http://www.reddit.com/submit?url=${encodeURIComponent(url)}`
115 | }
116 | }
117 |
118 | // smooth scroll to top of page
119 | export const scrollUp = () => {
120 | return new Promise((resolve, reject) => {
121 | const sTop = window.pageYOffset || document.documentElement.scrollTop
122 | if (sTop <= 10) return resolve()
123 |
124 | const speed = Math.min(sTop, 500)
125 | const scroller = scrollToElement(document.body, {
126 | duration: speed,
127 | ease: 'inOutQuad'
128 | })
129 | if (scroller) scroller.on('end', resolve)
130 | else reject()
131 | })
132 | }
133 |
134 | // clamp a value between min and max, inclusive
135 | export const clamp = (val, min, max) => {
136 | return Math.min(Math.max(val, min), max)
137 | }
138 |
139 | // mimics PHP's nl2br
140 | export const nl2br = str => {
141 | return str.replace(/(?:\r\n|\r|\n)/g, ' ')
142 | }
143 |
144 | // wait for an element with the given CSS selector to appear:
145 | // await asyncWaitFor('.thing')
146 | // doSomethingWith('.thing')
147 | export const asyncWaitFor = item => {
148 | return new Promise(res => {
149 | let present = false
150 |
151 | // set up loop
152 | const check = () => {
153 | present = document.querySelector(item)
154 |
155 | if (!present) {
156 | requestAnimationFrame(check)
157 | } else {
158 | requestAnimationFrame(res)
159 | }
160 | }
161 |
162 | // kick off loop
163 | check()
164 | })
165 | }
166 |
167 |
--------------------------------------------------------------------------------
/functions/meta-boxes.php:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 | 1 ) : ?>
24 |
25 | Select the custom template for this page:
26 |
27 |
28 | custom_vuehaus_template, $template); ?>>
29 |
30 |
31 |
32 |
33 |
34 |
35 | Enter the video URL for this page:
36 |
37 |
38 |
39 |
40 | ID ) );
56 |
57 | // See if there's a media id already saved as post meta
58 | $image_id = get_post_meta( $post->ID, $meta_key, true );
59 |
60 | // Get the image src
61 | $image_src = wp_get_attachment_image_src( $image_id, 'post-thumbnail' );
62 |
63 | // For convenience, see if the array is valid
64 | $has_image = is_array( $image_src );
65 |
66 | ?>
67 |
68 |
91 |
92 | ID, 'custom_video_url', true );
124 | $fields['custom_video_url'] = array(
125 | 'label' => __( 'Video URL', 'text-domain' ),
126 | 'input' => 'text',
127 | 'value' => $meta,
128 | 'show_in_edit' => true,
129 | );
130 |
131 | return $fields;
132 | }
133 | add_filter( 'attachment_fields_to_edit', 'custom_add_attachment_video_url', 10, 2 );
134 |
135 |
136 | /*
137 | * Update media custom field when updating attachment
138 | */
139 | function custom_update_attachment_video_url( $post_id = null ) {
140 | if( empty($post_id) and isset($_POST['id']) ) {
141 | $post_id = $_POST['id'];
142 | }
143 |
144 | if( isset( $_POST['attachments'][ $post_id ]['custom_video_url'] ) ) {
145 | update_post_meta( $post_id , 'custom_video_url', $_POST['attachments'][ $post_id ]['custom_video_url'] );
146 | }
147 |
148 | clean_post_cache($post_id);
149 | return;
150 | }
151 | add_action( 'edit_attachment', 'custom_update_attachment_video_url', 1 );
152 | add_action( 'wp_ajax_save-attachment-compat', 'custom_update_attachment_video_url', 0, 1 );
153 |
--------------------------------------------------------------------------------
/functions/README.md:
--------------------------------------------------------------------------------
1 | ## Example Rest-Easy Filters
2 |
3 | Here are a bunch of useful examples of how to use the Rest-Easy filter functions to send custom data to the frontend.
4 |
5 | ```php
6 | /**
7 | * Include the second post thumbnail in all posts
8 | *
9 | * @param array $post_data The post currently being processed by Rest-Easy
10 | */
11 | function add_second_featured_image($post_data){
12 | $image_id = get_post_meta($post_data['id'], 'second_post_thumbnail', true);
13 |
14 | if($image_id) {
15 | $post_data['secondPostThumbnail'] = apply_filters('rez_serialize_attachment', get_post($image_id));
16 | }
17 | return $post_data;
18 | }
19 | add_filter('rez_serialize_post', 'add_second_featured_image');
20 | ```
21 |
22 | ```php
23 | /**
24 | * Add a custom formatted date to all posts
25 | *
26 | * @param array $post_data The post currently being processed by Rest-Easy
27 | */
28 | function custom_format_date($post_data){
29 | $post = get_post($post_data['id']);
30 | $post_data['dateFormatted'] = get_the_date('F j Y', $post);
31 | return $post_data;
32 | }
33 | add_filter('rez_serialize_post', 'custom_format_date');
34 | ```
35 |
36 | ```php
37 | /**
38 | * Add custom credits to each post. Add line breaks while doing it.
39 | *
40 | * @param array $post_data The post currently being processed by Rest-Easy
41 | */
42 | function custom_format_credits($post_data){
43 | $post = get_post($post_data['id']);
44 | $post_data['meta']['customCredits'] = nl2br($post->custom_credits);
45 | return $post_data;
46 | }
47 | add_filter('rez_serialize_post', 'custom_format_credits');
48 | ```
49 |
50 | ```php
51 | /**
52 | * Overwrite the loop when in a tree to always be the children of the parent page.
53 | * This is useful when you want a child page to actually return all siblings as the main loop.
54 | * Common for contact pages that show as an accordian menu.
55 | *
56 | * @param array $loop All the pages/posts in the cuurent loop
57 | */
58 | function serialize_contact_pages($loop){
59 |
60 | $page = get_page_by_dev_id('contact');
61 |
62 | if( is_tree($page->ID) ) {
63 | $loop = array();
64 | $loop[0] = apply_filters('rez_serialize_object', get_post($page->ID));
65 | $loop[0]['related'] = apply_filters('rez_gather_related', get_post($page->ID));
66 | }
67 | return $loop;
68 | }
69 | add_filter('rez_build_loop_data', 'serialize_contact_pages');
70 | ```
71 |
72 | ## Example of some useful shortcodes
73 |
74 | Here are a bunch of useful examples of how to use WordPress shortcodes with vuehaus. You will need both the PHP and the Vue template.
75 |
76 | ```php
77 | /*
78 | * Image component (left or right aligned), with text on either side of it.
79 | */
80 | function custom_shortcode_image( $atts, $content = '', $name) {
81 | $props = '';
82 | $content = custom_filter_shortcode_text($content);
83 |
84 | // Include default props here
85 | extract(shortcode_atts(array(
86 | 'id' => '' // ID of WordPress attachment/image
87 | ), $atts));
88 |
89 | if( !empty($id) ) {
90 | $image = apply_filters('rez_serialize_attachment', get_post($id));
91 |
92 | // Props to pass to Vue component
93 | $props = ':image="' . esc_attr(json_encode($image)) . '"';
94 | }
95 |
96 | return ''. $content .' ';
97 | }
98 | add_shortcode( 'image-left', 'custom_shortcode_image' );
99 | add_shortcode( 'image-right', 'custom_shortcode_image' );
100 | ```
101 |
102 | ```vue
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
135 |
136 |
172 | ```
173 |
174 | ```php
175 | /*
176 | * Half column of text
177 | */
178 | function custom_shortcode_half_column( $atts, $content = '', $name ) {
179 | $content = custom_filter_shortcode_text($content);
180 | return ''. $content .' ';
181 | }
182 | add_shortcode( 'half-column', 'custom_shortcode_half_column' );
183 | ```
184 |
185 | ```vue
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
197 |
198 |
210 | ```
211 |
212 | ```php
213 | /**
214 | * Add serialized version of blog page to site data
215 | */
216 | function add_blog_permalink_to_sitedata($site_data){
217 | $blog_page = get_page_by_dev_id('blog');
218 | if ( $blog_page ) {
219 | $site_data['blogPage'] = apply_filters('rez_serialize_post', $blog_page);
220 | }
221 | return $site_data;
222 | }
223 | add_filter('rez_build_site_data', 'add_blog_permalink_to_sitedata');
224 | ```
225 |
--------------------------------------------------------------------------------
/functions/wp-functions.php:
--------------------------------------------------------------------------------
1 | ID) ) {
118 | $content = ''.get_the_post_thumbnail($post->ID).'
'.$content;
119 | }
120 |
121 | return $content;
122 | }
123 | add_filter('the_excerpt_rss', 'rss_post_thumbnail');
124 |
125 |
126 | /*
127 | * Custom conditional function. Used to get the parent and all it's child.
128 | */
129 | function is_tree($tree_id, $target_post = null) {
130 | // get full post object
131 | $target_post = get_post($target_post);
132 | // get all post ancestors
133 | $ancestors = get_ancestors($target_post->ID, $target_post->post_type);
134 | // if ID is target post OR in target post tree, return true
135 | return ( ($target_post->ID == $tree_id) or in_array($tree_id, $ancestors) );
136 | }
137 |
138 |
139 | /*
140 | * Allow SVG uploads
141 | */
142 | function add_mime_types($mimes) {
143 | $mimes['svg'] = 'image/svg+xml';
144 | return $mimes;
145 | }
146 | //add_filter('upload_mimes', 'add_mime_types');
147 |
148 |
149 | /*
150 | * Allow subscriber to see Private posts/pages
151 | */
152 | function add_theme_caps() {
153 | // Gets the author role
154 | $role = get_role('subscriber');
155 |
156 | // Add capabilities
157 | $role->add_cap('read_private_posts');
158 | $role->add_cap('read_private_pages');
159 | }
160 | //add_action( 'switch_theme', 'add_theme_caps');
161 |
162 |
163 | /*
164 | * Change the [...] that comes after excerpts
165 | */
166 | function custom_excerpt_ellipsis( $more ) {
167 | return '...';
168 | }
169 | add_filter('excerpt_more', 'custom_excerpt_ellipsis');
170 |
171 |
172 | /*
173 | * Add Google Analytics tracking settings to admin dashboard
174 | */
175 | function my_general_section() {
176 | add_settings_section(
177 | 'vh_google_analytics_section', // Section ID
178 | 'Google Analytics Tracking IDs', // Section Title
179 | 'vh_google_analytics_section', // Callback
180 | 'general' // This makes the section show up on the General Settings Page
181 | );
182 |
183 | add_settings_field(
184 | 'ga_tracking_code_1', // Option ID
185 | 'Tracking ID #1', // Label
186 | 'vh_google_analytics_settings', // !important - This is where the args go!
187 | 'general', // Page it will be displayed (General Settings)
188 | 'vh_google_analytics_section', // Name of our section
189 | array(
190 | 'ga_tracking_code_1' // Should match Option ID
191 | )
192 | );
193 |
194 | add_settings_field(
195 | 'ga_tracking_code_2', // Option ID
196 | 'Tracking ID #2', // Label
197 | 'vh_google_analytics_settings', // !important - This is where the args go!
198 | 'general', // Page it will be displayed (General Settings)
199 | 'vh_google_analytics_section', // Name of our section
200 | array(
201 | 'ga_tracking_code_2' // Should match Option ID
202 | )
203 | );
204 |
205 | register_setting('general','ga_tracking_code_1', 'esc_attr');
206 | register_setting('general','ga_tracking_code_2', 'esc_attr');
207 | }
208 | add_action('admin_init', 'my_general_section');
209 |
210 |
211 | /*
212 | * Settings callbacks that build the Analytics markup
213 | */
214 | function vh_google_analytics_section() {
215 | echo 'Enter Google Analytics tracking codes. Uses the gtag.js tracking method.
';
216 | }
217 |
218 | function vh_google_analytics_settings($args) {
219 | $option = get_option($args[0]);
220 | echo ' ';
221 | }
222 |
223 | /*
224 | * Add theme options page
225 | */
226 | // if( function_exists('acf_add_options_page') ) {
227 | // acf_add_options_page(array(
228 | // 'page_title' => 'Theme Settings',
229 | // 'menu_title' => 'Theme Settings',
230 | // 'menu_slug' => 'theme-settings'
231 | // ));
232 | // }
233 |
--------------------------------------------------------------------------------
/functions/rest-easy-filters.php:
--------------------------------------------------------------------------------
1 | $value ){
50 | $post_data['acf'][$name] = $value;
51 | }
52 |
53 | }
54 |
55 | return $post_data;
56 | }
57 | add_filter('rez_serialize_post', 'serialize_custom_acf_content');
58 |
59 | /*
60 | * Add Focushaus focal point and Funky-Colors colors to ACF images
61 | */
62 | function add_focal_point_to_acf_images($target){
63 |
64 | if( function_exists('get_offset_x') ){
65 | $target['focus'] = array(
66 | 'x' => get_offset_x( $target['ID'] ),
67 | 'y' => get_offset_y( $target['ID'] )
68 | );
69 | }
70 |
71 | if( function_exists('get_primary_image_color') ){
72 | $target['primary_color'] = get_primary_image_color($target['ID']);
73 | $target['secondary_color'] = get_second_image_color($target['ID']);
74 | }
75 |
76 | $image['videoSrc'] = $target->custom_video_url;
77 |
78 | return $target;
79 | }
80 | add_filter('acf/format_value/type=image', 'add_focal_point_to_acf_images', 20, 4);
81 |
82 | /*
83 | * Add Focushaus focal point and Funky-Colors colors to ACF gallery images
84 | */
85 | function add_image_data_to_acf_galleries($value){
86 |
87 | foreach ($value as $index => &$image) {
88 | $target_post = get_post($image['id']);
89 |
90 | if( function_exists('get_offset_x') ){
91 | $image['focus'] = array(
92 | 'x' => get_offset_x( $image['id'] ),
93 | 'y' => get_offset_y( $image['id'] )
94 | );
95 | }
96 | if( function_exists('get_primary_image_color') ){
97 | $image['primary_color'] = get_primary_image_color($image['id']);
98 | $image['secondary_color'] = get_second_image_color($image['id']);
99 | }
100 |
101 | // add video to gallery image
102 | $image['meta']['custom_video_url'] = $target_post->custom_video_url;
103 |
104 | }
105 | return $value;
106 | }
107 | add_filter('acf/format_value/type=gallery', 'add_image_data_to_acf_galleries', 20, 4);
108 |
109 | /**
110 | * Add pages with dev ID to site data
111 | *
112 | * @param array $input The nav item currently being processed by Rest-Easy
113 | */
114 | function add_dev_page_links_to_site_data($site_data){
115 |
116 | $args = array(
117 | 'posts_per_page' => -1,
118 | 'orderby' => 'menu_order',
119 | 'order' => 'ASC',
120 | // get all non-empty custom_developer_id pages
121 | 'meta_query' => array(
122 | array(
123 | 'key' => 'custom_developer_id',
124 | 'value' => array(''),
125 | 'compare' => 'NOT IN'
126 | )
127 | ),
128 | 'post_type' => 'page',
129 | );
130 | $posts_array = get_posts( $args );
131 |
132 | $site_data['devIds'] = array();
133 |
134 | foreach($posts_array as $dev_id_page){
135 | $key = $dev_id_page->custom_developer_id;
136 | $site_data['devIds'][$key] = wp_make_link_relative(get_permalink($dev_id_page));
137 | }
138 |
139 | return $site_data;
140 | }
141 | add_filter('rez_build_site_data', 'add_dev_page_links_to_site_data');
142 |
143 | /**
144 | * Serialize page siblings
145 | *
146 | * @param array $input The post currently being processed by Rest-Easy
147 | */
148 |
149 | // NOTE: This is a common Rest-Easy filter, but including it by default can slow
150 | // down load times on large sites. Uncomment to use, but it's best to
151 | // include some extra checks so this doesn't run on every page.
152 |
153 | // function add_page_siblings($related, $target = null){
154 | // $target = get_post($target);
155 | // $args = array(
156 | // 'posts_per_page' => -1,
157 | // 'orderby' => 'menu_order',
158 | // 'order' => 'ASC',
159 | // 'exclude' => array( $target->ID ),
160 | // 'post_type' => 'page',
161 | // 'post_parent' => $target->post_parent
162 | // );
163 | // $siblings = get_posts($args);
164 | //
165 | // // apply the serialize_object filter to all siblings
166 | // $related['siblings'] = array_map(
167 | // function ($sibling) { return apply_filters('rez_serialize_object', $sibling); },
168 | // $siblings
169 | // );
170 | //
171 | // // return modified data
172 | // return $related;
173 | // }
174 | // add_filter('rez_gather_related', 'add_page_siblings', 10, 2);
175 |
176 | // Add theme options to site data
177 | // function add_theme_options_to_site($site_data){
178 | // if ( function_exists('the_field') ){
179 | // $site_data['themeOptions'] = array(
180 | // 'singleText' => get_field('single_text', 'option'),
181 | // 'multilineText' => explode("\n", get_field('multiline_text', 'option')),
182 | // );
183 | // }
184 | // return $site_data;
185 | // }
186 | // add_filter('rez_build_site_data', 'add_theme_options_to_site');
187 |
188 | // Adds adjacent posts when they are unset due to recent bug in wordpress.
189 | //function add_adjacent_posts($related, $post = null) {
190 | // if (is_null($related['next']) && is_null($related['prev'])) {
191 | // $prev = get_posts(array(
192 | // 'post_type' => 'post',
193 | // 'posts_per_page' => 1,
194 | // 'date_query' => array(
195 | // 'after' => $post->post_date
196 | // )
197 | // ));
198 | // $next = get_posts(array(
199 | // 'post_type' => 'post',
200 | // 'posts_per_page' => 1,
201 | // 'date_query' => array(
202 | // 'before' => $post->post_date
203 | // )
204 | // ));
205 | // if (count($next) > 0){
206 | // $related['next'] = apply_filters('rez_serialize_object', $next[0]);
207 | // }
208 | // if (count($prev) > 0){
209 | // $related['prev'] = apply_filters('rez_serialize_object', $prev[0]);
210 | // }
211 | // }
212 | // return $related;
213 | // }
214 | // add_filter('rez_gather_related', 'add_adjacent_posts', 10, 2);
215 |
216 | /**
217 | * Add WP-Shopify options to site data
218 | *
219 | * @param array $site_data The site data currently being processed by Rest-Easy
220 | */
221 | function add_wps_options_to_site_data($site_data){
222 | if ( function_exists('get_wshop_domain') ){
223 | $site_data['storefrontToken'] = get_wshop_api_key();
224 | $site_data['shopifyDomain'] = get_wshop_domain();
225 | }
226 | return $site_data;
227 | }
228 | add_filter('rez_build_site_data', 'add_wps_options_to_site_data');
229 |
230 | /**
231 | * Add next posts link to site data for infinite scroll
232 | *
233 | * @param array $site_data The site data currently being processed by Rest-Easy
234 | */
235 | function add_next_post_page_link($input) {
236 | $input['meta']['next_post_link'] = next_posts(0, false);
237 | return $input;
238 | }
239 | add_filter('rez_build_site_data', 'add_next_post_page_link');
240 |
--------------------------------------------------------------------------------
/functions/developer-role.php:
--------------------------------------------------------------------------------
1 | get_role('administrator');
11 |
12 | add_role(
13 | 'developer',
14 | __('Developer'),
15 | $admin_role->capabilities
16 | );
17 |
18 | // set initial user to Developer
19 | $user = new WP_User(1);
20 | $user->set_role('developer');
21 | }
22 | add_action('after_switch_theme', 'custom_theme_switch');
23 |
24 | /*
25 | * Disable Rich Editor on certain pages
26 | */
27 | function disabled_rich_editor($allow_rich_editor) {
28 | global $post;
29 |
30 | if($post->_custom_hide_richedit === 'on') {
31 | return false;
32 | }
33 | return true;
34 | }
35 | add_filter( 'user_can_richedit', 'disabled_rich_editor');
36 |
37 | /*
38 | * Deactivate attachment serialization on certain pages
39 | */
40 | function deactivate_attachment_serialization($allow_attachment_serialization) {
41 | global $post;
42 |
43 | if($post->_custom_deactivate_attachment_serialization === 'on') {
44 | return false;
45 | }
46 | return true;
47 | }
48 | add_filter( 'rez-serialize-attachments', 'deactivate_attachment_serialization');
49 |
50 | /*
51 | * Add developer metaboxes to the new/edit page
52 | */
53 | function custom_add_developer_metaboxes($post_type, $post){
54 | if( !user_is_developer() ) return;
55 |
56 | add_meta_box('custom_dev_meta', 'Developer Meta', 'custom_dev_meta', 'page', 'normal', 'low');
57 | }
58 | add_action('add_meta_boxes', 'custom_add_developer_metaboxes', 10, 2);
59 |
60 |
61 | // Build dev meta box (only for users with Developer role)
62 | function custom_dev_meta($post) {
63 |
64 | ?>
65 |
66 | Enter the Developer ID for this page:
67 |
68 |
69 |
70 | Prevent non-dev deletion:
71 | _custom_lock ) echo 'checked'; ?>>
72 |
73 |
74 | Hide rich editor:
75 | _custom_hide_richedit === 'on' ) echo 'checked'; ?>>
76 |
77 |
78 | Prevent attachment serialization:
79 | _custom_deactivate_attachment_serialization === 'on' ) echo 'checked'; ?>>
80 |
81 |
82 |
83 |
84 | _custom_lock ){
92 | echo 'Only a user with the Developer role can delete this page. ';
93 | echo 'Back ';
94 | exit;
95 | }
96 | }
97 | add_action('wp_trash_post', 'check_custom_post_lock', 10, 1);
98 | add_action('before_delete_post', 'check_custom_post_lock', 10, 1);
99 |
100 | function custom_save_developer_metabox($post_id){
101 |
102 | // check autosave
103 | if( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
104 | return $post_id;
105 | }
106 |
107 | // these values will only be updated if the current user is a Developer
108 | if( !user_is_developer() ) return;
109 |
110 | if( isset($_POST['custom_developer_id']) ) {
111 | update_post_meta($post_id, 'custom_developer_id', $_POST['custom_developer_id']);
112 | }
113 | if( isset($_POST['_custom_lock']) ) {
114 | $value = $_POST['_custom_lock'] == 'on' ? 'on' : 0;
115 | update_post_meta($post_id, '_custom_lock', $value);
116 | } else {
117 | update_post_meta($post_id, '_custom_lock', 0);
118 | }
119 |
120 | if( isset($_POST['_custom_hide_richedit']) ){
121 | $value = $_POST['_custom_hide_richedit'] == 'on' ? 'on' : 0;
122 | update_post_meta($post_id, '_custom_hide_richedit', $_POST['_custom_hide_richedit']);
123 | } else {
124 | update_post_meta($post_id, '_custom_hide_richedit', 0);
125 | }
126 |
127 | if( isset($_POST['_custom_deactivate_attachment_serialization']) ){
128 | $value = $_POST['_custom_deactivate_attachment_serialization'] == 'on' ? 'on' : 0;
129 | update_post_meta($post_id, '_custom_deactivate_attachment_serialization', $_POST['_custom_deactivate_attachment_serialization']);
130 | } else {
131 | update_post_meta($post_id, '_custom_deactivate_attachment_serialization', 0);
132 | }
133 |
134 | }
135 | add_action('save_post', 'custom_save_developer_metabox');
136 |
137 | function user_is_developer(){
138 | $roles = wp_get_current_user()->roles;
139 | return in_array( 'developer', $roles );
140 | }
141 |
142 | // Gets page by a given dev ID
143 | function get_page_by_dev_id( $dev_id ){
144 | $args = array(
145 | 'posts_per_page' => 1,
146 | 'meta_key' => 'custom_developer_id',
147 | 'meta_value' => $dev_id,
148 | 'post_type' => 'page',
149 | );
150 | return reset( get_posts($args) );
151 | }
152 |
153 | // Convenience function - get relative path by dev ID
154 | function path_from_dev_id($dev_id, $after = ''){
155 | $retrieved_page = get_page_by_dev_id($dev_id);
156 |
157 | if( !$retrieved_page ){
158 | return '#404';
159 | }
160 |
161 | $base_url = rtrim(wp_make_link_relative(get_permalink($retrieved_page)), '/');
162 | return $base_url . $after;
163 | }
164 |
165 | // Convenience function - get slug by dev ID
166 | function slug_from_dev_id($dev_id){
167 | $retrieved_page = get_page_by_dev_id($dev_id);
168 |
169 | if( !$retrieved_page ){
170 | return '';
171 | }
172 |
173 | return $retrieved_page->post_name;
174 | }
175 |
176 | // Gets the nth child of a page with a given Developer ID
177 | function get_child_of_dev_id($dev_id, $nth_child = 0){
178 | $parent = get_page_by_dev_id($dev_id);
179 |
180 | $args = array(
181 | 'posts_per_page' => 1,
182 | 'offset' => $nth_child,
183 | 'orderby' => 'menu_order',
184 | 'order' => 'ASC',
185 | 'post_type' => 'page',
186 | 'post_parent' => $parent->ID,
187 | );
188 | return reset(get_posts($args));
189 | }
190 |
191 | // Gets the relative path of the nth child of a page with given Developer ID
192 | function get_child_of_dev_id_path($dev_id, $nth_child = 0, $after = ''){
193 | $permalink = get_permalink(get_child_of_dev_id($dev_id, $nth_child));
194 | return $base_url = rtrim($permalink, '/');
195 | }
196 |
197 | // Gets the children of a page with the given Developer ID
198 | function get_children_of_dev_id($dev_id){
199 | $parent = get_page_by_dev_id($dev_id);
200 |
201 | if( $parent === false ) {
202 | return false;
203 | }
204 |
205 | $args = array(
206 | 'posts_per_page' => -1,
207 | 'post_type' => 'page',
208 | 'orderby' => 'menu_order',
209 | 'order' => 'ASC',
210 | 'post_parent' => $parent->ID,
211 | );
212 | return get_posts($args);
213 | }
214 |
215 | // Makes sure Developer role can sort Nested Pages automatically
216 | function give_developer_ordering_permissions(){
217 |
218 | if( is_plugin_active('wp-nested-pages/nestedpages.php') ){
219 |
220 | $allowed_to_sort = get_option('nestedpages_allowsorting');
221 |
222 | if( !$allowed_to_sort ){
223 | $allowed_to_sort = array();
224 | }
225 |
226 | if( !in_array('developer', $allowed_to_sort) ){
227 | $allowed_to_sort[] = 'developer';
228 | update_option('nestedpages_allowsorting', $allowed_to_sort);
229 | }
230 | }
231 |
232 | }
233 | add_action('admin_init', 'give_developer_ordering_permissions', 1);
234 |
235 | // add 'is-developer' class to WP admin pages if we're a developer
236 | function add_developer_admin_body_class($classes){
237 | if( user_is_developer() ){
238 | $classes .= ' is-developer';
239 | }
240 | return $classes;
241 | }
242 | add_filter('admin_body_class', 'add_developer_admin_body_class');
243 |
244 | // add extra capabilities for common plugins
245 | function add_extra_vh_capabilities(){
246 | global $wp_roles;
247 |
248 | // add desired developer capabilities here
249 | $caps_to_add = array(
250 | 'manage_woocommerce',
251 | 'view_woocommerce_reports'
252 | );
253 |
254 | // woocommerce defaults
255 | // (https://github.com/woocommerce/woocommerce/blob/master/includes/class-wc-install.php#L981)
256 | $capability_types = array( 'product', 'shop_order', 'shop_coupon' );
257 | foreach ( $capability_types as $capability_type ) {
258 | $wc_caps_to_add = array(
259 | // Post type.
260 | "edit_{$capability_type}",
261 | "read_{$capability_type}",
262 | "delete_{$capability_type}",
263 | "edit_{$capability_type}s",
264 | "edit_others_{$capability_type}s",
265 | "publish_{$capability_type}s",
266 | "read_private_{$capability_type}s",
267 | "delete_{$capability_type}s",
268 | "delete_private_{$capability_type}s",
269 | "delete_published_{$capability_type}s",
270 | "delete_others_{$capability_type}s",
271 | "edit_private_{$capability_type}s",
272 | "edit_published_{$capability_type}s",
273 | // Terms.
274 | "manage_{$capability_type}_terms",
275 | "edit_{$capability_type}_terms",
276 | "delete_{$capability_type}_terms",
277 | "assign_{$capability_type}_terms",
278 | );
279 |
280 | foreach($wc_caps_to_add as $wc_cap_to_add){
281 | $caps_to_add[] = $wc_cap_to_add;
282 | }
283 | }
284 |
285 | // get developer role
286 | $developer_role = get_role('developer');
287 |
288 | // add desired caps to developer if they don't exist yet
289 | foreach($caps_to_add as $cap_to_add){
290 | if( !$developer_role->has_cap($cap_to_add) ){
291 | $wp_roles->add_cap( 'developer', $cap_to_add );
292 | }
293 | }
294 | }
295 | // add permissions after any plugin activated
296 | add_action('activated_plugin', 'add_extra_vh_capabilities');
297 |
--------------------------------------------------------------------------------
/blocks/dist/blocks.js:
--------------------------------------------------------------------------------
1 | /******/ ;(function(modules) {
2 | // webpackBootstrap
3 | /******/ // The module cache
4 | /******/ var installedModules = {} // The require function
5 | /******/
6 | /******/ /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if (installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports
11 | /******/
12 | } // Create a new module (and put it into the cache)
13 | /******/ /******/ var module = (installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/
18 | }) // Execute the module function
19 | /******/
20 | /******/ /******/ modules[moduleId].call(
21 | module.exports,
22 | module,
23 | module.exports,
24 | __webpack_require__
25 | ) // Flag the module as loaded
26 | /******/
27 | /******/ /******/ module.l = true // Return the exports of the module
28 | /******/
29 | /******/ /******/ return module.exports
30 | /******/
31 | } // expose the modules object (__webpack_modules__)
32 | /******/
33 | /******/
34 | /******/ /******/ __webpack_require__.m = modules // expose the module cache
35 | /******/
36 | /******/ /******/ __webpack_require__.c = installedModules // define getter function for harmony exports
37 | /******/
38 | /******/ /******/ __webpack_require__.d = function(exports, name, getter) {
39 | /******/ if (!__webpack_require__.o(exports, name)) {
40 | /******/ Object.defineProperty(exports, name, {
41 | /******/ configurable: false,
42 | /******/ enumerable: true,
43 | /******/ get: getter
44 | /******/
45 | })
46 | /******/
47 | }
48 | /******/
49 | } // getDefaultExport function for compatibility with non-harmony modules
50 | /******/
51 | /******/ /******/ __webpack_require__.n = function(module) {
52 | /******/ var getter =
53 | module && module.__esModule
54 | ? /******/ function getDefault() {
55 | return module['default']
56 | }
57 | : /******/ function getModuleExports() {
58 | return module
59 | }
60 | /******/ __webpack_require__.d(getter, 'a', getter)
61 | /******/ return getter
62 | /******/
63 | } // Object.prototype.hasOwnProperty.call
64 | /******/
65 | /******/ /******/ __webpack_require__.o = function(object, property) {
66 | return Object.prototype.hasOwnProperty.call(object, property)
67 | } // __webpack_public_path__
68 | /******/
69 | /******/ /******/ __webpack_require__.p = '' // Load entry module and return exports
70 | /******/
71 | /******/ /******/ return __webpack_require__((__webpack_require__.s = 0))
72 | /******/
73 | })(
74 | /************************************************************************/
75 | /******/ [
76 | /* 0 */
77 | /***/ function(module, exports, __webpack_require__) {
78 | __webpack_require__(1)
79 | module.exports = __webpack_require__(6)
80 |
81 | /***/
82 | },
83 | /* 1 */
84 | /***/ function(module, __webpack_exports__, __webpack_require__) {
85 | 'use strict'
86 | Object.defineProperty(__webpack_exports__, '__esModule', {
87 | value: true
88 | })
89 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_builder__ = __webpack_require__(
90 | 2
91 | )
92 |
93 | // Example block
94 | // buildBlock({
95 | // name: 'Title and Image',
96 | // slug: 'title-and-image',
97 | // description: 'A title and an image.',
98 | // class: 'title-and-image',
99 | // content: [
100 | // Title ,
101 | // { name: 'content', type: 'text' },
102 | // Image ,
103 | // { name: 'image', type: 'image' }
104 | // ]
105 | // })
106 |
107 | /***/
108 | },
109 | /* 2 */
110 | /***/ function(module, __webpack_exports__, __webpack_require__) {
111 | 'use strict'
112 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__prebuilt__ = __webpack_require__(
113 | 3
114 | )
115 | var _extends =
116 | Object.assign ||
117 | function(target) {
118 | for (var i = 1; i < arguments.length; i++) {
119 | var source = arguments[i]
120 | for (var key in source) {
121 | if (
122 | Object.prototype.hasOwnProperty.call(
123 | source,
124 | key
125 | )
126 | ) {
127 | target[key] = source[key]
128 | }
129 | }
130 | }
131 | return target
132 | }
133 |
134 | // prep prebuilt components
135 |
136 | var registerBlockType = wp.blocks.registerBlockType
137 | var RichText = wp.editor.RichText
138 |
139 | /* unused harmony default export */ var _unused_webpack_default_export = function() {
140 | var opts =
141 | arguments.length > 0 && arguments[0] !== undefined
142 | ? arguments[0]
143 | : {}
144 |
145 | var settings = _extends(
146 | {
147 | // defaults
148 | slug: 'block-slug',
149 | name: 'Example',
150 | description: '',
151 | icon: 'format-gallery',
152 | content: [],
153 | class: 'block-class'
154 | },
155 | opts
156 | )
157 |
158 | // build attributes according to content children
159 | var attributes = {}
160 | settings.content.map(function(child) {
161 | if (
162 | child.type &&
163 | __WEBPACK_IMPORTED_MODULE_0__prebuilt__[
164 | 'a' /* default */
165 | ][child.type]
166 | ) {
167 | attributes[
168 | child.name
169 | ] = __WEBPACK_IMPORTED_MODULE_0__prebuilt__[
170 | 'a' /* default */
171 | ][child.type].attributes(child)
172 | }
173 | })
174 |
175 | // register desired block
176 | registerBlockType('custom-fh/' + settings.slug, {
177 | title: settings.name,
178 | description: settings.description,
179 | icon: settings.icon,
180 | category: 'custom-fh',
181 |
182 | // use attributes defined above
183 | attributes: attributes,
184 |
185 | // Editor
186 | edit: function edit(props) {
187 | var attributes = props.attributes,
188 | className = props.className,
189 | setAttributes = props.setAttributes
190 |
191 | var classes = settings.class + ' fh-custom-block'
192 |
193 | var output = settings.content
194 | .map(function(child) {
195 | if (
196 | child.type &&
197 | __WEBPACK_IMPORTED_MODULE_0__prebuilt__[
198 | 'a' /* default */
199 | ][child.type]
200 | ) {
201 | return __WEBPACK_IMPORTED_MODULE_0__prebuilt__[
202 | 'a' /* default */
203 | ][child.type].edit(props, child)
204 | }
205 |
206 | return child
207 | })
208 | .filter(function(x) {
209 | return x
210 | })
211 |
212 | return wp.element.createElement(
213 | 'div',
214 | { className: classes },
215 | output
216 | )
217 | },
218 |
219 | // On save
220 | save: function save(props) {
221 | var output = settings.content
222 | .map(function(child) {
223 | if (
224 | child.type &&
225 | __WEBPACK_IMPORTED_MODULE_0__prebuilt__[
226 | 'a' /* default */
227 | ][child.type]
228 | ) {
229 | return __WEBPACK_IMPORTED_MODULE_0__prebuilt__[
230 | 'a' /* default */
231 | ][child.type].save(props, child)
232 | }
233 |
234 | return null
235 | })
236 | .filter(function(x) {
237 | return x
238 | })
239 |
240 | return wp.element.createElement(
241 | 'div',
242 | { className: settings.class },
243 | output
244 | )
245 | }
246 | })
247 | }
248 |
249 | /***/
250 | },
251 | /* 3 */
252 | /***/ function(module, __webpack_exports__, __webpack_require__) {
253 | 'use strict'
254 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__text__ = __webpack_require__(
255 | 4
256 | )
257 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__image__ = __webpack_require__(
258 | 5
259 | )
260 |
261 | /* harmony default export */ __webpack_exports__['a'] = {
262 | text: __WEBPACK_IMPORTED_MODULE_0__text__['a' /* default */],
263 | image: __WEBPACK_IMPORTED_MODULE_1__image__['a' /* default */]
264 | }
265 |
266 | /***/
267 | },
268 | /* 4 */
269 | /***/ function(module, __webpack_exports__, __webpack_require__) {
270 | 'use strict'
271 | function _defineProperty(obj, key, value) {
272 | if (key in obj) {
273 | Object.defineProperty(obj, key, {
274 | value: value,
275 | enumerable: true,
276 | configurable: true,
277 | writable: true
278 | })
279 | } else {
280 | obj[key] = value
281 | }
282 | return obj
283 | }
284 |
285 | var RichText = wp.editor.RichText
286 |
287 | /* harmony default export */ __webpack_exports__['a'] = {
288 | attributes: function attributes(child) {
289 | return {
290 | type: 'array',
291 | source: 'children',
292 | selector: '.' + child.name
293 | }
294 | },
295 | edit: function edit(props, child) {
296 | var attributes = props.attributes,
297 | className = props.className,
298 | setAttributes = props.setAttributes
299 |
300 | var content = attributes[child.name]
301 |
302 | var classes = child.name + ' ' + className
303 |
304 | function onChangeContent(newContent) {
305 | setAttributes(
306 | _defineProperty({}, child.name, newContent)
307 | )
308 | }
309 |
310 | return wp.element.createElement(RichText, {
311 | tagName: 'p',
312 | className: classes,
313 | onChange: onChangeContent,
314 | value: content
315 | })
316 | },
317 | save: function save(props, child) {
318 | // data attributes passed from editor
319 | var attributes = props.attributes
320 |
321 | // get content and classes relevant to this particular block
322 |
323 | var content = attributes[child.name]
324 | var classes = '' + child.name
325 |
326 | return wp.element.createElement(RichText.Content, {
327 | className: classes,
328 | tagName: 'p',
329 | value: content
330 | })
331 | }
332 | }
333 |
334 | /***/
335 | },
336 | /* 5 */
337 | /***/ function(module, __webpack_exports__, __webpack_require__) {
338 | 'use strict'
339 | function _defineProperty(obj, key, value) {
340 | if (key in obj) {
341 | Object.defineProperty(obj, key, {
342 | value: value,
343 | enumerable: true,
344 | configurable: true,
345 | writable: true
346 | })
347 | } else {
348 | obj[key] = value
349 | }
350 | return obj
351 | }
352 |
353 | var MediaUpload = wp.editor.MediaUpload
354 | var Button = wp.components.Button
355 |
356 | /* harmony default export */ __webpack_exports__['a'] = {
357 | // attributes
358 | attributes: function attributes(child) {
359 | return {
360 | mediaID: {
361 | type: 'number'
362 | },
363 | mediaURL: {
364 | type: 'string',
365 | source: 'attribute',
366 | selector: 'img',
367 | attribute: 'src'
368 | },
369 | width: {
370 | type: 'number'
371 | },
372 | height: {
373 | type: 'number'
374 | }
375 | }
376 | },
377 |
378 | // editing
379 | edit: function edit(props, child) {
380 | var setAttributes = props.setAttributes,
381 | attributes = props.attributes
382 |
383 | // try to find correct attributes, default to empty object
384 |
385 | var atts = attributes[child.name] || {}
386 | var mediaID = atts.mediaID,
387 | mediaURL = atts.mediaURL
388 |
389 | // selecting the image
390 |
391 | function onSelectImage(newImage) {
392 | // console.log(newImage)
393 | setAttributes(
394 | _defineProperty({}, child.name, {
395 | mediaURL: newImage.url,
396 | mediaID: newImage.id,
397 | width: newImage.width,
398 | height: newImage.height
399 | })
400 | )
401 | }
402 |
403 | // edit block
404 | return wp.element.createElement(MediaUpload, {
405 | onSelect: onSelectImage,
406 | type: 'image',
407 | value: mediaID,
408 | render: function render(_ref) {
409 | var open = _ref.open
410 | return wp.element.createElement(
411 | Button,
412 | {
413 | className: mediaID
414 | ? 'image-button'
415 | : 'button button-large',
416 | onClick: open
417 | },
418 | !mediaID
419 | ? 'Upload Image'
420 | : wp.element.createElement('img', {
421 | src: mediaURL
422 | })
423 | )
424 | }
425 | })
426 | },
427 | save: function save(props, child) {
428 | var attributes = props.attributes
429 |
430 | // ignore if we don't have the correct content
431 |
432 | if (!attributes || !attributes[child.name]) {
433 | return ''
434 | }
435 |
436 | var _attributes$child$nam = attributes[child.name],
437 | mediaID = _attributes$child$nam.mediaID,
438 | mediaURL = _attributes$child$nam.mediaURL,
439 | width = _attributes$child$nam.width,
440 | height = _attributes$child$nam.height
441 |
442 | if (mediaURL) {
443 | return wp.element.createElement('img', {
444 | 'data-replace-with': 'responsive-image',
445 | src: mediaURL,
446 | 'data-wp-id': mediaID,
447 | width: width,
448 | height: height
449 | })
450 | }
451 |
452 | return ''
453 | }
454 | }
455 |
456 | /***/
457 | },
458 | /* 6 */
459 | /***/ function(module, exports) {
460 | // removed by extract-text-webpack-plugin
461 | /***/
462 | }
463 | /******/
464 | ]
465 | )
466 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **DEPRECATED: We have stopped developing this, check out our new boilerplate [stackhaus](https://github.com/funkhaus/stackhaus).**
2 |
3 | Vuehaus is a boilerplate used to build fast, responsive [WordPress](https://wordpress.org/) templates with [Vue.js](https://vuejs.org/). Built by [Funkhaus](http://funkhaus.us/).
4 |
5 | # Tutorial
6 |
7 | Head over to the [tutorial](https://github.com/funkhaus/vuehaus/wiki) to learn how to build Vue.js + WordPress sites with Vuehaus!
8 |
9 | # Reference
10 |
11 | ## Table of Contents
12 |
13 | 1. [Install](#install)
14 | 1. [Common Tasks](#common-tasks)
15 | 1. [Getting Information from The Loop](#getting-information-from-the-loop)
16 | 1. [Loading Fonts](#loading-fonts)
17 | 1. [Using SVGs](#using-svgs)
18 | 1. [Images](#images)
19 | 1. [Images with Videos](#images-with-videos)
20 | 1. [Shared Styles](#shared-styles)
21 | 1. [The Developer Role and Developer IDs](#the-developer-role-and-developer-ids)
22 | 1. [Developer Capabilities](#developer-capabilities)
23 | 1. [Advanced Routing](#advanced-routing)
24 | 1. [Front Page Children](#front-page-children)
25 | 1. [Utility Functions](#utility-functions)
26 | 1. [Upgrading Plugins](#upgrading-plugins)
27 | 1. [Vuex and State](#vuex-and-state)
28 | 1. [Mutations](#mutations)
29 | 1. [Actions](#actions)
30 | 1. [Getters](#getters)
31 | 1. [Vuehaus Events](#vuehaus-events)
32 | 1. [Partials](#partials)
33 | 1. [Gutenberg Blocks](#gutenberg-blocks)
34 | 1. [Adding a Block](#adding-a-block)
35 | 1. [Block Content](#block-content)
36 | 1. [Block Content Types](#block-content-types)
37 | 1. [Adding Content Types](#adding-content-types)
38 | 1. [Rendering Gutenberg Content](#rendering-gutenberg-content)
39 | 1. [Removing Block Support](#removing-block-support)
40 | 1. [Deploying](#deploying)
41 | 1. [Recommended Reading](#recommended-reading)
42 | 1. [Contributing](#contributing)
43 |
44 | ## Install
45 |
46 | 1. In a WordPress `themes/` directory: `git clone https://github.com/funkhaus/vuehaus my-theme`
47 | 1. `cd my-theme`
48 | 1. `npm install`
49 | 1. Go to the WordPress back-end, activate, the Vuehaus theme, and follow the instructions to install [Rest-Easy](https://github.com/funkhaus/Rest-Easy).
50 | 1. `npm run dev`
51 | 1. To build: `npm run build`
52 | 1. (Optional) To build and deploy to server using [fh-deploy](https://github.com/funkhaus/fh-deploy): `npm run deploy`
53 |
54 | ## Common Tasks
55 |
56 | ### Getting Information from The Loop
57 |
58 | Paste these anywhere in your `