├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── footer.php ├── functions.php ├── header.php ├── index.php ├── package-lock.json ├── package.json ├── src ├── admin.js ├── components │ ├── Comments.js │ └── Post.js ├── editor.js ├── helpers.js ├── middlewares │ ├── route │ │ ├── fetchComments.js │ │ ├── updateHistory.js │ │ └── updateStore.js │ └── setup │ │ └── setupPopstate.js ├── session-manager.js └── theme.js ├── style.css ├── templates ├── comments │ └── comments.php ├── compatibility-mode │ └── index.php ├── footer │ └── footer.php ├── header │ ├── header.php │ └── noscript.php └── index │ ├── 404.php │ ├── archive-pagination.php │ ├── archive-post.php │ ├── archive.php │ ├── comments.php │ ├── index.php │ ├── no-posts.php │ ├── post.php │ └── singular.php └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | logs 3 | node_modules 4 | build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boilerplate for Nearly Headless WordPress Themes 2 | 3 | This is a plugin boilerplate built using [Underpin](https://github.com/alexstandiford/underpin) 4 | ,[Nicholas](https://github.com/nicholas-wordpress), and [AlpineJS](https://github.com/alpinejs/alpine/). It will allow 5 | you to build 6 | a [Nearly Headless](https://www.wpdev.academy/concepts/headless-wordpress-is-overrated-a-case-for-the-nearly-headless-web-app/) 7 | WordPress theme, that allows you to run a WordPress website like a headless app, but also allows you to render specific 8 | pages using PHP instead of Javascript. 9 | 10 | **Want to learn more about this method? Check out my 11 | course [here](https://www.wpdev.academy/course/build-a-nearly-headless-wordpress-site-using-alpinejs/)** 12 | 13 | ## Setup 14 | 15 | To set this plugin up, follow these steps: 16 | 17 | 1. Install composer packages `composer install` 18 | 2. Install NPM packages `npm i` 19 | 3. Compile NPM scripts `npm run start` or `npm run build` 20 | 21 | ## Templates 22 | 23 | Under the hood, this boilerplate uses Underpin's [Template Loader](https://github.com/Underpin-WP/template-loader/) for 24 | theme templating. Check out that library's readme file for documentation on how to create, and extend templates in this 25 | template. 26 | 27 | ## Scripts 28 | 29 | This boilerplate extends the [Nicholas library](https://github.com/nicholas-wordpress/app) to set up the interfaces 30 | needed to manage compatibility mode settings in the admin. Because of this, there are **3** scripts that get compiled. 31 | Check out that readme for more information on how to extend these scripts and customize them to suit your theme's needs. 32 | 33 | 1. **admin.js** - Used to render the react app that displays the Nicholas settings screen located in **Settings>> 34 | Nicholas Settings** 35 | 2. **editor.js** - Used to add the "compatibility mode" toggle to posts 36 | 3. **theme.js** - The core functionality to handle the nearly headless approach. 37 | 38 | ## Stylesheets 39 | 40 | The default webpack config comes with Webpack's [postCSS loader](https://webpack.js.org/loaders/postcss-loader/), and 41 | can handle SCSS and CSS files. You can override the default loader by adding a postcss file to this theme. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "extra": { 3 | "installer-paths": { 4 | "vendor/{$vendor}/{$name}": [ 5 | "type:wordpress-muplugin" 6 | ] 7 | } 8 | }, 9 | "require": { 10 | "nicholas-wordpress/core": "^1.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "0cb761db61c5bb11cc4969983f0d557e", 8 | "packages": [ 9 | { 10 | "name": "composer/installers", 11 | "version": "v1.11.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/composer/installers.git", 15 | "reference": "ae03311f45dfe194412081526be2e003960df74b" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/composer/installers/zipball/ae03311f45dfe194412081526be2e003960df74b", 20 | "reference": "ae03311f45dfe194412081526be2e003960df74b", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "composer-plugin-api": "^1.0 || ^2.0" 25 | }, 26 | "replace": { 27 | "roundcube/plugin-installer": "*", 28 | "shama/baton": "*" 29 | }, 30 | "require-dev": { 31 | "composer/composer": "1.6.* || ^2.0", 32 | "composer/semver": "^1 || ^3", 33 | "phpstan/phpstan": "^0.12.55", 34 | "phpstan/phpstan-phpunit": "^0.12.16", 35 | "symfony/phpunit-bridge": "^4.2 || ^5", 36 | "symfony/process": "^2.3" 37 | }, 38 | "type": "composer-plugin", 39 | "extra": { 40 | "class": "Composer\\Installers\\Plugin", 41 | "branch-alias": { 42 | "dev-main": "1.x-dev" 43 | } 44 | }, 45 | "autoload": { 46 | "psr-4": { 47 | "Composer\\Installers\\": "src/Composer/Installers" 48 | } 49 | }, 50 | "notification-url": "https://packagist.org/downloads/", 51 | "license": [ 52 | "MIT" 53 | ], 54 | "authors": [ 55 | { 56 | "name": "Kyle Robinson Young", 57 | "email": "kyle@dontkry.com", 58 | "homepage": "https://github.com/shama" 59 | } 60 | ], 61 | "description": "A multi-framework Composer library installer", 62 | "homepage": "https://composer.github.io/installers/", 63 | "keywords": [ 64 | "Craft", 65 | "Dolibarr", 66 | "Eliasis", 67 | "Hurad", 68 | "ImageCMS", 69 | "Kanboard", 70 | "Lan Management System", 71 | "MODX Evo", 72 | "MantisBT", 73 | "Mautic", 74 | "Maya", 75 | "OXID", 76 | "Plentymarkets", 77 | "Porto", 78 | "RadPHP", 79 | "SMF", 80 | "Starbug", 81 | "Thelia", 82 | "Whmcs", 83 | "WolfCMS", 84 | "agl", 85 | "aimeos", 86 | "annotatecms", 87 | "attogram", 88 | "bitrix", 89 | "cakephp", 90 | "chef", 91 | "cockpit", 92 | "codeigniter", 93 | "concrete5", 94 | "croogo", 95 | "dokuwiki", 96 | "drupal", 97 | "eZ Platform", 98 | "elgg", 99 | "expressionengine", 100 | "fuelphp", 101 | "grav", 102 | "installer", 103 | "itop", 104 | "joomla", 105 | "known", 106 | "kohana", 107 | "laravel", 108 | "lavalite", 109 | "lithium", 110 | "magento", 111 | "majima", 112 | "mako", 113 | "mediawiki", 114 | "miaoxing", 115 | "modulework", 116 | "modx", 117 | "moodle", 118 | "osclass", 119 | "phpbb", 120 | "piwik", 121 | "ppi", 122 | "processwire", 123 | "puppet", 124 | "pxcms", 125 | "reindex", 126 | "roundcube", 127 | "shopware", 128 | "silverstripe", 129 | "sydes", 130 | "sylius", 131 | "symfony", 132 | "tastyigniter", 133 | "typo3", 134 | "wordpress", 135 | "yawik", 136 | "zend", 137 | "zikula" 138 | ], 139 | "support": { 140 | "issues": "https://github.com/composer/installers/issues", 141 | "source": "https://github.com/composer/installers/tree/v1.11.0" 142 | }, 143 | "funding": [ 144 | { 145 | "url": "https://packagist.com", 146 | "type": "custom" 147 | }, 148 | { 149 | "url": "https://github.com/composer", 150 | "type": "github" 151 | }, 152 | { 153 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 154 | "type": "tidelift" 155 | } 156 | ], 157 | "time": "2021-04-28T06:42:17+00:00" 158 | }, 159 | { 160 | "name": "nicholas-wordpress/core", 161 | "version": "1.0.2", 162 | "source": { 163 | "type": "git", 164 | "url": "https://github.com/nicholas-wordpress/underpin-module.git", 165 | "reference": "50be4171700e0149b21342f12fd4381e42ea9148" 166 | }, 167 | "dist": { 168 | "type": "zip", 169 | "url": "https://api.github.com/repos/nicholas-wordpress/underpin-module/zipball/50be4171700e0149b21342f12fd4381e42ea9148", 170 | "reference": "50be4171700e0149b21342f12fd4381e42ea9148", 171 | "shasum": "" 172 | }, 173 | "require": { 174 | "underpin/decision-list-loader": "^1.0", 175 | "underpin/meta-loader": "^1.0", 176 | "underpin/option-loader": "^1.0", 177 | "underpin/rest-endpoint-loader": "^1.0", 178 | "underpin/script-loader": "^1.1", 179 | "underpin/template-loader": "^1.0", 180 | "underpin/underpin": "^1.3" 181 | }, 182 | "require-dev": { 183 | "underpin/debug-bar-extension": "^1.0" 184 | }, 185 | "type": "project", 186 | "autoload": { 187 | "files": [ 188 | "nicholas.php" 189 | ] 190 | }, 191 | "notification-url": "https://packagist.org/downloads/", 192 | "license": [ 193 | "MIT" 194 | ], 195 | "authors": [ 196 | { 197 | "name": "Alex Standiford", 198 | "email": "a@alexstandiford.com" 199 | } 200 | ], 201 | "description": "Nearly-headless application support for WordPress themes", 202 | "support": { 203 | "issues": "https://github.com/nicholas-wordpress/underpin-module/issues", 204 | "source": "https://github.com/nicholas-wordpress/underpin-module/tree/1.0.2" 205 | }, 206 | "time": "2021-09-11T21:53:12+00:00" 207 | }, 208 | { 209 | "name": "underpin/cron-job-loader", 210 | "version": "1.0.0", 211 | "source": { 212 | "type": "git", 213 | "url": "https://github.com/Underpin-WP/cron-job-loader.git", 214 | "reference": "2676e465f934e883d8fba7707e3c2b34d7413066" 215 | }, 216 | "dist": { 217 | "type": "zip", 218 | "url": "https://api.github.com/repos/Underpin-WP/cron-job-loader/zipball/2676e465f934e883d8fba7707e3c2b34d7413066", 219 | "reference": "2676e465f934e883d8fba7707e3c2b34d7413066", 220 | "shasum": "" 221 | }, 222 | "type": "library", 223 | "autoload": { 224 | "files": [ 225 | "cron-jobs.php" 226 | ] 227 | }, 228 | "notification-url": "https://packagist.org/downloads/", 229 | "license": [ 230 | "GPL-2.0-or-later" 231 | ], 232 | "authors": [ 233 | { 234 | "name": "Alex Standiford", 235 | "email": "a@alexstandiford.com" 236 | } 237 | ], 238 | "description": "Cron Job Loader for Underpin", 239 | "support": { 240 | "issues": "https://github.com/Underpin-WP/cron-job-loader/issues", 241 | "source": "https://github.com/Underpin-WP/cron-job-loader/tree/1.0.0" 242 | }, 243 | "time": "2021-05-11T18:35:25+00:00" 244 | }, 245 | { 246 | "name": "underpin/decision-list-loader", 247 | "version": "1.0.0", 248 | "source": { 249 | "type": "git", 250 | "url": "https://github.com/Underpin-WP/decision-list-loader.git", 251 | "reference": "1b17e130157e7aba4dad36e0b68f8b0f3aee0323" 252 | }, 253 | "dist": { 254 | "type": "zip", 255 | "url": "https://api.github.com/repos/Underpin-WP/decision-list-loader/zipball/1b17e130157e7aba4dad36e0b68f8b0f3aee0323", 256 | "reference": "1b17e130157e7aba4dad36e0b68f8b0f3aee0323", 257 | "shasum": "" 258 | }, 259 | "type": "library", 260 | "autoload": { 261 | "files": [ 262 | "decision-lists.php" 263 | ] 264 | }, 265 | "notification-url": "https://packagist.org/downloads/", 266 | "license": [ 267 | "GPL-2.0-or-later" 268 | ], 269 | "authors": [ 270 | { 271 | "name": "Alex Standiford", 272 | "email": "a@alexstandiford.com" 273 | } 274 | ], 275 | "description": "Decision List loader for Underpin", 276 | "support": { 277 | "issues": "https://github.com/Underpin-WP/decision-list-loader/issues", 278 | "source": "https://github.com/Underpin-WP/decision-list-loader/tree/1.0.0" 279 | }, 280 | "time": "2021-05-06T00:44:13+00:00" 281 | }, 282 | { 283 | "name": "underpin/logger-loader", 284 | "version": "2.0.0", 285 | "source": { 286 | "type": "git", 287 | "url": "https://github.com/Underpin-WP/logger-loader.git", 288 | "reference": "713ff0ab28ac55562facc897eddc22f66536c119" 289 | }, 290 | "dist": { 291 | "type": "zip", 292 | "url": "https://api.github.com/repos/Underpin-WP/logger-loader/zipball/713ff0ab28ac55562facc897eddc22f66536c119", 293 | "reference": "713ff0ab28ac55562facc897eddc22f66536c119", 294 | "shasum": "" 295 | }, 296 | "require": { 297 | "underpin/cron-job-loader": "^1.0", 298 | "underpin/decision-list-loader": "^1.0" 299 | }, 300 | "type": "library", 301 | "autoload": { 302 | "files": [ 303 | "logger.php" 304 | ] 305 | }, 306 | "notification-url": "https://packagist.org/downloads/", 307 | "license": [ 308 | "GPL-2.0-or-later" 309 | ], 310 | "authors": [ 311 | { 312 | "name": "Alex Standiford", 313 | "email": "a@alexstandiford.com" 314 | } 315 | ], 316 | "description": "Logger Loader for Underpin", 317 | "support": { 318 | "issues": "https://github.com/Underpin-WP/logger-loader/issues", 319 | "source": "https://github.com/Underpin-WP/logger-loader/tree/2.0.0" 320 | }, 321 | "time": "2021-08-05T13:06:26+00:00" 322 | }, 323 | { 324 | "name": "underpin/meta-loader", 325 | "version": "1.0.0", 326 | "source": { 327 | "type": "git", 328 | "url": "https://github.com/Underpin-WP/meta-loader.git", 329 | "reference": "2222ff25decf594ef912ac1d0790d1ba67813061" 330 | }, 331 | "dist": { 332 | "type": "zip", 333 | "url": "https://api.github.com/repos/Underpin-WP/meta-loader/zipball/2222ff25decf594ef912ac1d0790d1ba67813061", 334 | "reference": "2222ff25decf594ef912ac1d0790d1ba67813061", 335 | "shasum": "" 336 | }, 337 | "type": "library", 338 | "autoload": { 339 | "files": [ 340 | "meta.php" 341 | ] 342 | }, 343 | "notification-url": "https://packagist.org/downloads/", 344 | "license": [ 345 | "GPL-2.0-or-later" 346 | ], 347 | "authors": [ 348 | { 349 | "name": "Alex Standiford", 350 | "email": "a@alexstandiford.com" 351 | } 352 | ], 353 | "description": "Meta loader for Underpin", 354 | "support": { 355 | "issues": "https://github.com/Underpin-WP/meta-loader/issues", 356 | "source": "https://github.com/Underpin-WP/meta-loader/tree/1.0.0" 357 | }, 358 | "time": "2021-05-09T14:00:43+00:00" 359 | }, 360 | { 361 | "name": "underpin/option-loader", 362 | "version": "1.0.1", 363 | "source": { 364 | "type": "git", 365 | "url": "https://github.com/Underpin-WP/option-loader.git", 366 | "reference": "b9e66e11264162827ff7dce3c0a8992932497304" 367 | }, 368 | "dist": { 369 | "type": "zip", 370 | "url": "https://api.github.com/repos/Underpin-WP/option-loader/zipball/b9e66e11264162827ff7dce3c0a8992932497304", 371 | "reference": "b9e66e11264162827ff7dce3c0a8992932497304", 372 | "shasum": "" 373 | }, 374 | "type": "library", 375 | "autoload": { 376 | "files": [ 377 | "options.php" 378 | ] 379 | }, 380 | "notification-url": "https://packagist.org/downloads/", 381 | "license": [ 382 | "GPL-2.0-or-later" 383 | ], 384 | "authors": [ 385 | { 386 | "name": "Alex Standiford", 387 | "email": "a@alexstandiford.com" 388 | } 389 | ], 390 | "description": "Option loader for Underpin", 391 | "support": { 392 | "issues": "https://github.com/Underpin-WP/option-loader/issues", 393 | "source": "https://github.com/Underpin-WP/option-loader/tree/1.0.1" 394 | }, 395 | "time": "2021-08-25T23:24:29+00:00" 396 | }, 397 | { 398 | "name": "underpin/rest-endpoint-loader", 399 | "version": "1.0.0", 400 | "source": { 401 | "type": "git", 402 | "url": "https://github.com/Underpin-WP/rest-endpoint-loader.git", 403 | "reference": "01fb7e7b2260e5b5a9918d7120c78f614ce43b38" 404 | }, 405 | "dist": { 406 | "type": "zip", 407 | "url": "https://api.github.com/repos/Underpin-WP/rest-endpoint-loader/zipball/01fb7e7b2260e5b5a9918d7120c78f614ce43b38", 408 | "reference": "01fb7e7b2260e5b5a9918d7120c78f614ce43b38", 409 | "shasum": "" 410 | }, 411 | "type": "library", 412 | "autoload": { 413 | "files": [ 414 | "rest-endpoints.php" 415 | ] 416 | }, 417 | "notification-url": "https://packagist.org/downloads/", 418 | "license": [ 419 | "GPL-2.0-or-later" 420 | ], 421 | "authors": [ 422 | { 423 | "name": "Alex Standiford", 424 | "email": "a@alexstandiford.com" 425 | } 426 | ], 427 | "description": "Rest endpoint loader for Underpin", 428 | "support": { 429 | "issues": "https://github.com/Underpin-WP/rest-endpoint-loader/issues", 430 | "source": "https://github.com/Underpin-WP/rest-endpoint-loader/tree/1.0.0" 431 | }, 432 | "time": "2021-05-06T00:45:07+00:00" 433 | }, 434 | { 435 | "name": "underpin/script-loader", 436 | "version": "1.1.4", 437 | "source": { 438 | "type": "git", 439 | "url": "https://github.com/Underpin-WP/script-loader.git", 440 | "reference": "396615d03ae4047d482c7678f671d7ea0fcfe177" 441 | }, 442 | "dist": { 443 | "type": "zip", 444 | "url": "https://api.github.com/repos/Underpin-WP/script-loader/zipball/396615d03ae4047d482c7678f671d7ea0fcfe177", 445 | "reference": "396615d03ae4047d482c7678f671d7ea0fcfe177", 446 | "shasum": "" 447 | }, 448 | "type": "library", 449 | "autoload": { 450 | "files": [ 451 | "scripts.php" 452 | ] 453 | }, 454 | "notification-url": "https://packagist.org/downloads/", 455 | "license": [ 456 | "GPL-2.0-or-later" 457 | ], 458 | "authors": [ 459 | { 460 | "name": "Alex Standiford", 461 | "email": "a@alexstandiford.com" 462 | } 463 | ], 464 | "description": "Script Loader for Underpin", 465 | "support": { 466 | "issues": "https://github.com/Underpin-WP/script-loader/issues", 467 | "source": "https://github.com/Underpin-WP/script-loader/tree/1.1.4" 468 | }, 469 | "time": "2021-09-11T21:09:02+00:00" 470 | }, 471 | { 472 | "name": "underpin/template-loader", 473 | "version": "1.0.0", 474 | "source": { 475 | "type": "git", 476 | "url": "https://github.com/Underpin-WP/template-loader.git", 477 | "reference": "76df5f4b0139257ffe944603760f9b5493bf82bd" 478 | }, 479 | "dist": { 480 | "type": "zip", 481 | "url": "https://api.github.com/repos/Underpin-WP/template-loader/zipball/76df5f4b0139257ffe944603760f9b5493bf82bd", 482 | "reference": "76df5f4b0139257ffe944603760f9b5493bf82bd", 483 | "shasum": "" 484 | }, 485 | "type": "library", 486 | "autoload": { 487 | "files": [ 488 | "templates.php" 489 | ] 490 | }, 491 | "notification-url": "https://packagist.org/downloads/", 492 | "license": [ 493 | "GPL-2.0-or-later" 494 | ], 495 | "authors": [ 496 | { 497 | "name": "Alex Standiford", 498 | "email": "a@alexstandiford.com" 499 | } 500 | ], 501 | "description": "Template loader for Underpin. Intended to be used by WordPress themes.", 502 | "support": { 503 | "issues": "https://github.com/Underpin-WP/template-loader/issues", 504 | "source": "https://github.com/Underpin-WP/template-loader/tree/1.0.0" 505 | }, 506 | "time": "2021-08-14T18:36:46+00:00" 507 | }, 508 | { 509 | "name": "underpin/underpin", 510 | "version": "1.3.1", 511 | "source": { 512 | "type": "git", 513 | "url": "https://github.com/Underpin-WP/underpin.git", 514 | "reference": "926cb0a236ce630766318794a42f4de9568724f9" 515 | }, 516 | "dist": { 517 | "type": "zip", 518 | "url": "https://api.github.com/repos/Underpin-WP/underpin/zipball/926cb0a236ce630766318794a42f4de9568724f9", 519 | "reference": "926cb0a236ce630766318794a42f4de9568724f9", 520 | "shasum": "" 521 | }, 522 | "require": { 523 | "composer/installers": "^1.10", 524 | "underpin/logger-loader": "^2.0" 525 | }, 526 | "type": "wordpress-muplugin", 527 | "autoload": { 528 | "files": [ 529 | "Underpin.php" 530 | ] 531 | }, 532 | "notification-url": "https://packagist.org/downloads/", 533 | "license": [ 534 | "MIT" 535 | ], 536 | "authors": [ 537 | { 538 | "name": "Alex Standiford", 539 | "email": "a@alexstandiford.com" 540 | } 541 | ], 542 | "description": "Underpin WordPress framework", 543 | "support": { 544 | "issues": "https://github.com/Underpin-WP/underpin/issues", 545 | "source": "https://github.com/Underpin-WP/underpin/tree/1.3.1" 546 | }, 547 | "time": "2021-08-05T13:06:29+00:00" 548 | } 549 | ], 550 | "packages-dev": [], 551 | "aliases": [], 552 | "minimum-stability": "stable", 553 | "stability-flags": [], 554 | "prefer-stable": false, 555 | "prefer-lowest": false, 556 | "platform": [], 557 | "platform-dev": [], 558 | "plugin-api-version": "2.0.0" 559 | } 560 | -------------------------------------------------------------------------------- /footer.php: -------------------------------------------------------------------------------- 1 | 10 | templates()->get_template( 'footer', 'footer' ) ?> -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | scripts()->get( 'theme' )->enqueue(); 25 | } ); 26 | 27 | // Add compatibility mode URLs 28 | add_filter( 'nicholas/compatibility_mode_urls', function ( $urls ) { 29 | 30 | // Filter Twitter Embeds 31 | $filtered_urls = Nicholas::get_urls_for_query( [ 32 | 'post_type' => 'any', 33 | 's' => '"providerNameSlug":"twitter"', // Twitter embeds 34 | ] ); 35 | 36 | return array_merge( $urls, $filtered_urls ); 37 | } ); 38 | 39 | /** 40 | * Templates. 41 | * 42 | * Underpin comes with a powerful template loading system, and this boilerplate expands on that with a template loader. 43 | * Below, you will see a few basic pre-set loaders built inline. 44 | * 45 | * These can be built inline as shown below, but they can also be added as a class that extends 46 | * Theme/Abstracts/Template. In circumstances where your template needs to prefetch a lot of data before render, it's 47 | * probably better to create a class, add your data, and then pass that data to the template. 48 | * 49 | * For more information, check out Underpin loader documentation here: https://github.com/underpin-WP/underpin#loaders 50 | * You may also want to check out the Theme\Loaders\Templates loader to see how to register your own templates. 51 | * 52 | * Additionally, check out more information on how the template system works here: 53 | * https://github.com/underpin-WP/underpin#template-system-trait 54 | */ 55 | 56 | $template_path = trailingslashit( get_template_directory() ) . 'templates'; 57 | 58 | /** 59 | * Index Templates 60 | * Files located in /templates/index/ 61 | */ 62 | nicholas()->templates()->add( 'index', [ 63 | 'description' => "Renders the home page.", 64 | 'name' => "Index Template.", 65 | 'group' => 'index', 66 | 'root_path' => $template_path, 67 | 'templates' => [ 68 | 'index' => [ 'override_visibility' => 'public' ], 69 | 'archive' => [ 'override_visibility' => 'public' ], 70 | 'comments' => [ 'override_visibility' => 'public' ], 71 | 'singular' => [ 'override_visibility' => 'public' ], 72 | 'archive-post' => [ 'override_visibility' => 'public' ], 73 | 'archive-pagination' => [ 'override_visibility' => 'public' ], 74 | 'post' => [ 'override_visibility' => 'public' ], 75 | '404' => [ 'override_visibility' => 'public' ], 76 | 'no-posts' => [ 'override_visibility' => 'public' ], 77 | ], 78 | ] ); 79 | 80 | /** 81 | * Header Template 82 | * Files located in /templates/header/ 83 | */ 84 | nicholas()->templates()->add( 'header', [ 85 | 'description' => "Renders the header.", 86 | 'name' => "Header Template.", 87 | 'root_path' => $template_path, 88 | 'group' => 'header', 89 | 'templates' => [ 90 | 'header' => [ 'override_visibility' => 'public' ], 91 | 'noscript' => [ 'override_visibility' => 'public' ], 92 | ], 93 | ] ); 94 | 95 | /** 96 | * Footer Template 97 | * Files located in /templates/footer/ 98 | */ 99 | nicholas()->templates()->add( 'footer', [ 100 | 'description' => "Renders the home page.", 101 | 'name' => "Index Template.", 102 | 'root_path' => $template_path, 103 | 'group' => 'footer', 104 | 'templates' => [ 105 | 'footer' => [ 'override_visibility' => 'public' ], 106 | ], 107 | ] ); 108 | 109 | /** 110 | * Comments Template 111 | * Files located in /templates/comments/ 112 | */ 113 | nicholas()->templates()->add( 'comments', [ 114 | 'description' => "Renders comments.", 115 | 'name' => "Comments Template.", 116 | 'root_path' => $template_path, 117 | 'group' => 'comments', 118 | 'templates' => [ 119 | 'comments' => [ 'override_visibility' => 'public' ], 120 | ], 121 | ] ); 122 | 123 | /** 124 | * Compatibility Mode Template 125 | * Files located in /templates/compatibility-mode/ 126 | */ 127 | nicholas()->templates()->add( 'compatibility-mode', [ 128 | 'description' => "Renders the page in compatibility mode.", 129 | 'name' => "Compatibility Mode Index Template.", 130 | 'root_path' => $template_path, 131 | 'group' => 'compatibility-mode', 132 | 'templates' => [ 133 | 'index' => [ 'override_visibility' => 'public' ], 134 | ], 135 | ] ); -------------------------------------------------------------------------------- /header.php: -------------------------------------------------------------------------------- 1 | 10 | templates()->get_template( 'header', 'header' ) ?> -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 10 | templates()->get_template( 'index', 'index' ); 13 | get_footer(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nearly-headless-theme", 3 | "version": "1.0.0", 4 | "description": "This is a plugin boilerplate built on the [Underpin](https://github.com/alexstandiford/underpin) Framework. For information on how to use this, check out Underpin's docs.", 5 | "main": "webpack.config.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "build": "wp-scripts build", 11 | "format:js": "wp-scripts format-js", 12 | "lint:css": "wp-scripts lint-style", 13 | "lint:js": "wp-scripts lint-js", 14 | "start": "wp-scripts start" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "alpinejs": "^3.2.3", 20 | "nicholas-router": "^1.0", 21 | "nicholas-wp": "^1.0" 22 | }, 23 | "devDependencies": { 24 | "@wordpress/scripts": "*" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/admin.js: -------------------------------------------------------------------------------- 1 | import { render } from '@wordpress/element' 2 | import fetch from 'nicholas-wp' 3 | import {Admin} from 'nicholas-wp/admin' 4 | 5 | // Render the app 6 | window.onload = () => render( , document.getElementById( 'app' ) ) 7 | 8 | // Export fetch, so we can add midleware via PHP 9 | export { fetch } -------------------------------------------------------------------------------- /src/components/Comments.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return { 3 | 4 | isLoading: false, 5 | 6 | init() { 7 | //get comments 8 | new Promise( async ( res, rej ) => { 9 | // bail if comments are not open. 10 | if ( false === Alpine.store( 'commentsOpen' ) ) { 11 | res(); 12 | } 13 | 14 | this.isLoading = true 15 | 16 | // fetch comments 17 | const { output } = await theme.fetch( { path: `nicholas/v1/comment-output?path=${window.location.pathname}` } ) 18 | 19 | Alpine.store( 'comments', output ) 20 | 21 | this.isLoading = false 22 | 23 | res() 24 | } ) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/components/Post.js: -------------------------------------------------------------------------------- 1 | export default function ( iterator = 0 ) { 2 | 3 | return { 4 | field( field ) { 5 | const post = Alpine.store( 'posts' )[iterator] 6 | 7 | if ( undefined === post ) { 8 | return '' 9 | } 10 | 11 | if ( undefined === post[field] ) { 12 | return '' 13 | } 14 | 15 | return post[field] 16 | }, 17 | 18 | get title() { 19 | const field = this.field( 'title' ) 20 | 21 | return undefined === field.rendered ? '' : field.rendered 22 | }, 23 | 24 | get content() { 25 | const field = this.field( 'content' ) 26 | 27 | return undefined === field.rendered ? '' : field.rendered 28 | }, 29 | 30 | get excerpt() { 31 | const field = this.field( 'excerpt' ) 32 | 33 | return undefined === field.rendered ? '' : field.rendered 34 | }, 35 | 36 | get link(){ 37 | return this.field('link') 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/editor.js: -------------------------------------------------------------------------------- 1 | import { registerPlugin } from '@wordpress/plugins'; 2 | import CompatibilityModeToggle from 'nicholas-wp/editor/CompatibilityModeToggle' 3 | 4 | registerPlugin( 'theme', { render: () => } ); -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | import Alpine from "alpinejs"; 2 | 3 | function setStore( pageData ) { 4 | const defaults = { 5 | posts: [], 6 | body_class: [], 7 | type: '', 8 | pagination: '', 9 | comments_open: false 10 | } 11 | 12 | // Set default values. This ensures pageData has all of the necessary properties. 13 | pageData = { ...defaults, ...pageData } 14 | 15 | Alpine.store( 'posts', pageData.posts ) 16 | Alpine.store( 'type', pageData.type ) 17 | Alpine.store( 'pagination', pageData.pagination ) 18 | Alpine.store( 'bodyClass', pageData.body_class ) 19 | Alpine.store( 'commentsOpen', pageData.comments_open ) 20 | } 21 | 22 | function setLoadingState( to ) { 23 | Alpine.store( 'isLoading', to === true ) 24 | } 25 | 26 | async function setCompatibilityModeUrls() { 27 | const compatibilityModeUrls = await theme.fetch( { path: 'nicholas/v1/compatibility-mode-urls' } ) 28 | 29 | Alpine.store( 'compatibilityModeUrls', compatibilityModeUrls ); 30 | } 31 | 32 | function setHistory( url ){ 33 | window.history.pushState( { 34 | comments: Alpine.store( 'comments' ) 35 | }, document.title, url ) 36 | } 37 | 38 | export { setStore, setLoadingState, setCompatibilityModeUrls, setHistory } -------------------------------------------------------------------------------- /src/middlewares/route/fetchComments.js: -------------------------------------------------------------------------------- 1 | export default ( args, next ) => { 2 | // Reset comment store 3 | Alpine.store( 'comments', '' ) 4 | 5 | // Skip this if comments are not enabled on this post 6 | if ( false === Alpine.store( 'commentsOpen' ) ) { 7 | next() 8 | return 9 | } 10 | 11 | // Wrap this in a promise - no need to wait for it. 12 | new Promise( async ( res, rej ) => { 13 | 14 | const { output } = await theme.fetch( { path: `nicholas/v1/comment-output?path=${args.url.pathname}` } ) 15 | 16 | // Set store to comment output 17 | Alpine.store( 'comments', output ) 18 | } ) 19 | 20 | next() 21 | } -------------------------------------------------------------------------------- /src/middlewares/route/updateHistory.js: -------------------------------------------------------------------------------- 1 | import { setHistory } from '../../helpers' 2 | 3 | export default ( args, next ) => { 4 | setHistory( args.url.href ) 5 | next() 6 | } -------------------------------------------------------------------------------- /src/middlewares/route/updateStore.js: -------------------------------------------------------------------------------- 1 | import Alpine from "alpinejs"; 2 | import { setStore, setLoadingState } from '../../helpers' 3 | 4 | export default async ( args, next ) => { 5 | // We know the cache exists because it is primed in the previous step 6 | const cache = args.url.getCache() 7 | 8 | // Reset the scroll position 9 | window.scroll( { top: 0, behavior: 'smooth' } ) 10 | 11 | // Update the Alpine store using the cached data 12 | setStore( cache ) 13 | 14 | // Done loading! 15 | setLoadingState( false ) 16 | 17 | // Move on to the next action. 18 | next() 19 | } -------------------------------------------------------------------------------- /src/middlewares/setup/setupPopstate.js: -------------------------------------------------------------------------------- 1 | import { Url } from "nicholas-router"; 2 | import Alpine from "alpinejs"; 3 | import { setStore } from '../../helpers' 4 | 5 | export default ( args, next ) => { 6 | window.onpopstate = function ( e ) { 7 | const url = new Url( e.currentTarget.window.location.href ) 8 | const cache = url.getCache() 9 | if ( cache ) { 10 | e.preventDefault() 11 | setStore( cache ) 12 | 13 | if ( cache.comments_open ) { 14 | // If the comments are in the cache 15 | if ( e.data && e.data.comments ) { 16 | Alpine.store( 'comments', e.data.comments ) 17 | 18 | // Sometimes, history gets saved before comments are added. In this case, go fetch the data. 19 | } else { 20 | new Promise( async ( res, rej ) => { 21 | const { output } = await theme.fetch( { path: `nicholas/v1/comment-output?path=${url.pathname}` } ) 22 | 23 | // Set store to comment output 24 | Alpine.store( 'comments', output ) 25 | } ) 26 | } 27 | } 28 | } 29 | } 30 | 31 | next() 32 | } -------------------------------------------------------------------------------- /src/session-manager.js: -------------------------------------------------------------------------------- 1 | import { maybeFlushSessionCache } from "nicholas-router"; 2 | 3 | maybeFlushSessionCache() -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | import Alpine from 'alpinejs' 2 | import Post from './components/Post' 3 | import Comments from './components/Comments' 4 | import fetchComments from './middlewares/route/fetchComments' 5 | import updateStore from './middlewares/route/updateStore' 6 | import updateHistory from './middlewares/route/updateHistory' 7 | import setupPopstate from './middlewares/setup/setupPopstate' 8 | import { setStore, setLoadingState } from './helpers' 9 | import { 10 | addRouteActions, 11 | clearSessionCacheMiddleware, 12 | handleClickMiddleware, 13 | setupRouter, 14 | Url, 15 | validateMiddleware 16 | } from "nicholas-router"; 17 | 18 | import fetch from 'nicholas-wp' 19 | 20 | import { 21 | updateAdminBar, 22 | validateAdminPage, 23 | validateCompatibilityMode, 24 | primeCache, 25 | setPreloadWorker, 26 | validateCacheWorker 27 | } from 'nicholas-wp/middlewares' 28 | 29 | // Delay startup of this script until after the page is loaded. 30 | window.onload = function () { 31 | window.Alpine = Alpine 32 | 33 | // When Alpine is initialized, do these actions. 34 | document.addEventListener( 'alpine:init', async () => { 35 | 36 | // First, set up the initial state in our global store. 37 | // By passing an empty object here, we basically force it to reset. 38 | setStore( {} ) 39 | setLoadingState( true ) 40 | Alpine.store( 'comments', '' ) 41 | 42 | 43 | // Now fetch data. Fetch returns a promise, so we use 'await' to tell JS to wait it to resolve before moving on. 44 | const pageData = await theme.fetch( { path: theme_vars.preloaded_endpoint } ) 45 | 46 | // Setup the Alpine store 47 | setStore( pageData[0] ) 48 | setLoadingState( false ) 49 | 50 | // Store data in the cache 51 | new Url( window.location.href ).updateCache( pageData[0] ) 52 | } ) 53 | 54 | // Setup route middleware actions 55 | addRouteActions( 56 | // First, validate the URL 57 | validateMiddleware, 58 | // Validate this page is not an admin page 59 | validateAdminPage, 60 | // Validate this page doesn't require compatibility mode 61 | validateCompatibilityMode, 62 | // Then, we prime the cache for this URL 63 | //TODO: CREATE SETLOADING STATE MIDDLEWARE 64 | primeCache, 65 | // Then, we Update the Alpine store 66 | updateStore, 67 | // Maybe fetch comments, if enabled 68 | fetchComments, 69 | // Update the history 70 | updateHistory, 71 | // Maybe update the admin bar 72 | updateAdminBar 73 | ) 74 | 75 | // Fire up Nicholas router 76 | setupRouter( 77 | // Maybe clear the session cache. 78 | clearSessionCacheMiddleware, 79 | // Setup event listener for clicks 80 | handleClickMiddleware, 81 | // Setup pop state (history) handler 82 | setupPopstate, 83 | // Check the cache to see if it needs flushed every 5 minutes 84 | validateCacheWorker, 85 | // Continue to scan the page for URLs to preload, and store in the cache 86 | setPreloadWorker 87 | ) 88 | 89 | // Fire up AlpineJS 90 | Alpine.start() 91 | } 92 | 93 | // Export fetch so we can add middlware. 94 | export { fetch, Post, Comments } -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Theme Name: Nearly Headless Theme 3 | Author: A really cool developer 4 | Author URI: https://www.wordpress.org 5 | Description: A nearly headless WordPress theme, built on the Underpin framework 6 | Version: 1.0.0 7 | Requires at least: 5.8 8 | Tested up to: 5.8 9 | Requires PHP: 7.0 10 | Text Domain: theme 11 | */ -------------------------------------------------------------------------------- /templates/comments/comments.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) || ! comments_open() ) { 13 | return; 14 | } 15 | comments_template(); -------------------------------------------------------------------------------- /templates/compatibility-mode/index.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 14 | return; 15 | } 16 | ?> 17 |
18 | templates()->get_template( 'index', 'post', [ 24 | 'content' => Nicholas::get_buffer( 'the_content' ), 25 | 'title' => Nicholas::get_buffer( 'the_title' ), 26 | ] ); 27 | 28 | echo nicholas()->templates()->get_template( 'index', 'comments' ); 29 | } else { 30 | echo nicholas()->templates()->get_template( 'index', 'archive-post', [ 31 | 'excerpt' => Nicholas::get_buffer( 'the_excerpt' ), 32 | 'title' => Nicholas::get_buffer( 'the_title' ), 33 | 'link' => get_post_permalink(), 34 | ] ); 35 | } 36 | } 37 | if ( ! is_singular() ) { 38 | echo nicholas()->templates()->get_template( 'index', 'archive-pagination', [ 39 | 'pagination' => Nicholas::get_buffer( 'the_posts_pagination' ), 40 | ] ); 41 | } 42 | } 43 | ?> 44 |
45 | -------------------------------------------------------------------------------- /templates/footer/footer.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | ?> 15 | 16 | 17 | 18 |
19 | Love What You Do. 20 |
21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/header/header.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | 15 | ?> 16 | 17 | x-data> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | get_template( 'noscript' ) ?> 27 | 28 |
-------------------------------------------------------------------------------- /templates/header/noscript.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) || Nicholas::use_compatibility_mode() ) { 14 | return; 15 | } 16 | 17 | $url = add_query_arg( 'compatibility-mode', '1', get_home_url() . wp_parse_url( $_SERVER['REQUEST_URI'] )['path'] ); 18 | ?> 19 | 44 | -------------------------------------------------------------------------------- /templates/index/404.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | 15 | ?> 16 | 17 |

