├── .editorconfig ├── .env.example ├── .github └── workflows │ └── php.yml ├── .gitignore ├── .htaccess ├── README.md ├── composer.json ├── config ├── application.php └── environments │ ├── development.php │ └── staging.php ├── dev ├── cpt.txt └── taxonomy.txt ├── github └── composer.json ├── start.sh ├── web ├── .htaccess ├── app │ ├── languages │ │ └── .gitkeep │ ├── mu-plugins │ │ ├── bedrock-autoloader.php │ │ ├── juniper-checklist.php │ │ ├── juniper-commands.php │ │ └── register-theme-directory.php │ ├── plugins │ │ └── .gitkeep │ ├── themes │ │ ├── .gitkeep │ │ └── juniper-theme │ │ │ ├── .bootstraprc │ │ │ ├── .eslintrc.json │ │ │ ├── .gitignore │ │ │ ├── .posthtmlrc │ │ │ ├── README.md │ │ │ ├── acf-json │ │ │ └── .gitkeep │ │ │ ├── blocks │ │ │ ├── .gitkeep │ │ │ ├── cta │ │ │ │ ├── ajax.js │ │ │ │ ├── functions.php │ │ │ │ ├── script.js │ │ │ │ └── style.scss │ │ │ └── filteringposts │ │ │ │ ├── ajax.js │ │ │ │ ├── functions.php │ │ │ │ ├── script.js │ │ │ │ └── style.scss │ │ │ ├── class-startersite.php │ │ │ ├── composer.json │ │ │ ├── cypress.config.js │ │ │ ├── cypress │ │ │ ├── e2e │ │ │ │ └── blockRestAPI.cy.js │ │ │ ├── fixtures │ │ │ │ └── example.json │ │ │ └── support │ │ │ │ ├── commands.js │ │ │ │ └── e2e.js │ │ │ ├── functions.php │ │ │ ├── inc │ │ │ ├── Ajax │ │ │ │ └── JuniperAjaxFilteringposts.php │ │ │ ├── Blocks │ │ │ │ └── JuniperBlocks.php │ │ │ ├── Cpt │ │ │ │ └── .gitkeep │ │ │ ├── Taxonomies │ │ │ │ └── .gitkeep │ │ │ ├── gutenberg-styles.php │ │ │ ├── include.php │ │ │ └── pattern-categories.php │ │ │ ├── jsconfig.json │ │ │ ├── package.json │ │ │ ├── parts │ │ │ ├── footer.html │ │ │ ├── header.html │ │ │ └── top-bar.html │ │ │ ├── patterns │ │ │ └── cta.php │ │ │ ├── phpunit.xml │ │ │ ├── screenshot.png │ │ │ ├── src │ │ │ ├── css │ │ │ │ ├── _app.scss │ │ │ │ ├── base │ │ │ │ │ ├── _common.scss │ │ │ │ │ ├── _index.scss │ │ │ │ │ ├── _mixins.scss │ │ │ │ │ ├── _reset.scss │ │ │ │ │ ├── _typography.scss │ │ │ │ │ └── _variables.scss │ │ │ │ ├── components │ │ │ │ │ ├── _buttons.scss │ │ │ │ │ └── _forms.scss │ │ │ │ └── vendor │ │ │ │ │ └── swiper.scss │ │ │ ├── img │ │ │ │ └── sample.jpg │ │ │ └── js │ │ │ │ ├── _app.js │ │ │ │ ├── animations │ │ │ │ └── transitions.js │ │ │ │ └── nav.js │ │ │ ├── static │ │ │ ├── no-timber.html │ │ │ └── site.js │ │ │ ├── style.css │ │ │ ├── templates │ │ │ ├── 404.html │ │ │ ├── index.html │ │ │ └── my-custom-template.html │ │ │ ├── theme.json │ │ │ └── views │ │ │ └── blocks │ │ │ ├── .gitkeep │ │ │ └── cta.twig │ └── uploads │ │ └── .gitkeep ├── index.php └── wp-config.php ├── work.sh └── wp-cli.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | indent_style = tab 13 | insert_final_newline = true 14 | trim_trailing_whitespace = true 15 | 16 | [*.css] 17 | indent_style = tab 18 | indent_size = 4 19 | 20 | [*.scss] 21 | indent_style = tab 22 | indent_size = 4 23 | 24 | [*.json] 25 | indent_style = space 26 | indent_size = 4 27 | 28 | [*.txt] 29 | end_of_line = crlf 30 | 31 | [*.{yml,yaml}] 32 | indent_style = space 33 | indent_size = 2 34 | 35 | [*.md] 36 | trim_trailing_whitespace = false 37 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DB_NAME=database_name 2 | DB_USER=database_user 3 | DB_PASSWORD=database_password 4 | 5 | # Optional variables 6 | # DB_HOST=localhost 7 | # DB_PREFIX=wp_ 8 | 9 | WP_ENV=development 10 | WP_HOME=http://example.com 11 | WP_SITEURL=${WP_HOME}/wp 12 | 13 | # Generate your keys here: https://roots.io/salts.html 14 | AUTH_KEY='generateme' 15 | SECURE_AUTH_KEY='generateme' 16 | LOGGED_IN_KEY='generateme' 17 | NONCE_KEY='generateme' 18 | AUTH_SALT='generateme' 19 | SECURE_AUTH_SALT='generateme' 20 | LOGGED_IN_SALT='generateme' 21 | NONCE_SALT='generateme' 22 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Install PHP 23 | uses: shivammathur/setup-php@2.7.0 24 | with: 25 | php-version: '7.4' 26 | coverage: none 27 | tools: composer:v1 28 | 29 | - name: Install dependencies 30 | working-directory: ./github 31 | run: composer install --prefer-dist --no-progress 32 | 33 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 34 | # Docs: https://getcomposer.org/doc/articles/scripts.md 35 | 36 | - name: Run set phpcs suite 37 | working-directory: ./github 38 | run: composer run set 39 | 40 | - name: Run test phpcs 41 | working-directory: ./github 42 | run: composer run test 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Application 2 | web/app/plugins/* 3 | !web/app/plugins/.gitkeep 4 | web/app/mu-plugins/*/ 5 | web/app/upgrade 6 | web/app/uploads/* 7 | !web/app/uploads/.gitkeep 8 | 9 | # WordPress 10 | web/wp 11 | 12 | # Logs 13 | *.log 14 | 15 | # Dotenv 16 | .env 17 | .env.* 18 | !.env.example 19 | 20 | # Composer 21 | /vendor 22 | 23 | # WP-CLI 24 | wp-cli.local.yml 25 | 26 | .DS_Store 27 | .idea 28 | 29 | *.lock 30 | *-lock.json 31 | vendor/ 32 | node_modules 33 | 34 | web/app/advanced-cache.php 35 | web/app/cache 36 | web/app/w3tc-config 37 | web-exppress 38 | 39 | .htaccess 40 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteCond %{HTTP_HOST} ^(www.)?juniper.local$ 3 | RewriteCond %{REQUEST_URI} !^/web/ 4 | RewriteCond %{REQUEST_FILENAME} !-f 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | RewriteRule ^(.*)$ /web/$1 7 | RewriteCond %{HTTP_HOST} ^(www.)?juniper.local$ 8 | RewriteRule ^(/)?$ web/index.php [L] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Juniper 4 | 5 |

6 | 7 | 8 |

9 | Juniper - WordPress starter boilerplate + theme 10 |

