diff --git a/.github/workflows/test-php.yml b/.github/workflows/test-php.yml index a95f7d2e..913a00fc 100644 --- a/.github/workflows/test-php.yml +++ b/.github/workflows/test-php.yml @@ -13,7 +13,7 @@ jobs: - name: Setup PHP version uses: shivammathur/setup-php@v2 with: - php-version: "7.2" + php-version: "7.4" extensions: simplexml - name: Checkout source code uses: actions/checkout@v4 @@ -36,7 +36,7 @@ jobs: - name: Setup PHP version uses: shivammathur/setup-php@v2 with: - php-version: "7.2" + php-version: "7.4" extensions: simplexml, mysql tools: phpunit-polyfills - name: Checkout source code diff --git a/assets/js/wizard-promo.js b/assets/js/wizard-promo.js new file mode 100644 index 00000000..b061eada --- /dev/null +++ b/assets/js/wizard-promo.js @@ -0,0 +1,320 @@ +/** + * Raft Pro upsell injection for the Otter onboarding wizard. + * + * Two surfaces: + * - Sidebar nudge: appended to `.o-sidebar__content` on every step. + * - Finish card: appended to `.o-finish__container` (before its actions) + * when the wizard reaches its done state. + * + * We don't import from Otter — we treat its rendered DOM + Redux store as + * the only interfaces, so changes to Otter's internals can't break us + * unless they rename the BEM classes we target. The script is loaded only + * when `?onboarding=true` is present AND Raft Pro is inactive, so paying + * users never see either nudge. + * + * Re-injection is necessary because React owns the wizard DOM and can + * remount sections on state changes — using marker IDs to avoid duplicates. + * + * @package + */ +( function () { + 'use strict'; + + if ( + 'undefined' === typeof window.wp || + ! window.wp.data || + ! window.raftWizardPromo + ) { + return; + } + + const data = window.wp.data; + const domReady = window.wp.domReady; + const config = window.raftWizardPromo; + const strings = config.strings || {}; + const upgradeUrl = config.upgradeUrl; + + const SIDEBAR_ID = 'raft-pro-wizard-sidebar-nudge'; + const FINISH_ID = 'raft-pro-wizard-finish-card'; + + /** + * Inject inline CSS for the two surfaces. Scoped via the marker IDs so + * we don't leak styles into Otter's own components. + */ + function injectStyles() { + if ( document.getElementById( 'raft-pro-wizard-promo-style' ) ) { + return; + } + const style = document.createElement( 'style' ); + style.id = 'raft-pro-wizard-promo-style'; + style.textContent = + '' + + '#' + + SIDEBAR_ID + + ' { margin-top: 24px; padding: 16px; border: 1px dashed #C26148; border-radius: 8px; background: rgba(194, 97, 72, 0.06); }' + + '#' + + SIDEBAR_ID + + ' .raft-eyebrow { font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; color: #C26148; margin: 0 0 4px; }' + + '#' + + SIDEBAR_ID + + ' .raft-title { font-size: 15px; font-weight: 600; margin: 0 0 8px; color: #1e1e1e; }' + + '#' + + SIDEBAR_ID + + ' a { color: #C26148; font-weight: 600; text-decoration: none; font-size: 13px; }' + + '#' + + SIDEBAR_ID + + ' a:hover { text-decoration: underline; }' + + '#' + + FINISH_ID + + ' { margin: 24px 0; padding: 28px; border: 2px dashed #C26148; border-radius: 12px; background: rgba(194, 97, 72, 0.05); text-align: left; }' + + '#' + + FINISH_ID + + ' .raft-eyebrow { font-size: 12px; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; color: #C26148; margin: 0 0 8px; }' + + '#' + + FINISH_ID + + ' h3 { font-size: 22px; font-weight: 600; margin: 0 0 12px; color: #1e1e1e; }' + + '#' + + FINISH_ID + + ' p { font-size: 15px; line-height: 1.5; margin: 0 0 20px; color: #50575e; }' + + '#' + + FINISH_ID + + ' a.raft-cta { display: inline-block; background: #C26148; color: #fff; padding: 10px 22px; border-radius: 999px; text-decoration: none; font-weight: 600; }' + + '#' + + FINISH_ID + + ' a.raft-cta:hover { background: #AC5039; color: #fff; }'; + document.head.appendChild( style ); + } + + /** + * Build a DOM node with the given tag, attributes, and child content. + * Tiny helper to avoid the verbosity of native createElement chains. + * + * @param {string} tag + * @param {Object} attrs + * @param {string|Node|Array} children + * @return {HTMLElement} HTML element + */ + function el( tag, attrs, children ) { + const node = document.createElement( tag ); + if ( attrs ) { + Object.keys( attrs ).forEach( function ( key ) { + if ( 'className' === key ) { + node.className = attrs[ key ]; + } else { + node.setAttribute( key, attrs[ key ] ); + } + } ); + } + if ( children ) { + if ( ! Array.isArray( children ) ) { + children = [ children ]; + } + children.forEach( function ( child ) { + if ( 'string' === typeof child ) { + node.appendChild( document.createTextNode( child ) ); + } else if ( child ) { + node.appendChild( child ); + } + } ); + } + return node; + } + + /** + * Build the sidebar nudge element. Separated from `injectSidebarNudge` + * so we can build-then-position without rebuilding on every re-render. + * + * @return {HTMLElement} HTML structure for the sidebar nudge + */ + function buildSidebarNudge() { + return el( 'div', { id: SIDEBAR_ID }, [ + el( + 'p', + { className: 'raft-eyebrow' }, + strings.sidebarEyebrow || 'Want more?' + ), + el( + 'p', + { className: 'raft-title' }, + strings.sidebarTitle || 'Try Raft Pro' + ), + el( + 'a', + { + href: upgradeUrl, + target: '_blank', + rel: 'noopener noreferrer', + }, + ( strings.sidebarLink || "See what's included" ) + ' →' + ), + ] ); + } + + /** + * Make sure the nudge exists AND is the last child of the sidebar + * content area. React owns this subtree and re-mounts step-specific + * controls between wizard steps — if our node ends up sandwiched + * between `.o-sidebar__info` and the freshly-mounted controls, the + * nudge appears in the middle of the sidebar instead of at the bottom. + * + * Idempotent: if the nudge is already last child, do nothing — so + * MutationObserver loops don't re-trigger themselves. + */ + function injectSidebarNudge() { + const container = document.querySelector( '.o-sidebar__content' ); + if ( ! container ) { + return; + } + + const existing = document.getElementById( SIDEBAR_ID ); + + if ( existing && existing === container.lastElementChild ) { + return; + } + + if ( existing && existing.parentNode ) { + existing.parentNode.removeChild( existing ); + } + + container.appendChild( buildSidebarNudge() ); + } + + /** + * Inject the larger Finish-step card before its action buttons. Returns + * early if the Finish container isn't in the DOM (we're not on Finish + * step yet) or if we already injected. + */ + function injectFinishCard() { + if ( document.getElementById( FINISH_ID ) ) { + return; + } + const container = document.querySelector( '.o-finish__container' ); + if ( ! container ) { + return; + } + + const card = el( 'div', { id: FINISH_ID }, [ + el( + 'p', + { className: 'raft-eyebrow' }, + strings.finishEyebrow || 'Get even more' + ), + el( + 'h3', + null, + strings.finishTitle || 'Take it further with Raft Pro' + ), + el( + 'p', + null, + strings.finishBody || + 'Unlock 17 extra patterns, 8 style variations, 7 ready-made page templates, and a fully designed WooCommerce storefront.' + ), + el( + 'a', + { + href: upgradeUrl, + target: '_blank', + rel: 'noopener noreferrer', + className: 'raft-cta', + }, + strings.finishCta || 'Upgrade to Pro' + ), + ] ); + + // Insert before the actions row so it sits naturally above the + // CTAs rather than after them (lower visual hierarchy). + const actions = container.querySelector( '.o-finish__actions' ); + if ( actions ) { + container.insertBefore( card, actions ); + } else { + container.appendChild( card ); + } + } + + let rafPending = false; + + function tick() { + injectStyles(); + injectSidebarNudge(); + injectFinishCard(); + } + + /** + * Debounce tick() to the next animation frame. wp.data.subscribe fires + * synchronously DURING React's render phase, before commit — running + * tick() at that point can land our nodes between info and a + * not-yet-mounted Controls child. Deferring to rAF runs us after the + * commit phase, when the DOM matches React's intent. + */ + function scheduleTick() { + if ( rafPending ) { + return; + } + rafPending = true; + ( + window.requestAnimationFrame || + function ( cb ) { + return setTimeout( cb, 16 ); + } + )( function () { + rafPending = false; + tick(); + } ); + } + + let observer = null; + + /** + * Watch the wizard root for child changes. Fires AFTER React's commit + * phase, so the DOM is settled when we re-inject. Idempotent: our + * inject functions short-circuit when the nudge is already in the + * right place, so the observer doesn't loop on its own mutations. + */ + function setupObserver() { + if ( observer ) { + return true; + } + const root = + document.getElementById( 'otter-onboarding' ) || document.body; + if ( ! root ) { + return false; + } + observer = new MutationObserver( scheduleTick ); + observer.observe( root, { childList: true, subtree: true } ); + return true; + } + + function start() { + // React commit happens after subscribe fires — defer to next frame. + try { + data.subscribe( scheduleTick ); + } catch ( e ) { + // Subscribe can throw before store registers; the poll below + // covers that window. + } + + setupObserver(); + + // Initial poll covers the brief window before Otter mounts the + // wizard root (the observer needs an existing node to attach to). + let attempts = 0; + const poll = setInterval( function () { + attempts++; + if ( setupObserver() ) { + scheduleTick(); + } + if ( attempts > 30 ) { + clearInterval( poll ); + } + }, 200 ); + } + + if ( domReady ) { + domReady( start ); + } else if ( 'loading' === document.readyState ) { + document.addEventListener( 'DOMContentLoaded', start ); + } else { + start(); + } +} )(); diff --git a/composer.json b/composer.json index 5c9a014f..84427f56 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "wp-coding-standards/wpcs": "^2.3", "squizlabs/php_codesniffer": "^3.7", "phpcompatibility/php-compatibility": "^9.3", - "yoast/phpunit-polyfills": "^2.0" + "yoast/phpunit-polyfills": "^3.1" }, "scripts": { "format-fix-exit": "\"vendor/bin/phpcbf-fix-exit-0\" --standard=phpcs.xml --report-summary --report-source -s --runtime-set testVersion 7.0- ", diff --git a/composer.lock b/composer.lock index b1947c71..631e1ef6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3f1402152d7e4f9ecbc29193c854494a", + "content-hash": "1d2999c6ac7549d52b352485bfd0c866", "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.51", + "version": "3.3.52", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d" + "reference": "d1ae68cbd4f84934b4d982e9eeff317b9f4c814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/bb2a8414b0418b18c68c9ff1df3d7fb10467928d", - "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/d1ae68cbd4f84934b4d982e9eeff317b9f4c814a", + "reference": "d1ae68cbd4f84934b4d982e9eeff317b9f4c814a", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.51" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.52" }, - "time": "2026-03-30T07:58:49+00:00" + "time": "2026-05-14T19:43:56+00:00" } ], "packages-dev": [ @@ -1270,16 +1270,16 @@ }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", "shasum": "" }, "require": { @@ -1313,7 +1313,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" }, "funding": [ { @@ -1321,7 +1321,7 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2024-03-01T13:45:45+00:00" }, { "name": "sebastian/comparator", @@ -1635,16 +1635,16 @@ }, { "name": "sebastian/object-enumerator", - "version": "3.0.4", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + "reference": "ac5b293dba925751b808e02923399fb44ff0d541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541", "shasum": "" }, "require": { @@ -1680,7 +1680,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" }, "funding": [ { @@ -1688,20 +1688,20 @@ "type": "github" } ], - "time": "2020-11-30T07:40:27+00:00" + "time": "2024-03-01T13:54:02+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", "shasum": "" }, "require": { @@ -1735,7 +1735,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" }, "funding": [ { @@ -1743,20 +1743,20 @@ "type": "github" } ], - "time": "2020-11-30T07:37:18+00:00" + "time": "2024-03-01T13:56:04+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/9bfd3c6f1f08c026f542032dfb42813544f7d64c", + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c", "shasum": "" }, "require": { @@ -1798,7 +1798,7 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.2" }, "funding": [ { @@ -1806,7 +1806,7 @@ "type": "github" } ], - "time": "2020-11-30T07:34:24+00:00" + "time": "2024-03-01T14:07:30+00:00" }, { "name": "sebastian/resource-operations", @@ -1961,16 +1961,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.3", + "version": "3.13.4", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ad545ea9c1b7d270ce0fc9cbfb884161cd706119", + "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119", "shasum": "" }, "require": { @@ -2035,9 +2035,13 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" } ], - "time": "2024-09-18T10:38:58+00:00" + "time": "2025-09-05T05:47:09+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2061,12 +2065,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + }, "branch-alias": { "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -2342,29 +2346,31 @@ }, { "name": "yoast/phpunit-polyfills", - "version": "2.0.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "c758753e8f9dac251fed396a73c8305af3f17922" + "reference": "e6381c62c4df51677b657fbac79b79dfce7acdab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/c758753e8f9dac251fed396a73c8305af3f17922", - "reference": "c758753e8f9dac251fed396a73c8305af3f17922", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/e6381c62c4df51677b657fbac79b79dfce7acdab", + "reference": "e6381c62c4df51677b657fbac79b79dfce7acdab", "shasum": "" }, "require": { - "php": ">=5.6", - "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" + "php": ">=7.0", + "phpunit/phpunit": "^6.4.4 || ^7.0 || ^8.0 || ^9.0 || ^11.0" }, "require-dev": { - "yoast/yoastcs": "^2.3.0" + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -2396,9 +2402,10 @@ ], "support": { "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2023-06-06T20:28:24+00:00" + "time": "2025-01-12T08:41:37+00:00" } ], "aliases": [], diff --git a/inc/Admin.php b/inc/Admin.php index cff945f4..bf751cb1 100644 --- a/inc/Admin.php +++ b/inc/Admin.php @@ -180,7 +180,7 @@ public function render_welcome_notice() { $notice_html .= ''; - $learn_more = '' . __( 'Learn More', 'raft' ) . ''; + $learn_more = '' . __( 'Learn More', 'raft' ) . ''; $notice_html .= '
' . __( 'Install our free builder plugin for more blocks, enhanced functionality, and seamless theme setup.', 'raft' ) . ' ' . $learn_more . '
'; diff --git a/inc/Core.php b/inc/Core.php index 4d635941..e9e4d614 100644 --- a/inc/Core.php +++ b/inc/Core.php @@ -44,6 +44,9 @@ public function __construct() { new Admin(); new Block_Patterns(); new Block_Styles(); + new Dashboard(); + new Pro_Promotions(); + new Wizard_Promo(); } /** @@ -56,6 +59,37 @@ private function run_hooks() { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'add_editor_styles' ) ); add_filter( 'raft_strings', array( $this, 'strings' ) ); + add_filter( 'home_template_hierarchy', array( $this, 'home_falls_back_to_archive' ) ); + } + + /** + * Let the blog/posts page render through `archive.html` when no `home.html` + * is present. WP's default hierarchy for the home/posts page is + * [home, index] — archive is not in the chain — so a single archive + * customization wouldn't otherwise show up on the page assigned as Posts + * page. Inserting `archive` ahead of `index` keeps `home.html` winning + * if a child theme ever provides one, while giving the archive template + * one source of truth for all post-listing contexts. + * + * @param array $hierarchy Candidate template slugs in lookup order. + * + * @return array + */ + public function home_falls_back_to_archive( $hierarchy ) { + if ( in_array( 'archive.php', $hierarchy, true ) ) { + return $hierarchy; + } + + $index = array_search( 'index.php', $hierarchy, true ); + + if ( false === $index ) { + $hierarchy[] = 'archive.php'; + return $hierarchy; + } + + array_splice( $hierarchy, $index, 0, 'archive.php' ); + + return $hierarchy; } /** @@ -71,7 +105,6 @@ public function setup() { add_theme_support( 'starter-content', $starter_content->get() ); add_theme_support( 'wp-block-styles' ); add_theme_support( 'automatic-feed-links' ); - add_theme_support( 'title-tag' ); add_theme_support( 'post-thumbnails' ); add_theme_support( 'editor-styles' ); add_theme_support( @@ -102,7 +135,7 @@ public function setup() { 'title' => __( 'Archive Cards', 'raft' ), ), 'archive-row' => array( - 'file' => RAFT_DIR . 'library/archive/archive-row.php', + 'file' => RAFT_DIR . 'library/archive/archive-rows.php', 'title' => __( 'Archive Row', 'raft' ), ), ), @@ -156,6 +189,13 @@ public function setup() { ) ); + // WP < 5.9, wp_head() does NOT auto-output{$eyebrow}
+ + +{$body}
+ + + + +' . esc_html( $raft_strings['short_text'] ) . '
- + +' . esc_html( $raft_strings['short_text'] ) . '
+ - - - + + - +