404 - Page Not Found

18 |

The page you are trying to visit could not be found.

-------------------------------------------------------------------------------- /templates/index/archive-pagination.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | $pagination = $template->get_param( 'pagination', '' ); 15 | ?> 16 |
-------------------------------------------------------------------------------- /templates/index/archive-post.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | 15 | $title = $template->get_param( 'title', '' ); 16 | $excerpt = $template->get_param( 'excerpt', '' ); 17 | $url = $template->get_param( 'link', '' ); 18 | ?> 19 |
20 |

21 |
22 |
-------------------------------------------------------------------------------- /templates/index/archive.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | ?> 15 | 18 | templates()->get_template( 'index', 'archive-pagination' ); ?> -------------------------------------------------------------------------------- /templates/index/comments.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 13 | return; 14 | } 15 | ?> 16 | 28 | -------------------------------------------------------------------------------- /templates/index/index.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 13 | return; 14 | } 15 | 16 | if ( true === Nicholas::use_compatibility_mode() ): ?> 17 | templates()->get_template( 'compatibility-mode', 'index' ) ?> 18 | 19 | 24 | 25 | 33 | 34 | 39 | -------------------------------------------------------------------------------- /templates/index/no-posts.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | ?> 15 |
16 | __('Sorry, there is no content to show.') ?> 17 |
-------------------------------------------------------------------------------- /templates/index/post.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | 15 | $title = $template->get_param( 'title', '' ); 16 | $content = $template->get_param( 'content', '' ); 17 | ?> 18 |
19 |