11 | 12 | ## Overview 13 | 14 | Juniper is symbiosis of Bedrock boilerplate and Timber. 15 | 16 | Bedrock is a modern WordPress stack that helps you get started with the best development tools and project structure. 17 | Timber allows you to use twig templating system in your WP project. 18 | With this approach you can create theme code with logic files separated from frontend. 19 | 20 | ## Features 21 | 22 | - Easy WordPress configuration with environment specific files 23 | - File structure which makes keeping and maintaining your project on a Git a lot easier 24 | - Dependency management with [Composer](https://getcomposer.org) 25 | - Twig templating system 26 | - Bash console scripts to make creating project from the scratch much easier 27 | 28 | ## Requirements 29 | 30 | - PHP >= 7.4 31 | - Composer - [Install](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx) 32 | - PHPCS & PHPCBF with WordPress-Extra standard installed 33 | 34 | ## Installation 35 | 36 | 1. Create local database as you would do for normal WP instance 37 | 2. Map project main catalogue to domain on your localhost 38 | 3. Start a new project: 39 | ```sh 40 | $ bash start.sh 41 | ``` 42 | and follow the instructions in the console. 43 | Type in details from step 1 and 2. .env file will 44 | be crated for you (all DB and site details sits there) 45 | 4. Fill correct domain details in .htaccess in main catalogue. 46 | 5. Check if /web/ directory has .htaccess file with default WP entries. 47 | 6. Run 48 | ```sh 49 | $ bash work.sh 50 | ``` 51 | in main project directory 52 | 7. Start coding your theme in /web/app/themes/juniper-theme/ :) 53 | 54 | ## Composer dependencies 55 | 56 | To maintain project correctly we insist you to use composer.json in main catalogue. 57 | 58 | If you want to add new plugin to your project you can use [WPackagist](https://wpackagist.org/) - 59 | the only thing you need is plugin slug from the main WP repository. 60 | 61 | You can update them with the same easy way by changing version in composer.json. 62 | 63 | ## Bash scripts 64 | 65 | The main operations that we automate have been handled by below scripts: : 66 | 67 | 1) start.sh - used for the initial configuration of the project. Through this process, we areable to enter the basic data to the database, define the main URL and ACF key. After providing those information, the installer will generate an .env file, which in our case will contain all configuration data (as in wp-config.php in a vanilla WordPress installation). 68 | 69 | 2) work.sh - used every time you work on a project. It compiles the styles in real time by calling a parcel script to listen for file changes. 70 | 71 | ## WP-CLI Commands 72 | 73 | 1. Adding Custom Post Types - After getting the name, a CPT will be created. Its editing will of course be possible later, because this command will generate a file in the theme directory. 74 | ```sh 75 | $ wp add cpt --name="Product" 76 | ``` 77 | 2. Adding Taxonomies - Add a name of the taxonomy you want and the slug name of the posts you want it to be attached to and this command will take care of the rest 78 | ```sh 79 | $ wp add taxonomy --name="Category" --post="product" 80 | ``` 81 | 3. Adding Gutenberg Blocks - This creates a custom Gutenberg block for the user utilizin the ACF Timber Blocks solution which allows us to use one .twig file with the appropriate comment to create a block. Keywords and description fields are optional 82 | ```sh 83 | $ wp add block --name="Reviews" --keywords="quote,stars" --description="Show three newest reviews" 84 | ``` 85 | 86 | 87 | ## 88 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osomstudio/juniper", 3 | "type": "project", 4 | "license": "MIT", 5 | "homepage": "https://github.com/osomstudio/juniper", 6 | "authors": [ 7 | { 8 | "name": "OsomStudio", 9 | "homepage": "https://osomstudio.com" 10 | } 11 | ], 12 | "keywords": [ 13 | "juniper", 14 | "timber", 15 | "bedrock", 16 | "composer", 17 | "roots", 18 | "wordpress", 19 | "wp", 20 | "wp-config" 21 | ], 22 | "support": { 23 | "issues": "https://github.com/osomstudio/juniper/issues" 24 | }, 25 | "repositories": [ 26 | { 27 | "type": "composer", 28 | "url": "https://wpackagist.org", 29 | "only": [ 30 | "wpackagist-plugin/*", 31 | "wpackagist-theme/*" 32 | ] 33 | }, 34 | { 35 | "type": "composer", 36 | "url": "https://connect.advancedcustomfields.com" 37 | } 38 | ], 39 | "require": { 40 | "php": ">=8.1", 41 | "composer/installers": "^2.2", 42 | "vlucas/phpdotenv": "^5.5", 43 | "oscarotero/env": "^2.1", 44 | "roots/bedrock-autoloader": "^1.0", 45 | "roots/bedrock-disallow-indexing": "^2.0", 46 | "roots/wordpress": "^6.7.2", 47 | "roots/wp-config": "1.0.0", 48 | "roots/wp-password-bcrypt": "^1.1", 49 | "wpackagist-plugin/contact-form-7": "6.0.4", 50 | "wpackagist-plugin/wordpress-seo": "24.6", 51 | "wpackagist-plugin/w3-total-cache": "2.8.6", 52 | "wpackagist-plugin/contact-form-7-honeypot": "2.1.7", 53 | "wpackagist-plugin/webp-express": "0.25.9", 54 | "wpengine/advanced-custom-fields-pro": "^6.2" 55 | }, 56 | "require-dev": { 57 | "roave/security-advisories": "dev-latest", 58 | "squizlabs/php_codesniffer": "^3.6.0", 59 | "wp-coding-standards/wpcs": "^3.1" 60 | }, 61 | "extra": { 62 | "installer-paths": { 63 | "web/app/mu-plugins/{$name}/": [ 64 | "type:wordpress-muplugin" 65 | ], 66 | "web/app/plugins/{$name}/": [ 67 | "type:wordpress-plugin" 68 | ], 69 | "web/app/themes/{$name}/": [ 70 | "type:wordpress-theme" 71 | ] 72 | }, 73 | "wordpress-install-dir": "web/wp" 74 | }, 75 | "scripts": { 76 | "post-root-package-install": [ 77 | "php -r \"copy('.env.example', '.env');\"" 78 | ], 79 | "test": [ 80 | "phpcs --standard=WordPress-Extra -snp --basepath=. --ignore=vendor,node_modules --extensions=php --exclude=WordPress.Files.FileName ./web/app/themes" 81 | ], 82 | "start": [ 83 | "composer install", 84 | "cd web/app/themes/* && npm install && composer install" 85 | ] 86 | }, 87 | "config": { 88 | "optimize-autoloader": true, 89 | "preferred-install": "dist", 90 | "allow-plugins": { 91 | "composer/installers": true, 92 | "roots/wordpress-core-installer": true, 93 | "dealerdirect/phpcodesniffer-composer-installer": true 94 | }, 95 | "sort-packages": true, 96 | "platform": { 97 | "php": "8.1" 98 | } 99 | }, 100 | "minimum-stability": "dev", 101 | "prefer-stable": true 102 | } 103 | -------------------------------------------------------------------------------- /config/application.php: -------------------------------------------------------------------------------- 1 | load(); 39 | $dotenv->required(['WP_HOME', 'WP_SITEURL']); 40 | if (!env('DATABASE_URL')) { 41 | $dotenv->required(['DB_NAME', 'DB_USER', 'DB_PASSWORD']); 42 | } 43 | } 44 | 45 | /** 46 | * Set up our global environment constant and load its config first 47 | * Default: production 48 | */ 49 | define('WP_ENV', env('WP_ENV') ?: 'production'); 50 | 51 | /** 52 | * URLs 53 | */ 54 | Config::define('WP_HOME', env('WP_HOME')); 55 | Config::define('WP_SITEURL', env('WP_SITEURL')); 56 | 57 | /** 58 | * Custom Content Directory 59 | */ 60 | Config::define('CONTENT_DIR', '/app'); 61 | Config::define('WP_CONTENT_DIR', $webroot_dir . Config::get('CONTENT_DIR')); 62 | Config::define('WP_CONTENT_URL', Config::get('WP_HOME') . Config::get('CONTENT_DIR')); 63 | 64 | /** 65 | * DB settings 66 | */ 67 | Config::define('DB_NAME', env('DB_NAME')); 68 | Config::define('DB_USER', env('DB_USER')); 69 | Config::define('DB_PASSWORD', env('DB_PASSWORD')); 70 | Config::define('DB_HOST', env('DB_HOST') ?: 'localhost'); 71 | Config::define('DB_CHARSET', 'utf8mb4'); 72 | Config::define('DB_COLLATE', ''); 73 | $table_prefix = env('DB_PREFIX') ?: 'wp_'; 74 | 75 | if (env('DATABASE_URL')) { 76 | $dsn = (object) parse_url(env('DATABASE_URL')); 77 | 78 | Config::define('DB_NAME', substr($dsn->path, 1)); 79 | Config::define('DB_USER', $dsn->user); 80 | Config::define('DB_PASSWORD', isset($dsn->pass) ? $dsn->pass : null); 81 | Config::define('DB_HOST', isset($dsn->port) ? "{$dsn->host}:{$dsn->port}" : $dsn->host); 82 | } 83 | 84 | /** 85 | * Authentication Unique Keys and Salts 86 | */ 87 | Config::define('AUTH_KEY', env('AUTH_KEY')); 88 | Config::define('SECURE_AUTH_KEY', env('SECURE_AUTH_KEY')); 89 | Config::define('LOGGED_IN_KEY', env('LOGGED_IN_KEY')); 90 | Config::define('NONCE_KEY', env('NONCE_KEY')); 91 | Config::define('AUTH_SALT', env('AUTH_SALT')); 92 | Config::define('SECURE_AUTH_SALT', env('SECURE_AUTH_SALT')); 93 | Config::define('LOGGED_IN_SALT', env('LOGGED_IN_SALT')); 94 | Config::define('NONCE_SALT', env('NONCE_SALT')); 95 | 96 | /** 97 | * Custom Settings 98 | */ 99 | Config::define('AUTOMATIC_UPDATER_DISABLED', true); 100 | Config::define('DISABLE_WP_CRON', env('DISABLE_WP_CRON') ?: false); 101 | // Disable the plugin and theme file editor in the admin 102 | Config::define('DISALLOW_FILE_EDIT', true); 103 | // Disable plugin and theme updates and installation from the admin 104 | Config::define('DISALLOW_FILE_MODS', true); 105 | // Limit the number of post revisions that Wordpress stores (true (default WP): store every revision) 106 | Config::define('WP_POST_REVISIONS', env('WP_POST_REVISIONS') ?: true); 107 | 108 | /** 109 | * Debugging Settings 110 | */ 111 | Config::define('WP_DEBUG_DISPLAY', false); 112 | Config::define('WP_DEBUG_LOG', false); 113 | Config::define('SCRIPT_DEBUG', false); 114 | ini_set('display_errors', '0'); 115 | 116 | /** 117 | * Allow WordPress to detect HTTPS when used behind a reverse proxy or a load balancer 118 | * See https://codex.wordpress.org/Function_Reference/is_ssl#Notes 119 | */ 120 | if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { 121 | $_SERVER['HTTPS'] = 'on'; 122 | } 123 | 124 | $env_config = __DIR__ . '/environments/' . WP_ENV . '.php'; 125 | 126 | if (file_exists($env_config)) { 127 | require_once $env_config; 128 | } 129 | 130 | Config::apply(); 131 | 132 | /** 133 | * Bootstrap WordPress 134 | */ 135 | if (!defined('ABSPATH')) { 136 | define('ABSPATH', $webroot_dir . '/wp/'); 137 | } 138 | -------------------------------------------------------------------------------- /config/environments/development.php: -------------------------------------------------------------------------------- 1 | cpt_slug = substr( __( 'replace_rewrite_name' ), 0, 20 ); 11 | $this->cpt_name = substr( __( 'replace_cpt_name' ), 0, 20 ); 12 | 13 | add_action( 'init', array( $this, 'register_custom_cpt' ) ); 14 | } 15 | 16 | public function register_custom_cpt() { 17 | register_post_type( 18 | $this->cpt_slug, 19 | array( 20 | 'labels' => array( 21 | 'name' => $this->cpt_name, 22 | 'singular_name' => $this->cpt_name, 23 | ), 24 | 'public' => true, 25 | 'has_archive' => true, 26 | 'rewrite' => array( 'slug' => $this->cpt_slug ), 27 | ) 28 | ); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /dev/taxonomy.txt: -------------------------------------------------------------------------------- 1 | taxonomy_slug = 'replace_rewrite_name'; 11 | $this->taxonomy_name = 'replace_taxonomy_name'; 12 | 13 | add_action( 'init', array( $this, 'register_custom_taxonomy' ) ); 14 | } 15 | 16 | public function register_custom_taxonomy() { 17 | $args = array( 18 | 'label' => $this->taxonomy_name, 19 | 'public' => true, 20 | 'rewrite' => false, 21 | 'hierarchical' => true 22 | ); 23 | 24 | register_taxonomy( $this->taxonomy_slug, 'selected_post_type', $args ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /github/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bartosznowak/github", 3 | "authors": [ 4 | { 5 | "name": "bartnovak", 6 | "email": "b.nowak@osomstudio.com" 7 | } 8 | ], 9 | "require": { 10 | "squizlabs/php_codesniffer": "*", 11 | "wp-coding-standards/wpcs": "^2.0" 12 | }, 13 | "scripts": { 14 | "test": "phpcs --standard=WordPress-Extra --extensions=php ../web/app/themes --exclude=WordPress.Files.FileName -s", 15 | "set" : "phpcs --config-set installed_paths vendor/wp-coding-standards/wpcs" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | clear 4 | 5 | echo "Checking if you have required packages." 6 | sleep 2 7 | 8 | phpcs_not_installed=$(which phpcs) 9 | if [[ $phpcs_not_installed == "" ]]; then 10 | echo "PHPCS is not installed" 11 | exit 12 | fi 13 | 14 | phpcbf_not_installed=$(which phpcbf) 15 | if [[ $phpcbf_not_installed == "" ]]; then 16 | echo "PHPCBF is not installed" 17 | exit 18 | fi 19 | 20 | standard_not_installed=$(phpcbf -i | grep "WordPress-Extra") 21 | if [[ -z $standard_not_installed ]]; then 22 | echo "WordPress-Extra standard is not installed" 23 | exit 24 | fi 25 | 26 | echo "Checking if it's fresh install of project." 27 | sleep 2 28 | 29 | FILE=composer.json 30 | 31 | if [ ! -f "$FILE" ]; then 32 | echo "No composer.json found." 33 | exit 34 | fi 35 | 36 | echo "I found composer.json. Let's begin downloading required libraries." 37 | sleep 2 38 | 39 | echo "# DATABASE" >> .env; 40 | read -p "Enter db name: " dbname 41 | echo "DB_NAME=$dbname" >> .env; 42 | read -p "Enter db username: " dbuser 43 | echo "DB_USER=$dbuser" >> .env; 44 | read -p "Enter db password: " dbpwd 45 | echo "DB_PASSWORD=$dbpwd" >> .env; 46 | read -e -p "Enter db host (default: localhost): " -i "localhost" dbhost 47 | echo "DB_HOST=$dbhost" >> .env; 48 | read -e -p "Enter db prefix (default: wp_): " -i "wp_" dbprefix 49 | echo "DB_PREFIX=$dbprefix" >> .env; 50 | echo "#/ DATABASE" >> .env; 51 | echo >> .env; 52 | echo >> .env; 53 | echo "# ENV" >> .env; 54 | echo "WP_ENV=development" >> .env 55 | read -p "Enter local domain name (eg. wordpress.local): " domain 56 | echo "WP_HOME=http://$domain" >> .env; 57 | echo 'WP_SITEURL=${WP_HOME}/wp' >> .env; 58 | read -p "Enter ACF PRO KEY: " acfprokey 59 | echo "ACF_PRO_KEY=$acfprokey" >> .env 60 | echo "#/ ENV" >> .env; 61 | echo >> .env; 62 | echo >> .env; 63 | 64 | htaccess="$(cat '.htaccess')" 65 | > '.htaccess' 66 | echo "$htaccess" | sed -E "s/juniper\\.local/${domain}/g" >> '.htaccess' 67 | 68 | echo "# SALTS" >> .env; 69 | wget -qO - https://api.wordpress.org/secret-key/1.1/salt/ \ 70 | | tr "'" '"' \ 71 | | sed -n -e 's#^define("\([A-Z_]\+\)", \+\("[^"]\+"\));$#\1=\2#p' >> .env 72 | echo "#/ SALTS" >> .env; 73 | 74 | composer install --ignore-platform-reqs 75 | 76 | cd web/app/themes/juniper-theme 77 | npm install 78 | 79 | composer install --ignore-platform-reqs 80 | 81 | echo "That's all. If you set up your DB and htaccess correctly environment should be ready." 82 | -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | # BEGIN WordPress 2 | # The directives (lines) between "BEGIN WordPress" and "END WordPress" are 3 | # dynamically generated, and should only be modified via WordPress filters. 4 | # Any changes to the directives between these markers will be overwritten. 5 | 6 | RewriteEngine On 7 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 8 | RewriteBase / 9 | RewriteRule ^index\.php$ - [L] 10 | RewriteCond %{REQUEST_FILENAME} !-f 11 | RewriteCond %{REQUEST_FILENAME} !-d 12 | RewriteRule . /index.php [L] 13 | 14 | 15 | # END WordPress -------------------------------------------------------------------------------- /web/app/languages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/languages/.gitkeep -------------------------------------------------------------------------------- /web/app/mu-plugins/bedrock-autoloader.php: -------------------------------------------------------------------------------- 1 | mu_plugins = dirname( __FILE__ ); 19 | } 20 | 21 | private function get_name( $assoc_args ) { 22 | if ( ! key_exists( 'name', $assoc_args ) ) { 23 | WP_CLI::error( 'I need to know the name' ); 24 | } 25 | 26 | return $assoc_args['name']; 27 | } 28 | 29 | private function validate_name( $name ) { 30 | if ( strlen( $name ) > 20 ) { 31 | WP_CLI::error( 'The name is too long - Max 20 characters' ); 32 | } 33 | 34 | if ( ! preg_match( '/^[A-Za-z0-9-_ ]+$/i', $name ) ) { 35 | WP_CLI::error( 'Name can only have: letters, spaces, dashes, floors' ); 36 | } 37 | } 38 | 39 | private function get_needed_names( $name ) { 40 | $lowercase_name = strtolower( $name ); 41 | 42 | return array( 43 | 'lowercase_name' => $lowercase_name, 44 | 'slug_name' => str_replace( ' ', '-', $lowercase_name ), 45 | 'rewrite_name' => str_replace( ' ', '-', $lowercase_name ), 46 | ); 47 | } 48 | 49 | /** 50 | * Adds a new custom post type. 51 | * 52 | * ## OPTIONS 53 | * 54 | * [--name] 55 | * : The name of custom post type you want 56 | * 57 | * ## EXAMPLES 58 | * 59 | * wp add cpt --name="Products" 60 | * 61 | * @when before_wp_load 62 | */ 63 | public function cpt( $args, $assoc_args ) { 64 | $og_name = $this->get_name( $assoc_args ); 65 | 66 | $this->validate_name( $og_name ); 67 | 68 | extract( $this->get_needed_names( $og_name ) ); // phpcs:ignore 69 | 70 | $class_name = ucfirst( $slug_name ); 71 | 72 | if ( file_exists( $this->mu_plugins . "/../themes/juniper-theme/inc/Cpt/$class_name.php" ) ) { 73 | WP_CLI::error( 'Custom post type already exists' ); 74 | } 75 | 76 | $replace_array = array( 77 | array( 'replace_cpt_slug', $class_name ), 78 | array( 'replace_cpt_name', $og_name ), 79 | array( 'replace_rewrite_name', $rewrite_name ), 80 | ); 81 | 82 | $file_contents = file_get_contents( $this->mu_plugins . '/../../../dev/cpt.txt' ); // phpcs:ignore 83 | foreach ( $replace_array as $search_replace ) { 84 | $file_contents = str_replace( $search_replace[0], $search_replace[1], $file_contents ); 85 | } 86 | 87 | file_put_contents( $this->mu_plugins . "/../themes/juniper-theme/inc/Cpt/$class_name.php", $file_contents ); 88 | 89 | $new_class = "\$juniper_$slug_name = new \Juniper\Cpt\\$class_name();" . PHP_EOL; 90 | file_put_contents( $this->mu_plugins . '/../themes/juniper-theme/inc/include.php', $new_class, FILE_APPEND ); 91 | 92 | shell_exec( 'phpcbf --standard=WordPress-Extra ' . $this->mu_plugins . "/../themes/juniper-theme/inc/Cpt/$slug_name.php" ); 93 | shell_exec( 'phpcbf --standard=WordPress-Extra ' . $this->mu_plugins . '/../themes/juniper-theme/inc/include.php' ); 94 | } 95 | 96 | /** 97 | * Adds a new taxonomy and attaches it to a post type. 98 | * 99 | * ## OPTIONS 100 | * 101 | * [--name] 102 | * : The name of custom taxonomy you want to add 103 | * 104 | * [--post] 105 | * : The name of the custom post type that this taxonomy should be attached to 106 | * 107 | * ## EXAMPLES 108 | * 109 | * wp add taxonomy --name="Categories" --post="products" 110 | * 111 | * @when before_wp_load 112 | */ 113 | public function taxonomy( $args, $assoc_args ) { 114 | $og_name = $this->get_name( $assoc_args ); 115 | 116 | if ( ! key_exists( 'post', $assoc_args ) ) { 117 | WP_CLI::error( 'I need to know the name of the custom post type' ); 118 | } 119 | 120 | $post_cpt = $assoc_args['post']; 121 | 122 | $this->validate_name( $og_name ); 123 | $this->validate_name( $post_cpt ); 124 | 125 | extract( $this->get_needed_names( $og_name ) ); 126 | 127 | if ( file_exists( $this->mu_plugins . "/../themes/juniper-theme/inc/Taxonomies/$slug_name.php" ) ) { 128 | WP_CLI::error( 'Taxonomy already exists' ); 129 | } 130 | 131 | $replace_array = array( 132 | array( 'replace_taxonomy_slug', $slug_name ), 133 | array( 'replace_taxonomy_name', $og_name ), 134 | array( 'replace_rewrite_name', $rewrite_name ), 135 | array( 'selected_post_type', $post_cpt ), 136 | ); 137 | 138 | $file_contents = file_get_contents( $this->mu_plugins . '/../../../dev/taxonomy.txt' ); 139 | foreach ( $replace_array as $search_replace ) { 140 | $file_contents = str_replace( $search_replace[0], $search_replace[1], $file_contents ); 141 | } 142 | 143 | file_put_contents( $this->mu_plugins . "/../themes/juniper-theme/inc/Taxonomies/$slug_name.php", $file_contents ); 144 | 145 | $new_class = "\$juniper_$slug_name = new \Juniper\Taxonomies\\$slug_name();" . PHP_EOL; 146 | file_put_contents( $this->mu_plugins . '/../themes/juniper-theme/inc/include.php', $new_class, FILE_APPEND ); 147 | 148 | shell_exec( 'phpcbf --standard=WordPress-Extra ' . $this->mu_plugins . '/../themes/juniper-theme/inc/include.php' ); 149 | } 150 | 151 | 152 | /** 153 | * Adds a Gutenberg block. 154 | * 155 | * ## OPTIONS 156 | * 157 | * [--name] 158 | * : The name of the new Gutenberg block 159 | * 160 | * [--keywords] 161 | * : Optional keywords of the Gutenberg block 162 | * 163 | * [--description] 164 | * : Optional description of the Gutenberg block 165 | * 166 | * ## EXAMPLES 167 | * wp add block --name="Reviews" 168 | * wp add block --name="Reviews" --keywords="people,stars,quotes" 169 | * wp add block --name="Reviews" --keywords="people,stars,quotes" --description="This section shows the three newest reviews" 170 | * 171 | * @when before_wp_load 172 | */ 173 | public function block( $args, $assoc_args ) { 174 | $og_name = $this->get_name( $assoc_args ); 175 | 176 | if ( ! preg_match( '/^[A-Za-z0-9-_ ]+$/i', $og_name ) ) { 177 | WP_CLI::error( 'Name can only have: letters, spaces, dashes, floors' ); 178 | } 179 | 180 | $lowercase_name = strtolower( $og_name ); 181 | $slug_name = str_replace( ' ', '-', $lowercase_name ); 182 | 183 | if ( file_exists( "../themes/juniper-theme/blocks/$slug_name/" ) ) { 184 | WP_CLI::error( 'Block already exists' ); 185 | } 186 | 187 | mkdir( $this->mu_plugins . "/../themes/juniper-theme/blocks/$slug_name/", 0755 ); 188 | 189 | $keywords = ''; 190 | if ( key_exists( 'keywords', $assoc_args ) ) { 191 | $keywords = $assoc_args['keywords']; 192 | } 193 | 194 | $description = ''; 195 | if ( key_exists( 'description', $assoc_args ) ) { 196 | $description = $assoc_args['description']; 197 | } 198 | 199 | file_put_contents( $this->mu_plugins . "/../themes/juniper-theme/blocks/$slug_name/script.js", '' ); 200 | file_put_contents( $this->mu_plugins . "/../themes/juniper-theme/blocks/$slug_name/ajax.js", '' ); 201 | 202 | $css = ".$slug_name {}\n\n" . 203 | "body.wp-admin {\n" . 204 | "\t.$slug_name {}\n" . 205 | '}'; 206 | file_put_contents( $this->mu_plugins . "/../themes/juniper-theme/blocks/$slug_name/style.scss", $css ); 207 | 208 | $php = "mu_plugins . "/../themes/juniper-theme/blocks/$slug_name/functions.php", $php ); 226 | 227 | $html = "{#\n" . 228 | "\tTitle: $og_name\n" . 229 | "\tDescription: $description\n" . 230 | "\tCategory: formatting\n" . 231 | "\tIcon: admin-comments\n" . 232 | "\tKeywords: $keywords\n" . 233 | "\tMode: edit\n" . 234 | "\tAlign: full\n" . 235 | "\tSupportsAlign: left right full\n" . 236 | "\tSupportsMode: true\n" . 237 | "\tSupportsMultiple: true\n" . 238 | '#}'; 239 | file_put_contents( $this->mu_plugins . "/../themes/juniper-theme/views/blocks/$slug_name.twig", $html ); 240 | 241 | shell_exec( 'phpcbf -d error_reporting="E_ALL&~E_DEPRECATED" --standard="WordPress-Extra" ' . $this->mu_plugins . "/../themes/juniper-theme/Blocks/$slug_name/functions.php" ); 242 | } 243 | } 244 | 245 | WP_CLI::add_command( 'add', 'Juniper_Commands' ); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /web/app/mu-plugins/register-theme-directory.php: -------------------------------------------------------------------------------- 1 | ` block. 109 | Template parts in Gutenberg can be registered in the theme.json file, allowing you to define reusable sections such as headers, footers, or sidebars. This helps structure the theme and makes it easier to manage global components. 110 | ```json 111 | { 112 | "templateParts": [ 113 | { 114 | "area": "footer", 115 | "name": "footer", 116 | "title": "Footer" 117 | }, 118 | { 119 | "area": "header", 120 | "name": "header", 121 | "title": "Header" 122 | }, 123 | { 124 | "area": "header", 125 | "name": "top-bar", 126 | "title": "Top bar" 127 | } 128 | ] 129 | } 130 | ``` 131 | 132 | ## Block patterns 133 | Block patterns are predefined layouts of blocks that can be inserted into the editor with a single click. They are stored in the patterns directory. 134 | You can see an exmaple in the `juniper-theme` in the `patterns` directory in `cta.php` file. 135 | 136 | ### Pattern categories 137 | Example: 138 | ```php 139 | register_block_pattern_category( 140 | 'cta', 141 | array( 'label' => __( 'CTA', 'juniper-theme' ) ) 142 | ); 143 | ``` 144 | 145 | ## Block styles 146 | Block styles are predefined styles for blocks that can be applied with a single click. An example of block style registration: 147 | ```php 148 | add_action( 'init', 'juniper_register_blocks_styles' ); 149 | function juniper_register_blocks_styles() : void { 150 | register_block_style( 151 | 'core/button', 152 | array( 153 | 'name' => 'arrowed', 154 | 'label' => __( 'Arrowed', 'juniper' ), 155 | ) 156 | ); 157 | } 158 | ``` 159 | 160 | ## ACF Blocks 161 | ACF Blocks are good for creating template parts, such us Header, Navigation, Footer and other reusable blocks. 162 | Example of adding ACF block: 163 | `` 164 | 165 | ## Development workflow 166 | 1. Start from configuration in `theme.json` file. 167 | 2. Register block styles and pattern categories. 168 | 3. Create Design System template in the `templates` directory. 169 | 4. Add blocks to the Design System template. 170 | 5. Create template parts in the `parts` directory. 171 | 6. Start creating the rest blocks with use of Gutenberg and ACF. 172 | 7. Register created blocks in the `patterns` directory. 173 | 174 | Source: https://fullsiteediting.com/ -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/acf-json/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/acf-json/.gitkeep -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/blocks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/blocks/.gitkeep -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/blocks/cta/ajax.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/blocks/cta/ajax.js -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/blocks/cta/functions.php: -------------------------------------------------------------------------------- 1 | { 3 | event.preventDefault(); 4 | 5 | const ajax_data = { 6 | action: 'filteringposts', 7 | nonce: ajax.nonce, 8 | // custom fields below. 9 | inputajax1: jQuery('input[name="inputajax1"]').val(), 10 | inputajax2: jQuery('input[name="inputajax2"]').val(), 11 | inputajax3: jQuery('input[name="inputajax3"]').val(), 12 | }; 13 | 14 | jQuery.ajax( 15 | { 16 | type: 'POST', 17 | url: ajax.ajax_url, 18 | data: ajax_data, 19 | success(data) { 20 | console.log(data); 21 | }, 22 | error(xhr, status, errorThrown) { 23 | console.log('Custom ajax error'); 24 | }, 25 | }, 26 | ); 27 | }, 28 | ); 29 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/blocks/filteringposts/functions.php: -------------------------------------------------------------------------------- 1 | tag in the document head, and expect WordPress to 34 | * provide it for us. 35 | */ 36 | add_theme_support( 'title-tag' ); 37 | 38 | /* 39 | * Enable support for Post Thumbnails on posts and pages. 40 | * 41 | * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ 42 | */ 43 | add_theme_support( 'post-thumbnails' ); 44 | 45 | /* 46 | * Switch default core markup for search form, comment form, and comments 47 | * to output valid HTML5. 48 | */ 49 | add_theme_support( 50 | 'html5', 51 | array( 52 | 'comment-form', 53 | 'comment-list', 54 | 'gallery', 55 | 'caption', 56 | ) 57 | ); 58 | 59 | /* 60 | * Enable support for Post Formats. 61 | * 62 | * See: https://codex.wordpress.org/Post_Formats 63 | */ 64 | add_theme_support( 65 | 'post-formats', 66 | array( 67 | 'aside', 68 | 'image', 69 | 'video', 70 | 'quote', 71 | 'link', 72 | 'gallery', 73 | 'audio', 74 | ) 75 | ); 76 | 77 | add_theme_support( 'menus' ); 78 | } 79 | 80 | /** This Would return 'foo bar!'. 81 | * 82 | * @param string $text being 'foo', then returned 'foo bar!'. 83 | */ 84 | public function myfoo( $text ) { 85 | $text .= ' bar!'; 86 | return $text; 87 | } 88 | 89 | /** This is where you can add your own functions to twig. 90 | * 91 | * @param string $twig get extension. 92 | */ 93 | public function add_to_twig( $twig ) { 94 | $twig->addExtension( new Twig\Extension\StringLoaderExtension() ); 95 | $twig->addFilter( new Twig\TwigFilter( 'myfoo', array( $this, 'myfoo' ) ) ); 96 | return $twig; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "junipertheme/junipertheme", 3 | "type": "library", 4 | "authors": [ 5 | { 6 | "name": "bartnovak", 7 | "email": "b.nowak@osomstudio.com" 8 | } 9 | ], 10 | "autoload": { 11 | "psr-4": { 12 | "Juniper\\": "inc" 13 | } 14 | }, 15 | "require": { 16 | "php": ">=8.1", 17 | "timber/timber": "^2.0", 18 | "bartnovak/timber-acf-wp-blocks": "^0.1.0" 19 | }, 20 | "config": { 21 | "optimize-autoloader": true, 22 | "preferred-install": "dist", 23 | "allow-plugins": { 24 | "composer/installers": true 25 | }, 26 | "sort-packages": true, 27 | "platform": { 28 | "php": "8.1" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("cypress"); 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | setupNodeEvents(on, config) { 6 | // implement node event listeners here 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/cypress/e2e/blockRestAPI.cy.js: -------------------------------------------------------------------------------- 1 | describe('BlockRestAPI', () => { 2 | it('passes', () => { 3 | var homeURL = ''; 4 | var request = '/wp-json/wp/v2/'; 5 | 6 | if ( homeURL === '' ) { 7 | alert('Fill homeURL in this test .cy.js file.'); 8 | } 9 | 10 | cy.request({ 11 | method: 'GET', 12 | url: homeURL + request, 13 | body: {name: "test"}, 14 | failOnStatusCode: false 15 | }) 16 | .then(response => { 17 | expect(response.status).to.equal(401) 18 | }) 19 | 20 | }) 21 | }) -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/functions.php: -------------------------------------------------------------------------------- 1 |

