12 Commits

15 changed files with 493 additions and 274 deletions

View File

@@ -0,0 +1,20 @@
<?php
// Customize home URL for headless WordPress
add_filter( 'home_url', 'headless_home_url', 10, 4 );
function headless_home_url( $url, $path, $orig_scheme, $blog_id ) {
// Exclude specific patterns from rewriting
$excluded_patterns = array(
'#/wp-json(/|$)#i', // WP REST API
'#\.(xsl|xml)$#i', // Sitemap and XSLT files
);
foreach ( $excluded_patterns as $pattern ) {
if ( preg_match( $pattern, $url ) ) {
return get_site_url( $blog_id, $path, $orig_scheme );
}
}
// Rewrite URL protocol to match original home scheme
$scheme = wp_parse_url( get_option( 'home' ) )['scheme'] ?? 'https';
return preg_replace( '#^https:#i', "$scheme:", $url );
}

View File

@@ -0,0 +1,9 @@
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
export function useResponsive() {
const { isMobileOrTablet } = useDevice();
const breakpoints = useBreakpoints(breakpointsTailwind, { ssrWidth: isMobileOrTablet ? 375 : 1024 });
const isDesktop = breakpoints.greaterOrEqual("lg");
return { breakpoints, isDesktop };
}

View File

@@ -5,4 +5,9 @@ require_once __DIR__ . '/includes/core/theme-setup.php';
// Vendors // Vendors
require_once __DIR__ . '/includes/vendors/acf.php'; require_once __DIR__ . '/includes/vendors/acf.php';
require_once __DIR__ . '/includes/vendors/rankmath.php';
require_once __DIR__ . '/includes/vendors/tinymce.php'; require_once __DIR__ . '/includes/vendors/tinymce.php';
require_once __DIR__ . '/includes/vendors/wpgraphql.php';
// WPGraphQL
require_once __DIR__ . '/includes/wpgraphql/term-connection.php';

View File