20 |
21 |
-------------------------------------------------------------------------------- /templates/index/singular.php: -------------------------------------------------------------------------------- 1 | templates()->is_valid_template( $template ) ) { 12 | return; 13 | } 14 | ?> 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress Dependencies 3 | */ 4 | const defaultConfig = require( '@wordpress/scripts/config/webpack.config.js' ); 5 | 6 | // Force Webpack to compile nicholas in this script. 7 | defaultConfig.module.rules = defaultConfig.module.rules.map( ( rule ) => { 8 | 9 | // If the webpack configuration excludes node modules, change the exclusion to compile Nicholas. 10 | if ( rule.exclude && rule.exclude.toString() === /node_modules/.toString() ) { 11 | rule.exclude = /node_modules\/!nicholas$/ 12 | } 13 | 14 | return rule 15 | } ) 16 | 17 | // Now, take the resulting export and combine it with last-minute overrides. 18 | module.exports = { 19 | ...defaultConfig, 20 | ...{ 21 | /** 22 | * Add your entry points for CSS and JS here. 23 | */ 24 | entry: { 25 | theme: './src/theme.js', 26 | editor: './src/editor.js', 27 | sessionManager: './src/session-manager.js', 28 | admin: './src/admin.js' 29 | }, 30 | 31 | output: { 32 | ...defaultConfig.output, 33 | libraryTarget: 'var', 34 | library: '[name]' 35 | }, 36 | } 37 | } --------------------------------------------------------------------------------