Timber not activated. Make sure you activate the plugin in ' . esc_url( admin_url( 'plugins.php' ) ) . '

'; 51 | } 52 | ); 53 | 54 | add_filter( 55 | 'template_include', 56 | function ( $template ) { 57 | return get_stylesheet_directory() . '/static/no-timber.html'; 58 | } 59 | ); 60 | return; 61 | } 62 | 63 | /** 64 | * Sets the directories (inside your theme) to find .twig files 65 | */ 66 | Timber::$dirname = array( 'templates', 'views' ); 67 | 68 | /** 69 | * By default, Timber does NOT autoescape values. Want to enable Twig's autoescape? 70 | * No prob! Just set this value to true 71 | */ 72 | Timber::$autoescape = false; 73 | 74 | 75 | //StarterSite class 76 | require_once 'class-startersite.php'; 77 | new StarterSite(); 78 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/inc/Ajax/JuniperAjaxFilteringposts.php: -------------------------------------------------------------------------------- 1 | self::TEXT_FIELD, 34 | 'inputajax2' => self::TEXT_FIELD, 35 | 'inputajax3' => self::TEXT_FIELD, 36 | ); 37 | 38 | /** 39 | * Array with input values after sanitization 40 | * 41 | * @var array 42 | */ 43 | private array $ajax_fields_sanitized = array(); 44 | 45 | /** 46 | * Ajax class constructor. Basic properties setting, ajax necessary actions registered, enqueue ajax script with localize. 47 | * 48 | * @param string $action_name Ajax action name. 49 | * @param string $js_directory string Directory with js file to be used with this ajax module. 50 | * @param string $block_name Block name 51 | */ 52 | public function __construct( $action_name, $js_directory, $block_name ) { 53 | $this->action_name = $action_name; 54 | $this->js_directory = $js_directory; 55 | $this->block_name = $block_name; 56 | 57 | add_action( 'wp_ajax_' . $this->action_name, array( $this, 'ajax_action_handler' ) ); 58 | add_action( 'wp_ajax_nopriv_' . $this->action_name, array( $this, 'ajax_action_handler' ) ); 59 | add_action( 'wp_enqueue_scripts', array( $this, 'register_script' ) ); 60 | } 61 | 62 | /** 63 | * Main ajax handler method. Do not make spaghetti code - if you want to parse data create separate method. 64 | * 65 | * @return void wp_send_json always ends with wp_die 66 | */ 67 | public function ajax_action_handler() { 68 | // We are setting up input names and their type in $ajax_fields_types first to sanitize them automatically. 69 | $this->parse_ajax_fields(); 70 | 71 | // Custom methods to parse data. 72 | 73 | /** 74 | * Method needs to end with data sent to JS. If error occurs during the processing of 75 | * method, we need to send $this->send_ajax_error(). 76 | */ 77 | $this->send_ajax_success( $this->ajax_fields_sanitized ); 78 | } 79 | 80 | /** 81 | * Methods used to register necessary scripts and localize them. 82 | * 83 | * @return void 84 | */ 85 | public function register_script() { 86 | if ( has_block( 'acf/' . $this->block_name ) ) { 87 | wp_enqueue_script( 'ajax-' . $this->action_name, $this->js_directory, array( 'jquery' ), time(), true ); 88 | wp_localize_script( 89 | 'ajax-' . $this->action_name, 90 | 'ajax', 91 | array( 92 | 'ajax_url' => admin_url( 'admin-ajax.php' ), 93 | 'action_name' => $this->action_name, 94 | 'nonce' => wp_create_nonce( $this->action_name ), 95 | ) 96 | ); 97 | } 98 | } 99 | 100 | /** 101 | * Validation method for fields used in ajax request - $this->ajax_fields_sanitized 102 | * 103 | * 104 | * @return void 105 | */ 106 | private function parse_ajax_fields() { 107 | if ( ! check_ajax_referer( $this->action_name, 'nonce' ) ) { 108 | $this->send_ajax_error( __( 'Unauthorized request. Try again later.' ) ); 109 | } 110 | 111 | foreach ( $this->ajax_fields_types as $field_name => $sanitization_type ) { 112 | if ( ! isset( $_POST[ $field_name ] ) ) { 113 | continue; 114 | } 115 | 116 | switch ( $sanitization_type ) { 117 | case self::TEXT_FIELD: 118 | $this->ajax_fields_sanitized[ $field_name ] = sanitize_text_field( wp_unslash( $_POST[ $field_name ] ) ); 119 | break; 120 | case self::TEXTAREA_FIELD: 121 | $this->ajax_fields_sanitized[ $field_name ] = sanitize_textarea_field( wp_unslash( $_POST[ $field_name ] ) ); 122 | break; 123 | case self::EMAIL_FIELD: 124 | $this->ajax_fields_sanitized[ $field_name ] = sanitize_email( wp_unslash( $_POST[ $field_name ] ) ); 125 | break; 126 | default: 127 | $this->ajax_fields_sanitized[ $field_name ] = sanitize_text_field( wp_unslash( $_POST[ $field_name ] ) ); 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Universal method for returning request error data. 134 | * 135 | * @param string $error_msg Error message, remember to use __() function. 136 | * @return void wp_send_json ends with wp_die. 137 | */ 138 | private function send_ajax_error( string $error_msg ) { 139 | wp_send_json( 140 | array( 141 | 'result' => false, 142 | 'msg' => $error_msg, 143 | ) 144 | ); 145 | } 146 | 147 | /** 148 | * @param array $data Array with response data. 149 | * @return void wp_send_json ends with wp_die. 150 | */ 151 | private function send_ajax_success( array $data ) { 152 | wp_send_json( 153 | array( 154 | 'result' => true, 155 | 'data' => $data, 156 | ) 157 | ); 158 | } 159 | 160 | } 161 | 162 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/inc/Blocks/JuniperBlocks.php: -------------------------------------------------------------------------------- 1 | blocks_dir = get_template_directory() . '/blocks'; 11 | $this->blocks_list = $this->get_all_blocks(); 12 | 13 | } 14 | 15 | private function get_all_blocks() { 16 | return array_values( array_diff( scandir( $this->blocks_dir ), array( '..', '.', '.gitkeep' ) ) ); 17 | } 18 | 19 | public function include_blocks_functions() { 20 | foreach ( $this->blocks_list as $single_block ) { 21 | require_once get_template_directory() . '/blocks/' . $single_block . '/functions.php'; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/inc/Cpt/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/inc/Cpt/.gitkeep -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/inc/Taxonomies/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/inc/Taxonomies/.gitkeep -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/inc/gutenberg-styles.php: -------------------------------------------------------------------------------- 1 | 'arrowed', 8 | 'label' => __( 'Arrowed', 'juniper' ), 9 | ) 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/inc/include.php: -------------------------------------------------------------------------------- 1 | include_blocks_functions(); 8 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/inc/pattern-categories.php: -------------------------------------------------------------------------------- 1 | __( 'CTA', 'juniper-theme' ) ) 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "**/node_modules", 4 | "wp-admin", 5 | "wp-includes" 6 | ] 7 | } -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parcel-juniper-theme", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@barba/core": "^2.9", 6 | "@barba/prefetch": "^2.1", 7 | "bootstrap": "^5.3", 8 | "cypress": "^13.9", 9 | "gsap": "^3.12.5", 10 | "imagesloaded": "^5.0", 11 | "jquery": "^3.7", 12 | "postcss": "^8.4" 13 | }, 14 | "devDependencies": { 15 | "@parcel/transformer-sass": "^2.12", 16 | "browserslist": "^4.23", 17 | "eslint": "^8.56", 18 | "eslint-config-airbnb-base": "^15.0", 19 | "husky": "^9.0", 20 | "litepicker": "^2.0", 21 | "parcel": "^2.12", 22 | "sass": "^1.77" 23 | }, 24 | "scripts": { 25 | "test": "echo \"Error: no test specified\" && exit 1", 26 | "dev": "parcel watch src/css/_app.scss src/js/_app.js blocks/**/*.js blocks/**/*.scss --public-url ./ --no-source-maps", 27 | "build": "parcel build src/js/_app.js src/css/_app.scss blocks/**/*.js blocks/**/*.scss --public-url ./", 28 | "cypress": "./node_modules/.bin/cypress open", 29 | "flint": "eslint --fix ./src/js/**/*", 30 | "pretest": "eslint --ignore-path .gitignore ./src/js/**/* --fix && eslint --ignore-path .gitignore ./blocks/**/*.js --fix" 31 | }, 32 | "husky": { 33 | "hooks": { 34 | "pre-commit": "npm run pretest" 35 | } 36 | }, 37 | "author": "osom", 38 | "license": "ISC", 39 | "browserslist": [ 40 | "last 2 versions" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/parts/footer.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |

