This is a quick guide to get you started if you want to contribute to the theme, fork off your own version, or create a child theme. Nothing too comprehensive, but enough to introduce the core concepts and practices.
You can follow the WordPress [coding](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/) and [documentation](https://developer.wordpress.org/coding-standards/inline-documentation-standards/) standards, but they are not imperative here. Since the theme was originally not meant to be publicly released, no thought was spent on collaboration. Which does not mean no standards apply, just not the official ones. Great effort has been put into refactoring and documenting, making everything orderly and clean. Contributors are to uphold this effort and improve what is lacking. Either by following the official handbook or mimicking the existing code.
However, there are a few guidelines:
* Indentation tabs are two spaces wide; not four and especially not eight or more.
* PHP tags for multi-line snippets may share lines to avoid errant spaces in the HTML.
* Braces may be omitted if not necessary, e.g. `if ( condition ) return;` to safe space.
* Arrays may be declared using the short syntax `[]`, except for multi-line definitions.
* CSS may use `rgb()` and `hsl()`, which is even required for some features.
* CSS is compiled from SCSS (Dart Sass) and minified, never edit the processed styles.
* You may `return` from within a case statement, but still add the `break` afterwards.
*This theme was compiled with CodeKit. I’m sick and tired of package managers. There is no dependency hell and I wish to keep it that way. Anything beyond building assets, which would be reasonable, will be rejected. I’m not sorry. If you disagree with that sentiment, you are free to branch or fork and do as you like.*
Fictioneer was originally styled with [BEM](https://getbem.com/) — and still is, although with some modifications. While BEM offers great structure and prevents cascading issues, this comes with a bag of redundancy and heavy markup. You easily end up with extremely long class signatures and lists that break several lines. BEM also does not work well with utility classes which escape the idea of scoped concerns, causing cascading issues again. But adding redundant modifiers for each and every block is not great either.
To alleviate these drawbacks, Fictioneer makes two changes to the default BEM methodology. Modifiers are no longer suffixes but classes that start with an underscore (`._modifier`), always within a block scope. Modifiers are chained to the block parent (`.block._modifier` or `.block__element._modifier`), granting them a higher specificity to override parent styles and single-specificity utility classes. Of course this is no silver bullet either, as it is slightly less efficient.
<liclass="block__other-element _modifier bad-utility good-utility">Italic coral with 1rem margin bottom</li>
</ul>
</section>
```
```scss
.block {
color: black;
&__element {
&._modifier {
color: coral; // Overrides parent block and element styles
}
}
&__element {
color: navy; // Overridden despite being lower (for demonstration, do not actually do this)
}
&__other-element {
&._modifier {
font-style: italic; // Same name, different effect (but better use specific names)
}
}
}
/*
* Utility classes still follow the cascade! They are not modifiers and should not be
* used as such. If possible, only apply properties via utilities that are not innate
* to the element. Notable exceptions include display modes and visibility.
*/
.bad-utility {
font-style: normal; // Tries (and fails) to modify existing target element property
}
.good-utility {
margin-bottom: 1rem; // Target element does not have the property
}
```
### CSS Custom Properties (Variables)
Most of the theme’s "skin" — colors, spacing, borders, shadows, etc. — is controlled by an extensive set of [custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) located in **[_properties.scss](src/scss/common/_properties.scss)** and **[_lightmode.scss](src/scss/common/_lightmode.scss)**. A few of these properties can be overwritten with the Customizer, as noted. This setup makes global changes easy but can cause render issues if handled without care. You can learn more about that in this [article by Lisi Linhart](https://lisilinhart.info/posts/css-variables-performance/).
*There was once a naive developer who thought updating a custom property with the current scroll position would be a great way to add parallax effects with only one line of JavaScript. He was right, except it also set his CPU on fire.*
### Functions
The responsive spacing and customizable colors are generated by functions. You will immediately see why that is necessary. Not using these functions (or replicating their results) can break the theme. Located in **[_functions.scss](src/scss/common/_functions.scss)**.
~~Fictioneer is built on [Vanilla JS](http://vanilla-js.com/) without hard dependencies, *especially* not jQuery which is to be avoided like the plague. Bad enough that WordPress and most plugins insist on the performance hog. If you need something from an external library, just copy the functions and credit the source. Avoid additional overhead. There is also an argument for refactoring the JS into classes and modules. But since everything is already working, this would be a labor of passion without immediate benefit.~~
As of 5.27.0, the theme uses [Stimulus](https://stimulus.hotwired.dev/). This section will be updated in the future.
AJAX requests can be made with the theme’s [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) helper functions, returning an object parsed from a JSON response. For that to work, the server must always respond with a JSON (which can contain an HTML node, of course). You can use [wp_send_json_success()](https://developer.wordpress.org/reference/functions/wp_send_json_success/) and [wp_send_json_error()](https://developer.wordpress.org/reference/functions/wp_send_json_error/) for this, making it easier to evaluate the result client-side.
These are the protected meta fields used specifically for the **fcn_story** post type. Some field values are verbatim due to legacy reasons (the theme previously used the ACF plugin), so they rely on the `fcntr()` translation helper. Falsy values are not stored; instead, the meta field gets deleted from the database to save rows.
| META KEY | TYPE | PURPOSE
| --- | :---: | ---
| fictioneer_story_short_description | string | Short description used on cards.
These are the protected meta fields used specifically for the **fcn_chapter** post type. Some field values are verbatim due to legacy reasons (the theme previously used the ACF plugin), so they rely on the `fcntr()` translation helper. Falsy values are not stored; instead, the meta field gets deleted from the database to save rows.
| META KEY | TYPE | PURPOSE
| --- | :---: | ---
| fictioneer_chapter_story | int | ID of the story the chapter belongs to.
| fictioneer_chapter_list_title | string | Alternative title for lists with little space.
| fictioneer_chapter_group | string | Chapter group name (case-sensitive).
| fictioneer_chapter_foreword | string | Content of the chapter foreword.
| fictioneer_chapter_afterword | string | Content of the chapter afterword.
| fictioneer_chapter_password_note | string | Content of the note shown if the story is password protected.
Fictioneer customizes WordPress by using as many standard action and filter hooks as possible, keeping the theme compatible with plugins adhering to the same principles. However, the theme was not initially built for a public release and despite great efforts to refactor the code, some conflicts are unavoidable. Please make sure to also look at the theme’s custom [action hooks](ACTIONS.md) and [filter hooks](FILTERS.md). Following is a list of (not) all theme actions and filters hooked to WordPress in a not particularly easy to read at fashion. Some of them are conditional.
Fictioneer is cache aware. This means the theme provides an interface to aid cache plugins in purging stale caches across the site, not just the updated post. It can even operate as static website with dynamic content being fetched via AJAX. By default, four cache plugins are considered: [WP Super Cache](https://wordpress.org/plugins/wp-super-cache/), [W3 Total Cache](https://wordpress.org/plugins/w3-total-cache/), [LiteSpeed Cache](https://wordpress.org/plugins/litespeed-cache/), and [WP Rocket](https://wp-rocket.me/). Whenever you publish or update content, a series of actions is triggered to purge anything related.
*"There are only two hard things in Computer Science: cache invalidation and naming things" — Phil Karlton.*
* [Cache Purge All Hook](ACTIONS.md#do_action-fictioneer_cache_purge_all-): `fictioneer_cache_purge_all`
* [Cache Purge Post Hook](ACTIONS.md#do_action-fictioneer_cache_purge_post-post_id-): `fictioneer_cache_purge_post`
### Functions
#### `fictioneer_caching_active(): bool`
Returns true or false depending on whether the site is currently cached. Either because a known cache plugin is active or the **Enable cache compatibility mode** option is checked.
#### `fictioneer_private_caching_active(): bool`
Returns true or false depending on whether *private* caching is active. Private caching creates individual cache files for each user, which can result in high disk usage. Always false if **Enable public cache compatibility mode** is checked.
#### `fictioneer_purge_all_caches()`
Helper to trigger the "purge all" functions of known cache plugins. Can be extended with the `fictioneer_cache_purge_all` hook.
#### `fictioneer_purge_post_cache( $post_id )`
Helper to trigger the "purge post" functions of known cache plugins. Can be extended with the `fictioneer_cache_purge_post` hook.
Purges all caches for posts with the given template (without the `.php`). This is by default done for `stories`, `chapters`, `collections`, and `recommendations`.
Hooked if caching is active and called whenever a post is published, updated, trashed, restored, or deleted. Purges the cache for the given post and any other that has or may have a relationship with it. This includes the front page, the associated story, all associated chapters, relevant list pages, collections, featured lists, and theme shortcodes.
Returns an array with maintained relationships between posts. This "registry" is updated whenever a post is published, updated, trashed, restored, or deleted — with the post in question. If the registry is cleared or becomes corrupted, old relationships cannot be restored unless each post is opened and re-saved.