@@ -17,21 +17,6 @@ function moonshine_after_setup_theme() {
// Register sidebars // Register sidebars
} }
// Bypass headless home URL for specific cases
add_filter( 'home_url', 'moonshine_bypass_home_url', 10, 4 );
function moonshine_bypass_home_url( $url, $path, $orig_scheme, $blog_id ) {
$excluded_patterns = array(
'#/wp-json(/|$)#i', // WP REST API
'#\.(xsl|xml)$#i', // Sitemap and XSLT files
);
foreach ( $excluded_patterns as $pattern ) {
if ( preg_match( $pattern, $url ) ) {
return get_site_url( $blog_id, $path, $orig_scheme );
}
}
return $url;
}
// Display theme version in admin footer // Display theme version in admin footer
add_filter( 'update_footer', 'moonshine_update_footer', 100 ); add_filter( 'update_footer', 'moonshine_update_footer', 100 );
function moonshine_update_footer() { function moonshine_update_footer() {

View File

@@ -0,0 +1 @@
<?php

View File

@@ -0,0 +1,17 @@
<?php
// Default WPGraphQL settings
add_filter( 'graphql_get_setting_section_field_value', 'moonshine_wpgraphql_settings', 10, 5 );
function moonshine_wpgraphql_settings( $value, $default_value, $option_name, $section_fields, $section_name ) {
if ( $section_name === 'graphql_general_settings' ) {
switch ( $option_name ) {
case 'graphql_endpoint':
$value = 'graphql';
break;
case "public_introspection_enabled":
$value = "on";
break;
}
}
return $value;
}

View File

@@ -0,0 +1,13 @@
<?php
// Override TermConnection query args
add_filter( 'graphql_term_object_connection_query_args', 'moonshine_graphql_term_object_connection_query_args', 10, 3 );
function moonshine_graphql_term_object_connection_query_args( $query_args, $source, $args ) {
// Sort by 'order' meta value instead of legacy 'term_order' field
if ( 'term_order' === $args['where']['orderby'] ?? false ) {
$query_args['meta_key'] = 'order';
$query_args['orderby'] = 'meta_value_num';
}
return $query_args;
}

View File

@@ -1,3 +1,5 @@
import { version } from "./package.json";
const siteUrl = process.env.NUXT_SITE_URL; const siteUrl = process.env.NUXT_SITE_URL;
if (!siteUrl) { if (!siteUrl) {
throw new Error(`NUXT_SITE_URL is not defined. Make sure to set it in your build environment variables.`); throw new Error(`NUXT_SITE_URL is not defined. Make sure to set it in your build environment variables.`);
@@ -15,8 +17,10 @@ export default defineNuxtConfig({
"@lewebsimple/nuxt-graphql", "@lewebsimple/nuxt-graphql",
"@nuxt/eslint", "@nuxt/eslint",
"@nuxt/ui", "@nuxt/ui",
"@nuxtjs/device",
"@nuxtjs/seo", "@nuxtjs/seo",
"nuxt-auth-utils", "nuxt-auth-utils",
"nuxt-svgo",
], ],
components: { components: {
@@ -39,6 +43,10 @@ export default defineNuxtConfig({
colorMode: false, colorMode: false,
}, },
runtimeConfig: {
wpUrl,
},
compatibilityDate: "2026-01-01", compatibilityDate: "2026-01-01",
nitro: { nitro: {
@@ -47,13 +55,24 @@ export default defineNuxtConfig({
deployConfig: true, deployConfig: true,
nodeCompat: true, nodeCompat: true,
wrangler: { wrangler: {
name: "foobar", // Project name
name: "moonshine",
// Cloudflare Workers settings
compatibility_date: "2026-01-27", compatibility_date: "2026-01-27",
main: "./.output/server/index.mjs",
observability: { enabled: true },
preview_urls: false,
// Environment variables
vars: { vars: {
NODE_ENV: "production", NODE_ENV: "staging",
NUXT_SITE_URL: siteUrl, NUXT_SITE_URL: siteUrl,
NUXT_WP_URL: wpUrl, NUXT_WP_URL: wpUrl,
}, },
// Bindings
assets: {
binding: "ASSETS",
directory: "./.output/public/",
},
}, },
}, },
}, },
@@ -71,6 +90,11 @@ export default defineNuxtConfig({
}, },
graphql: { graphql: {
client: {
cache: {
keyVersion: version,
},
},
server: { server: {
context: ["server/graphql/context"], context: ["server/graphql/context"],
schema: { schema: {
@@ -85,4 +109,10 @@ export default defineNuxtConfig({
sitemap: false, sitemap: false,
svgo: {
autoImportPath: "~/assets/svg/",
componentPrefix: "Svg",
defaultImport: "component",
},
}); });

View File

@@ -16,12 +16,14 @@
}, },
"dependencies": { "dependencies": {
"@iconify-json/lucide": "^1.2.87", "@iconify-json/lucide": "^1.2.87",
"@lewebsimple/nuxt-graphql": "^0.6.1", "@lewebsimple/nuxt-graphql": "^0.6.6",
"@nuxt/ui": "4.3.0", "@nuxt/ui": "4.3.0",
"@nuxtjs/seo": "^3.3.0", "@nuxtjs/device": "4.0.0",
"@nuxtjs/seo": "^3.4.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"nuxt": "^4.3.0", "nuxt": "^4.3.0",
"nuxt-auth-utils": "^0.5.28", "nuxt-auth-utils": "^0.5.28",
"nuxt-svgo": "^4.2.6",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
"vue": "^3.5.27", "vue": "^3.5.27",
"vue-router": "^4.6.4", "vue-router": "^4.6.4",

File diff suppressed because it is too large Load Diff

View File

@@ -3250,6 +3250,19 @@ type GeneralSettings {
"""Code local de linstallation WordPress.""" """Code local de linstallation WordPress."""
language: String language: String
"""
The media item representing the site icon configured in site settings, used as the site&#039;s favicon and app icon.
"""
siteIcon: GeneralSettingsToMediaItemConnectionEdge
"""
Site icon URL configured in site settings, used as the site&#039;s favicon and app icon.
"""
siteIconUrl(
"""Size of the site icon in pixels. Defaults to 512. Max 512."""
size: Int
): String
""" """
Le numéro du jour de la semaine à laquelle la semaine devrait commencer. Le numéro du jour de la semaine à laquelle la semaine devrait commencer.
""" """
@@ -3268,6 +3281,17 @@ type GeneralSettings {
url: String url: String
} }
"""Connection between the GeneralSettings type and the MediaItem type"""
type GeneralSettingsToMediaItemConnectionEdge implements Edge & MediaItemConnectionEdge & OneToOneConnection {
"""
Opaque reference to the nodes position in the connection. Value can be used with pagination args.
"""
cursor: String
"""The node of the connection, without the edges"""
node: MediaItem!
}
"""The Login client options for the github provider.""" """The Login client options for the github provider."""
type GithubClientOptions implements LoginClientOptions { type GithubClientOptions implements LoginClientOptions {
"""The client ID.""" """The client ID."""
@@ -9478,6 +9502,18 @@ type RankMathAuthorArchiveMetaSettings implements RankMathMetaSettingWithArchive
robotsMeta: [RankMathRobotsMetaValueEnum] robotsMeta: [RankMathRobotsMetaValueEnum]
} }
"""The Breadcrumb trail."""
type RankMathBreadcrumbs {
"""Whether the given breadcrumb is hidden from the schema"""
isHidden: Boolean
"""The text for the given breadcrumb"""
text: String
"""The url for the given breadcrumb"""
url: String
}
"""The RankMath SEO breadcrumbs settings.""" """The RankMath SEO breadcrumbs settings."""
type RankMathBreadcrumbsConfig { type RankMathBreadcrumbsConfig {
"""Format the label used for archive pages.""" """Format the label used for archive pages."""
@@ -9533,6 +9569,9 @@ type RankMathCategoryTermSeo implements RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -9563,6 +9602,9 @@ interface RankMathContentNodeSeo implements RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -9825,6 +9867,9 @@ type RankMathMediaItemObjectSeo implements RankMathContentNodeSeo & RankMathSeo
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -9861,6 +9906,9 @@ type RankMathMediaItemTypeSeo implements RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -10602,6 +10650,9 @@ type RankMathPageObjectSeo implements RankMathContentNodeSeo & RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -10638,6 +10689,9 @@ type RankMathPageTypeSeo implements RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -10668,6 +10722,9 @@ type RankMathPostFormatTermSeo implements RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -10698,6 +10755,9 @@ type RankMathPostObjectSeo implements RankMathContentNodeSeo & RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -10734,6 +10794,9 @@ type RankMathPostTypeSeo implements RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -10806,6 +10869,9 @@ interface RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -11253,6 +11319,9 @@ type RankMathTagTermSeo implements RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -11313,6 +11382,9 @@ type RankMathUserSeo implements RankMathSeo {
"""The title to use in the breadcrumbs for this post""" """The title to use in the breadcrumbs for this post"""
breadcrumbTitle: String breadcrumbTitle: String
"""The breadcrumbs trail for the given object"""
breadcrumbs: [RankMathBreadcrumbs]
"""The canonical url.""" """The canonical url."""
canonicalUrl: String canonicalUrl: String
@@ -15551,9 +15623,6 @@ enum UserRoleEnum {
"""User role with specific capabilities""" """User role with specific capabilities"""
SUBSCRIBER SUBSCRIBER
"""User role with specific capabilities"""
TRANSLATOR
} }
"""Connection between the User type and the Comment type""" """Connection between the User type and the Comment type"""

View File

@@ -1,11 +1,10 @@
import { defu } from "defu";
export default defineRemoteExecutorHooks({ export default defineRemoteExecutorHooks({
onRequest(request) { onRequest(request) {
if (request.context.authToken) { // Attach the Authorization header if an authToken is present in the context
request.extensions ??= {}; if (request.context?.authToken) {
request.extensions.headers = { request.extensions = defu(request.extensions, { headers: { Authorization: `Bearer ${request.context.authToken}` } });
...request.extensions.headers,
Authorization: `Bearer ${request.context.authToken}`,
};
} }
}, },
}); });

View File

@@ -42,8 +42,8 @@ function getAuthUser(user: AuthUserFragment): User {
// Refresh auth token by calling remote GraphQL endpoint directly // Refresh auth token by calling remote GraphQL endpoint directly
export async function refreshAuthToken(refreshToken: string): Promise<string | undefined> { export async function refreshAuthToken(refreshToken: string): Promise<string | undefined> {
// TODO: const { public: { graphql: { endpoint } } } = useRuntimeConfig(); const { public: { wpUrl } } = useRuntimeConfig();
const endpoint = `${process.env.NUXT_WP_URL || "https://cultureat.ledevsimple.ca"}/graphql`; const endpoint = `${wpUrl}/graphql`;
const { data } = await executeGraphQLHTTP<ResultOf<"AuthRefreshToken">>({ const { data } = await executeGraphQLHTTP<ResultOf<"AuthRefreshToken">>({
query: AuthRefreshTokenDocument, query: AuthRefreshTokenDocument,
variables: { refreshToken }, variables: { refreshToken },

View File

@@ -1,4 +1,4 @@
// Helper: Extracts nodes from a GraphQL connection object, returning an empty array if nodes are absent. // Helper: Extracts nodes from a GraphQL connection object, returning an empty array if nodes are absent.
export function extractNodes<T>(connection: { nodes?: readonly T[] } | null | undefined): readonly T[] { export function extractNodes<T>(connection: { nodes?: T[] } | undefined): T[] {
return connection?.nodes ?? []; return connection?.nodes ?? [];
} }

View File

@@ -1,21 +0,0 @@
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"main": "./.output/server/index.mjs",
/**
* Static Assets Binding
*/
"assets": {
"binding": "ASSETS",
"directory": "./.output/public/"
},
/**
* Observability & Analytics
*/
"observability": {
"enabled": true
},
}