parts/footer.html

6 | 7 |
8 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/parts/header.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

parts/header.html

5 | 6 |
7 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/parts/top-bar.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Test

4 | 5 | 6 | 7 |

Paragraph

8 |
9 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/patterns/cta.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 |
13 |

Testing 123123

14 | 15 | 16 | 17 |

Paragraph

18 |
19 | 20 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/phpunit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 16 | 17 | ./tests/ 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/screenshot.png -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/_app.scss: -------------------------------------------------------------------------------- 1 | @use 'base'; 2 | @use 'vendor/swiper'; -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/base/_common.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/src/css/base/_common.scss -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/base/_index.scss: -------------------------------------------------------------------------------- 1 | @forward 'reset'; 2 | @forward 'variables'; 3 | @forward 'mixins'; 4 | @forward 'typography'; 5 | @forward 'common'; -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/base/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin grid { 2 | margin: 0 auto; 3 | width: 90%; 4 | position: relative; 5 | 6 | @include media($gridWidth + 100, min) { 7 | width: $gridWidth; 8 | } 9 | } 10 | 11 | /* 12 | * Centers element by type 13 | */ 14 | @mixin center($type: flex) { 15 | @if $type=='flex' { 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | } 20 | 21 | @else { 22 | position: absolute; 23 | top: 50%; 24 | left: 50%; 25 | transform: translate(-50%, -50%); 26 | } 27 | } 28 | 29 | 30 | /* 31 | * Sets element media query 32 | */ 33 | @mixin media($breakpoint, $type: max) { 34 | @media screen and (#{$type}-width: #{$breakpoint}) { 35 | @content; 36 | } 37 | } 38 | 39 | /* 40 | * Flexbox row system 41 | */ 42 | @mixin flex($align:'start', $space:'start') { 43 | display: flex; 44 | flex-wrap: wrap; 45 | 46 | @if $align=='center' { 47 | align-items: center; 48 | } 49 | 50 | @if $align=='start' { 51 | align-items: flex-start; 52 | } 53 | 54 | @if $align=='end' { 55 | align-items: flex-end; 56 | } 57 | 58 | @if $align=='strech' { 59 | align-items: stretch; 60 | } 61 | 62 | @if $space=='center' { 63 | justify-content: center; 64 | } 65 | 66 | @if $space=='space' { 67 | justify-content: space-between; 68 | } 69 | 70 | @if $space=='end' { 71 | justify-content: flex-end; 72 | } 73 | } 74 | 75 | /* 76 | * Column width system 77 | */ 78 | @mixin col($items) { 79 | @include media($mobile, min) { 80 | width: calc(8.3333333% * #{$items}); 81 | } 82 | } 83 | 84 | /* 85 | * Styles input placeholder 86 | * This is getting prefixed by gulp 87 | */ 88 | @mixin input-placeholder { 89 | &::placeholder { 90 | @content; 91 | } 92 | } 93 | 94 | /* 95 | * Fills relative parent width and height 96 | */ 97 | @mixin cover { 98 | position: absolute; 99 | top: 0; 100 | left: 0; 101 | width: 100%; 102 | height: 100%; 103 | display: block; 104 | } 105 | 106 | /* 107 | * Includes pseudo element if param is true 108 | * Wrapper for @cover 109 | */ 110 | @mixin fw { 111 | position: relative; 112 | 113 | &::after { 114 | content: ''; 115 | @include cover(); 116 | } 117 | } 118 | 119 | 120 | /* 121 | * Sets item width and height 122 | */ 123 | @mixin box($width, $height:$width, $display:inline-flex) { 124 | width: $width; 125 | height: $height; 126 | display: $display; 127 | } 128 | 129 | /* 130 | * Includes animation with params 131 | */ 132 | @mixin keyframes($name, $params) { 133 | animation: #{$name} #{$params}; 134 | 135 | @keyframes #{$name} { 136 | @content; 137 | } 138 | } 139 | 140 | /* 141 | * Keeps item aspect ratio 142 | * Item height is based on padding-top 143 | */ 144 | @mixin ratio($x, $y) { 145 | $padding: unquote(($y / $x) * 100 + '%'); 146 | padding-top: $padding; 147 | } 148 | 149 | /* 150 | * Px to vw calculator 151 | * Remember to define your laytout width in _app.scss 152 | */ 153 | @function vw($px) { 154 | $vw: ($projectWidth * 0.01) * 1px; 155 | @return ($px / $vw) * 1vw; 156 | } 157 | 158 | @mixin fixed { 159 | position: fixed; 160 | top: 0; 161 | bottom: 0; 162 | right: 0; 163 | left: 0; 164 | display: flex; 165 | justify-content: center; 166 | align-items: center; 167 | } 168 | 169 | @mixin pushup($i:2, $pos:relative) { 170 | z-index: $i; 171 | position: $pos; 172 | } 173 | 174 | @function randomNum($min, $max) { 175 | $rand: random(); 176 | $randomNum: $min + floor($rand * (($max - $min) + 1)); 177 | 178 | @return $randomNum; 179 | } -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/base/_reset.scss: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | vertical-align: baseline; 4 | scroll-behavior: smooth; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | margin: 0; 11 | padding: 0; 12 | box-sizing: inherit; 13 | } 14 | 15 | img, 16 | body, 17 | article, 18 | main, 19 | aside, 20 | address, 21 | details, 22 | figcaption, 23 | figure, 24 | footer, 25 | header, 26 | nav { 27 | display: block; 28 | } 29 | 30 | img { 31 | max-width: 100%; 32 | } 33 | 34 | ol, 35 | ul { 36 | list-style: none 37 | } 38 | 39 | li:empty, 40 | p:empty { 41 | display: none; 42 | } 43 | 44 | textarea, 45 | select, 46 | input, 47 | button { 48 | -webkit-appearance: none; 49 | -moz-appearance: none; 50 | appearance: none; 51 | color: inherit; 52 | background: transparent; 53 | border: none; 54 | } 55 | 56 | strong { 57 | font-weight: bold; 58 | } 59 | 60 | a { 61 | text-decoration: none; 62 | color: inherit; 63 | 64 | &:hover { 65 | text-decoration: none; 66 | } 67 | } 68 | 69 | :focus, 70 | :active { 71 | outline: none; 72 | } -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/base/_typography.scss: -------------------------------------------------------------------------------- 1 | html { 2 | line-height: 1.15; 3 | -webkit-font-smoothing: antialiased; 4 | -webkit-text-size-adjust: 100%; 5 | font-size: 1rem; 6 | } 7 | 8 | body { 9 | font-family: myriad-pro, myriad-pro-condensed, myriad-pro-semiextended, myriad-pro-semi-condensed, Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; 10 | font-weight: inherit; 11 | } -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/base/_variables.scss: -------------------------------------------------------------------------------- 1 | /* Assign variables with values from theme.json */ -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/components/_buttons.scss: -------------------------------------------------------------------------------- 1 | a, 2 | button, 3 | .button { 4 | transition: 300; 5 | cursor: pointer; 6 | } -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/components/_forms.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/src/css/components/_forms.scss -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/css/vendor/swiper.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --swiper-theme-color: #333; 3 | } 4 | 5 | .swiper-container { 6 | margin-left: auto; 7 | margin-right: auto; 8 | position: relative; 9 | overflow: hidden; 10 | list-style: none; 11 | padding: 0; 12 | /* Fix of Webkit flickering */ 13 | z-index: 1; 14 | width: 100%; 15 | } 16 | 17 | .swiper-container-vertical>.swiper-wrapper { 18 | flex-direction: column; 19 | } 20 | 21 | .swiper-wrapper { 22 | position: relative; 23 | width: 100%; 24 | height: 100%; 25 | z-index: 1; 26 | display: flex; 27 | transition-property: transform; 28 | box-sizing: content-box; 29 | } 30 | 31 | .swiper-container-android .swiper-slide, 32 | .swiper-wrapper { 33 | transform: translate3d(0px, 0, 0); 34 | } 35 | 36 | .swiper-container-multirow>.swiper-wrapper { 37 | flex-wrap: wrap; 38 | } 39 | 40 | .swiper-container-multirow-column>.swiper-wrapper { 41 | flex-wrap: wrap; 42 | flex-direction: column; 43 | } 44 | 45 | .swiper-container-free-mode>.swiper-wrapper { 46 | transition-timing-function: ease-out; 47 | margin: 0 auto; 48 | } 49 | 50 | .swiper-slide { 51 | flex-shrink: 0; 52 | width: 100%; 53 | height: 100%; 54 | position: relative; 55 | transition-property: transform; 56 | } 57 | 58 | .swiper-slide-invisible-blank { 59 | visibility: hidden; 60 | } 61 | 62 | /* Auto Height */ 63 | .swiper-container-autoheight { 64 | 65 | &, 66 | .swiper-slide { 67 | height: auto; 68 | } 69 | 70 | .swiper-wrapper { 71 | align-items: flex-start; 72 | transition-property: transform, height; 73 | } 74 | } 75 | 76 | /* 3D Effects */ 77 | .swiper-container-3d { 78 | perspective: 1200px; 79 | 80 | .swiper-wrapper, 81 | .swiper-slide, 82 | .swiper-slide-shadow-left, 83 | .swiper-slide-shadow-right, 84 | .swiper-slide-shadow-top, 85 | .swiper-slide-shadow-bottom, 86 | .swiper-cube-shadow { 87 | transform-style: preserve-3d; 88 | } 89 | 90 | .swiper-slide-shadow-left, 91 | .swiper-slide-shadow-right, 92 | .swiper-slide-shadow-top, 93 | .swiper-slide-shadow-bottom { 94 | position: absolute; 95 | left: 0; 96 | top: 0; 97 | width: 100%; 98 | height: 100%; 99 | pointer-events: none; 100 | z-index: 10; 101 | } 102 | 103 | .swiper-slide-shadow-left { 104 | background-image: linear-gradient(to left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 105 | } 106 | 107 | .swiper-slide-shadow-right { 108 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 109 | } 110 | 111 | .swiper-slide-shadow-top { 112 | background-image: linear-gradient(to top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 113 | } 114 | 115 | .swiper-slide-shadow-bottom { 116 | background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 117 | } 118 | } 119 | 120 | /* CSS Mode */ 121 | .swiper-container-css-mode { 122 | >.swiper-wrapper { 123 | overflow: auto; 124 | scrollbar-width: none; 125 | /* For Firefox */ 126 | -ms-overflow-style: none; 127 | 128 | /* For Internet Explorer and Edge */ 129 | &::-webkit-scrollbar { 130 | display: none; 131 | } 132 | } 133 | 134 | >.swiper-wrapper>.swiper-slide { 135 | scroll-snap-align: start start; 136 | } 137 | } 138 | 139 | .swiper-container-horizontal.swiper-container-css-mode { 140 | >.swiper-wrapper { 141 | scroll-snap-type: x mandatory; 142 | } 143 | } 144 | 145 | .swiper-container-vertical.swiper-container-css-mode { 146 | >.swiper-wrapper { 147 | scroll-snap-type: y mandatory; 148 | } 149 | } 150 | 151 | .swiper-button { 152 | height: 10px; 153 | 154 | &-next, 155 | &-prev { 156 | width: 30px; 157 | height: 30px; 158 | background: #ccc; 159 | position: absolute; 160 | top: 50%; 161 | transform: translateY(-50%); 162 | z-index: 2; 163 | cursor: pointer; 164 | } 165 | 166 | &-prev { 167 | left: 5%; 168 | } 169 | 170 | &-next { 171 | right: 5%; 172 | } 173 | } 174 | 175 | .swiper-pagination { 176 | position: absolute; 177 | bottom: 20px; 178 | left: 20px; 179 | z-index: 2; 180 | display: flex; 181 | 182 | &-bullet { 183 | display: block; 184 | width: 10px; 185 | height: 10px; 186 | background: #ccc; 187 | margin-right: 5px; 188 | 189 | &-active { 190 | background: red; 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/img/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/src/img/sample.jpg -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/js/_app.js: -------------------------------------------------------------------------------- 1 | import nav from './nav'; 2 | 3 | export default class App { 4 | initCore() { 5 | nav.hamburger(); 6 | } 7 | } 8 | 9 | function LoadApp() { 10 | const app = new App(); 11 | app.initCore(); 12 | } 13 | 14 | if (document.readyState === 'loading') { 15 | document.addEventListener('DOMContentLoaded', LoadApp); 16 | } else { 17 | LoadApp(); 18 | } 19 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/js/animations/transitions.js: -------------------------------------------------------------------------------- 1 | import barba from '@barba/core'; 2 | import barbaPrefetch from '@barba/prefetch'; 3 | import App from '../_app'; 4 | 5 | export default function () { 6 | barba.use(barbaPrefetch); 7 | barba.init({ 8 | transitions: [{ 9 | leave(data) { // eslint-disable-line no-unused-vars 10 | const done = this.async(); 11 | done(); 12 | document.body.classList.remove('nav--toggled'); 13 | document.querySelector('header').scrollTo({ behavior: 'smooth' }); 14 | }, 15 | enter(data) { // eslint-disable-line no-unused-vars 16 | const done = this.async(); 17 | done(); // call on transition end 18 | 19 | App(); // reinit everything inside .main 20 | }, 21 | }], 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/src/js/nav.js: -------------------------------------------------------------------------------- 1 | const nav = { 2 | hamburger: () => { 3 | const hamburger = document.querySelector('.nav-toggle'); 4 | if (!hamburger) return; 5 | 6 | const container = document.querySelector('body'); 7 | hamburger.addEventListener('click', () => { 8 | hamburger.classList.toggle('nav--toggled'); 9 | container.classList.toggle('nav--toggled'); 10 | }); 11 | }, 12 | }; 13 | 14 | export default nav; 15 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/static/no-timber.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Timber not active 5 | 6 | 7 | 8 |

Timber not activated

9 | 10 | 11 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/static/site.js: -------------------------------------------------------------------------------- 1 | jQuery( document ).ready( function( $ ) { 2 | 3 | // Your JavaScript goes here 4 | 5 | }); -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Theme Name: Juniper Theme 3 | * Description: OsomStudio 4 | * Author: OsomStudio 5 | * Author URI: http://osomstudio.com 6 | * Version: 2.1 7 | */ 8 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/templates/404.html: -------------------------------------------------------------------------------- 1 |

ERROR 404

-------------------------------------------------------------------------------- /web/app/themes/juniper-theme/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/templates/my-custom-template.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/templates/my-custom-template.html -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schemas.wp.org/trunk/theme.json", 3 | "version": 2, 4 | "settings": { 5 | "typography": { 6 | "fontSizes": [ 7 | { 8 | "name": "Mini", 9 | "size": "13px", 10 | "slug": "mini" 11 | }, 12 | { 13 | "name": "X Small", 14 | "size": "16px", 15 | "slug": "x-small" 16 | }, 17 | { 18 | "name": "Small", 19 | "size": "18px", 20 | "slug": "small" 21 | }, 22 | { 23 | "name": "Medium", 24 | "size": "20px", 25 | "slug": "medium" 26 | }, 27 | { 28 | "name": "Large", 29 | "size": "24px", 30 | "slug": "large" 31 | }, 32 | { 33 | "name": "X Large", 34 | "size": "32px", 35 | "slug": "x-large" 36 | }, 37 | { 38 | "name": "XX Large", 39 | "size": "40px", 40 | "slug": "xx-large" 41 | }, 42 | { 43 | "name": "XXX Large", 44 | "size": "48px", 45 | "slug": "xxx-large" 46 | } 47 | ], 48 | "fontFamilies": [ 49 | { 50 | "name": "Default", 51 | "slug": "default", 52 | "fallbacks": [ 53 | "system-ui", 54 | "sans-serif" 55 | ] 56 | } 57 | ], 58 | "lineHeights": [ 59 | { 60 | "name": "Default", 61 | "size": 1.5, 62 | "slug": "default" 63 | } 64 | ] 65 | }, 66 | "appearanceTools": true, 67 | "color": { 68 | "defaultDuotone": false, 69 | "defaultPalette": false, 70 | "defaultGradients": false, 71 | "palette": [ 72 | { 73 | "color": "#164194", 74 | "name": "Primary", 75 | "slug": "primary" 76 | }, 77 | { 78 | "color": "#e6ebf5", 79 | "name": "Blueish", 80 | "slug": "blueish" 81 | }, 82 | { 83 | "color": "#d0d9ea", 84 | "name": "Blueish:hover", 85 | "slug": "blueish-hover" 86 | }, 87 | { 88 | "color": "rgba(0, 0, 0, .5)", 89 | "name": "Gray", 90 | "slug": "gray" 91 | }, 92 | { 93 | "color": "#f7f9fc", 94 | "name": "Gray 100", 95 | "slug": "gray-100" 96 | }, 97 | { 98 | "color": "#9e9e9e", 99 | "name": "Gray 500", 100 | "slug": "gray-500" 101 | }, 102 | { 103 | "color": "#ffffff", 104 | "name": "White", 105 | "slug": "white" 106 | }, 107 | { 108 | "color": "#000000", 109 | "name": "Black", 110 | "slug": "black" 111 | }, 112 | { 113 | "color": "#e45454", 114 | "name": "Red", 115 | "slug": "red" 116 | }, 117 | { 118 | "color": "#87ab3f", 119 | "name": "Green", 120 | "slug": "green" 121 | } 122 | ] 123 | }, 124 | "layout": { 125 | "contentSize": "1140px", 126 | "wideSize": "1280px" 127 | }, 128 | "spacing": { 129 | "spacingScale": { 130 | "steps": 0 131 | }, 132 | "spacingSizes": [ 133 | { 134 | "name": "1", 135 | "size": "1rem", 136 | "slug": "10" 137 | }, 138 | { 139 | "name": "2", 140 | "size": "min(1.5rem, 2vw)", 141 | "slug": "20" 142 | }, 143 | { 144 | "name": "3", 145 | "size": "min(2.5rem, 3vw)", 146 | "slug": "30" 147 | }, 148 | { 149 | "name": "4", 150 | "size": "min(4rem, 5vw)", 151 | "slug": "40" 152 | }, 153 | { 154 | "name": "5", 155 | "size": "min(6.5rem, 8vw)", 156 | "slug": "50" 157 | }, 158 | { 159 | "name": "6", 160 | "size": "min(10.5rem, 13vw)", 161 | "slug": "60" 162 | } 163 | ], 164 | "units": ["%", "px", "em", "rem", "vh", "vw"] 165 | }, 166 | "useRootPaddingAwareAlignments": false 167 | }, 168 | "styles": { 169 | "blocks": { 170 | "core/group": { 171 | "spacing": { 172 | "padding": { 173 | "top": "var(--wp--preset--spacing--40)", 174 | "bottom": "var(--wp--preset--spacing--40)" 175 | }, 176 | "margin": { 177 | "top": "0", 178 | "bottom": "0" 179 | } 180 | } 181 | } 182 | }, 183 | "typography": { 184 | "fontSize": "var(--wp--preset--font-size--mini)" 185 | }, 186 | "elements": { 187 | "h1": { 188 | "typography": { 189 | "fontSize": "var(--wp--preset--font-size--x-large)" 190 | } 191 | }, 192 | "h2": { 193 | "typography": { 194 | "fontSize": "var(--wp--preset--font-size--large)" 195 | } 196 | }, 197 | "h3": { 198 | "typography": { 199 | "fontSize": "var(--wp--preset--font-size--medium)" 200 | } 201 | }, 202 | "h4": { 203 | "typography": { 204 | "fontSize": "var(--wp--preset--font-size--small)" 205 | } 206 | } 207 | } 208 | }, 209 | "templateParts": [ 210 | { 211 | "area": "footer", 212 | "name": "footer", 213 | "title": "Footer" 214 | }, 215 | { 216 | "area": "header", 217 | "name": "header", 218 | "title": "Header" 219 | }, 220 | { 221 | "area": "header", 222 | "name": "top-bar", 223 | "title": "Top bar" 224 | } 225 | ], 226 | "customTemplates": [ 227 | { 228 | "name": "my-custom-template", 229 | "title": "The template title", 230 | "postTypes": [ 231 | "page", 232 | "post", 233 | "my-cpt" 234 | ] 235 | } 236 | ] 237 | } 238 | -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/views/blocks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/themes/juniper-theme/views/blocks/.gitkeep -------------------------------------------------------------------------------- /web/app/themes/juniper-theme/views/blocks/cta.twig: -------------------------------------------------------------------------------- 1 | {# 2 | Title: CTA 3 | Description: 4 | Category: formatting 5 | Icon: admin-comments 6 | Keywords: 7 | Mode: edit 8 | Align: full 9 | SupportsAlign: left right full 10 | SupportsMode: true 11 | SupportsMultiple: true 12 | #} -------------------------------------------------------------------------------- /web/app/uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osomstudio/juniper/0002b3a545cf0ba73ab7f6041224144605e670c4/web/app/uploads/.gitkeep -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 |