@wpnuxt/core 2.0.0-alpha.9 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/dist/module.d.mts +35 -0
- package/dist/module.d.ts +35 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +113 -21
- package/dist/runtime/components/WPContent.d.vue.ts +0 -0
- package/dist/runtime/components/WPContent.vue +61 -0
- package/dist/runtime/components/WPContent.vue.d.ts +0 -0
- package/dist/runtime/composables/useWPContent.js +30 -12
- package/dist/runtime/plugins/sanitizeHtml.d.ts +0 -0
- package/dist/runtime/plugins/sanitizeHtml.js +29 -0
- package/dist/runtime/queries/Pages.gql +2 -2
- package/dist/runtime/queries/Posts.gql +2 -2
- package/dist/runtime/types/stub.d.ts +6 -2
- package/dist/runtime/util/content.js +15 -7
- package/dist/runtime/util/links.d.ts +0 -0
- package/dist/runtime/util/links.js +40 -0
- package/package.json +14 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present - WPNuxt Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/module.d.mts
CHANGED
|
@@ -45,6 +45,41 @@ interface WPNuxtConfig {
|
|
|
45
45
|
* @default true
|
|
46
46
|
*/
|
|
47
47
|
downloadSchema: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Bearer token for authenticated schema introspection at build time.
|
|
50
|
+
*
|
|
51
|
+
* Required when your WordPress GraphQL endpoint has public introspection disabled.
|
|
52
|
+
* The token is sent as an `Authorization: Bearer <token>` header during:
|
|
53
|
+
* - Endpoint validation (introspection query)
|
|
54
|
+
* - Schema download (get-graphql-schema)
|
|
55
|
+
*
|
|
56
|
+
* Can also be set via `WPNUXT_SCHEMA_AUTH_TOKEN` environment variable.
|
|
57
|
+
*
|
|
58
|
+
* This token is only used at build time and is NOT included in client bundles.
|
|
59
|
+
*/
|
|
60
|
+
schemaAuthToken?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Whether to replace internal WordPress links with client-side navigation (NuxtLink).
|
|
63
|
+
*
|
|
64
|
+
* When enabled, clicks on `<a>` tags pointing to the WordPress domain inside
|
|
65
|
+
* `<WPContent>` are intercepted and handled via `navigateTo()`.
|
|
66
|
+
*
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
69
|
+
replaceLinks?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Whether to convert featured image `sourceUrl` values to relative paths.
|
|
72
|
+
*
|
|
73
|
+
* When enabled, a `relativePath` property is added to `featuredImage.node`
|
|
74
|
+
* by stripping the WordPress domain from `sourceUrl`.
|
|
75
|
+
*
|
|
76
|
+
* Set to `true` when using relative image paths with Nuxt Image or a proxy.
|
|
77
|
+
* Leave `false` (default) for SSG or external image providers (Vercel, Cloudflare)
|
|
78
|
+
* that need full URLs.
|
|
79
|
+
*
|
|
80
|
+
* @default false
|
|
81
|
+
*/
|
|
82
|
+
imageRelativePaths?: boolean;
|
|
48
83
|
/**
|
|
49
84
|
* Whether to enable debug mode
|
|
50
85
|
*
|
package/dist/module.d.ts
CHANGED
|
@@ -45,6 +45,41 @@ interface WPNuxtConfig {
|
|
|
45
45
|
* @default true
|
|
46
46
|
*/
|
|
47
47
|
downloadSchema: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Bearer token for authenticated schema introspection at build time.
|
|
50
|
+
*
|
|
51
|
+
* Required when your WordPress GraphQL endpoint has public introspection disabled.
|
|
52
|
+
* The token is sent as an `Authorization: Bearer <token>` header during:
|
|
53
|
+
* - Endpoint validation (introspection query)
|
|
54
|
+
* - Schema download (get-graphql-schema)
|
|
55
|
+
*
|
|
56
|
+
* Can also be set via `WPNUXT_SCHEMA_AUTH_TOKEN` environment variable.
|
|
57
|
+
*
|
|
58
|
+
* This token is only used at build time and is NOT included in client bundles.
|
|
59
|
+
*/
|
|
60
|
+
schemaAuthToken?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Whether to replace internal WordPress links with client-side navigation (NuxtLink).
|
|
63
|
+
*
|
|
64
|
+
* When enabled, clicks on `<a>` tags pointing to the WordPress domain inside
|
|
65
|
+
* `<WPContent>` are intercepted and handled via `navigateTo()`.
|
|
66
|
+
*
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
69
|
+
replaceLinks?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Whether to convert featured image `sourceUrl` values to relative paths.
|
|
72
|
+
*
|
|
73
|
+
* When enabled, a `relativePath` property is added to `featuredImage.node`
|
|
74
|
+
* by stripping the WordPress domain from `sourceUrl`.
|
|
75
|
+
*
|
|
76
|
+
* Set to `true` when using relative image paths with Nuxt Image or a proxy.
|
|
77
|
+
* Leave `false` (default) for SSG or external image providers (Vercel, Cloudflare)
|
|
78
|
+
* that need full URLs.
|
|
79
|
+
*
|
|
80
|
+
* @default false
|
|
81
|
+
*/
|
|
82
|
+
imageRelativePaths?: boolean;
|
|
48
83
|
/**
|
|
49
84
|
* Whether to enable debug mode
|
|
50
85
|
*
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import { consola } from 'consola';
|
|
1
2
|
import { defu } from 'defu';
|
|
2
3
|
import { promises, cpSync, existsSync, readdirSync, statSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { writeFile, rename, readFile, mkdir } from 'node:fs/promises';
|
|
4
5
|
import { join, relative, dirname } from 'node:path';
|
|
5
|
-
import { useLogger, createResolver, resolveFiles, defineNuxtModule, addPlugin, addImports, addComponentsDir, addTemplate, addTypeTemplate,
|
|
6
|
+
import { useLogger, createResolver, resolveFiles, defineNuxtModule, addPlugin, hasNuxtModule, addImports, addComponentsDir, addTemplate, addTypeTemplate, installModule } from '@nuxt/kit';
|
|
6
7
|
import { upperFirst } from 'scule';
|
|
7
8
|
import { ref } from 'vue';
|
|
8
9
|
import { parse, GraphQLError } from 'graphql';
|
|
9
10
|
import { execSync } from 'node:child_process';
|
|
10
|
-
import { consola } from 'consola';
|
|
11
11
|
|
|
12
|
-
const version = "2.0.0-
|
|
12
|
+
const version = "2.0.0-beta.2";
|
|
13
13
|
|
|
14
14
|
function createModuleError(module, message) {
|
|
15
15
|
return new Error(formatErrorMessage(module, message));
|
|
@@ -201,7 +201,7 @@ async function prepareContext(ctx) {
|
|
|
201
201
|
const getFragmentType = (q) => {
|
|
202
202
|
if (q.fragments?.length) {
|
|
203
203
|
const fragmentSuffix = q.nodes?.includes("nodes") ? "[]" : "";
|
|
204
|
-
return q.fragments.map((f) =>
|
|
204
|
+
return q.fragments.map((f) => `WithImagePath<${f}Fragment>${fragmentSuffix}`).join(" | ");
|
|
205
205
|
}
|
|
206
206
|
if (q.nodes?.length) {
|
|
207
207
|
let typePath = `${q.name}RootQuery`;
|
|
@@ -304,6 +304,11 @@ async function prepareContext(ctx) {
|
|
|
304
304
|
"",
|
|
305
305
|
"type WPMutationResult<T> = GraphqlResponse<T>",
|
|
306
306
|
"",
|
|
307
|
+
"/** Adds relativePath to featuredImage.node when present (injected at runtime by transformData) */",
|
|
308
|
+
"type WithImagePath<T> = T extends { featuredImage?: unknown }",
|
|
309
|
+
" ? T & { featuredImage?: { node: { relativePath?: string } } }",
|
|
310
|
+
" : T",
|
|
311
|
+
"",
|
|
307
312
|
"declare module '#wpnuxt' {"
|
|
308
313
|
];
|
|
309
314
|
queries.forEach((f) => {
|
|
@@ -350,11 +355,15 @@ async function validateWordPressEndpoint(wordpressUrl, graphqlEndpoint = "/graph
|
|
|
350
355
|
try {
|
|
351
356
|
const controller = new AbortController();
|
|
352
357
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
358
|
+
const headers = {
|
|
359
|
+
"Content-Type": "application/json"
|
|
360
|
+
};
|
|
361
|
+
if (options.authToken) {
|
|
362
|
+
headers["Authorization"] = `Bearer ${options.authToken}`;
|
|
363
|
+
}
|
|
353
364
|
const response = await fetch(fullUrl, {
|
|
354
365
|
method: "POST",
|
|
355
|
-
headers
|
|
356
|
-
"Content-Type": "application/json"
|
|
357
|
-
},
|
|
366
|
+
headers,
|
|
358
367
|
body: JSON.stringify({
|
|
359
368
|
query: "{ __typename }"
|
|
360
369
|
}),
|
|
@@ -397,7 +406,8 @@ Make sure WPGraphQL plugin is installed and activated on your WordPress site.`
|
|
|
397
406
|
}
|
|
398
407
|
if (options.schemaPath && !existsSync(options.schemaPath)) {
|
|
399
408
|
try {
|
|
400
|
-
|
|
409
|
+
const authFlag = options.authToken ? ` -h "Authorization=Bearer ${options.authToken}"` : "";
|
|
410
|
+
execSync(`npx get-graphql-schema "${fullUrl}"${authFlag} > "${options.schemaPath}"`, {
|
|
401
411
|
stdio: "pipe",
|
|
402
412
|
timeout: 6e4
|
|
403
413
|
// 60 second timeout
|
|
@@ -785,6 +795,8 @@ const module$1 = defineNuxtModule({
|
|
|
785
795
|
warnOnOverride: true
|
|
786
796
|
},
|
|
787
797
|
downloadSchema: true,
|
|
798
|
+
replaceLinks: true,
|
|
799
|
+
imageRelativePaths: false,
|
|
788
800
|
debug: false,
|
|
789
801
|
cache: {
|
|
790
802
|
enabled: true,
|
|
@@ -795,13 +807,19 @@ const module$1 = defineNuxtModule({
|
|
|
795
807
|
},
|
|
796
808
|
async setup(options, nuxt) {
|
|
797
809
|
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
798
|
-
const wpNuxtConfig = loadConfig(options, nuxt);
|
|
810
|
+
const wpNuxtConfig = await loadConfig(options, nuxt);
|
|
811
|
+
if (!wpNuxtConfig) {
|
|
812
|
+
const logger2 = initLogger(false);
|
|
813
|
+
logger2.warn("WordPress URL not configured. Skipping WPNuxt setup. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
799
816
|
const logger = initLogger(wpNuxtConfig.debug);
|
|
800
817
|
logger.debug("Starting WPNuxt in debug mode");
|
|
801
818
|
const resolver = createResolver(import.meta.url);
|
|
802
819
|
nuxt.options.runtimeConfig.public.buildHash = randHashGenerator();
|
|
803
820
|
addPlugin(resolver.resolve("./runtime/plugins/graphqlConfig"));
|
|
804
821
|
addPlugin(resolver.resolve("./runtime/plugins/graphqlErrors"));
|
|
822
|
+
addPlugin(resolver.resolve("./runtime/plugins/sanitizeHtml"));
|
|
805
823
|
configureTrailingSlash(nuxt, logger);
|
|
806
824
|
const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver);
|
|
807
825
|
await setupServerOptions(nuxt, resolver, logger);
|
|
@@ -814,7 +832,7 @@ const module$1 = defineNuxtModule({
|
|
|
814
832
|
await validateWordPressEndpoint(
|
|
815
833
|
wpNuxtConfig.wordpressUrl,
|
|
816
834
|
wpNuxtConfig.graphqlEndpoint,
|
|
817
|
-
{ schemaPath }
|
|
835
|
+
{ schemaPath, authToken: wpNuxtConfig.schemaAuthToken }
|
|
818
836
|
);
|
|
819
837
|
logger.debug("Schema downloaded successfully");
|
|
820
838
|
} else {
|
|
@@ -822,7 +840,8 @@ const module$1 = defineNuxtModule({
|
|
|
822
840
|
try {
|
|
823
841
|
await validateWordPressEndpoint(
|
|
824
842
|
wpNuxtConfig.wordpressUrl,
|
|
825
|
-
wpNuxtConfig.graphqlEndpoint
|
|
843
|
+
wpNuxtConfig.graphqlEndpoint,
|
|
844
|
+
{ authToken: wpNuxtConfig.schemaAuthToken }
|
|
826
845
|
);
|
|
827
846
|
logger.debug("WordPress endpoint validation passed");
|
|
828
847
|
} catch (error) {
|
|
@@ -846,19 +865,49 @@ const module$1 = defineNuxtModule({
|
|
|
846
865
|
const nitroOptions = nuxt.options;
|
|
847
866
|
nitroOptions.nitro = nitroOptions.nitro || {};
|
|
848
867
|
nitroOptions.nitro.routeRules = nitroOptions.nitro.routeRules || {};
|
|
849
|
-
nitroOptions.nitro.routeRules["/api/wpnuxt/**"] = {
|
|
868
|
+
nitroOptions.nitro.routeRules["/api/wpnuxt/query/**"] = {
|
|
850
869
|
cache: {
|
|
851
870
|
maxAge,
|
|
852
871
|
swr: wpNuxtConfig.cache?.swr !== false
|
|
853
872
|
}
|
|
854
873
|
};
|
|
855
|
-
logger.debug(`Server-side caching enabled for GraphQL
|
|
874
|
+
logger.debug(`Server-side caching enabled for GraphQL queries (maxAge: ${maxAge}s, SWR: ${wpNuxtConfig.cache?.swr !== false})`);
|
|
856
875
|
}
|
|
876
|
+
{
|
|
877
|
+
const nitroOptions = nuxt.options;
|
|
878
|
+
nitroOptions.nitro = nitroOptions.nitro || {};
|
|
879
|
+
nitroOptions.nitro.routeRules = nitroOptions.nitro.routeRules || {};
|
|
880
|
+
nitroOptions.nitro.routeRules["/wp-content/uploads/**"] = {
|
|
881
|
+
proxy: `${wpNuxtConfig.wordpressUrl}/wp-content/uploads/**`
|
|
882
|
+
};
|
|
883
|
+
logger.debug(`Configured WordPress uploads proxy: /wp-content/uploads/** \u2192 ${wpNuxtConfig.wordpressUrl}/wp-content/uploads/**`);
|
|
884
|
+
}
|
|
885
|
+
nuxt.hook("modules:done", () => {
|
|
886
|
+
if (hasNuxtModule("@nuxt/image")) {
|
|
887
|
+
const imageConfig = nuxt.options.image || {};
|
|
888
|
+
const provider = process.env.NUXT_IMAGE_PROVIDER || imageConfig.provider || "ipx";
|
|
889
|
+
if (provider === "ipx") {
|
|
890
|
+
const wpHost = new URL(wpNuxtConfig.wordpressUrl).host;
|
|
891
|
+
const domains = imageConfig.domains || [];
|
|
892
|
+
if (!domains.includes(wpHost)) {
|
|
893
|
+
domains.push(wpHost);
|
|
894
|
+
}
|
|
895
|
+
imageConfig.domains = domains;
|
|
896
|
+
const alias = imageConfig.alias || {};
|
|
897
|
+
alias["/wp-content"] = `${wpNuxtConfig.wordpressUrl}/wp-content`;
|
|
898
|
+
imageConfig.alias = alias;
|
|
899
|
+
nuxt.options.image = imageConfig;
|
|
900
|
+
logger.debug(`Configured IPX for WordPress: alias /wp-content \u2192 ${wpNuxtConfig.wordpressUrl}/wp-content, domain '${wpHost}' added`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
});
|
|
857
904
|
configureVercelSettings(nuxt, logger);
|
|
858
905
|
addImports([
|
|
859
906
|
{ name: "useWPContent", as: "useWPContent", from: resolver.resolve("./runtime/composables/useWPContent") },
|
|
860
907
|
{ name: "useAsyncWPContent", as: "useAsyncWPContent", from: resolver.resolve("./runtime/composables/useWPContent") },
|
|
861
908
|
{ name: "getRelativeImagePath", as: "getRelativeImagePath", from: resolver.resolve("./runtime/util/images") },
|
|
909
|
+
{ name: "isInternalLink", as: "isInternalLink", from: resolver.resolve("./runtime/util/links") },
|
|
910
|
+
{ name: "toRelativePath", as: "toRelativePath", from: resolver.resolve("./runtime/util/links") },
|
|
862
911
|
{ name: "usePrevNextPost", as: "usePrevNextPost", from: resolver.resolve("./runtime/composables/usePrevNextPost") }
|
|
863
912
|
// Note: useGraphqlMutation is auto-imported via nuxt-graphql-middleware with includeComposables: true
|
|
864
913
|
]);
|
|
@@ -878,6 +927,8 @@ const module$1 = defineNuxtModule({
|
|
|
878
927
|
nuxt.options.alias["#wpnuxt"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt");
|
|
879
928
|
nuxt.options.alias["#wpnuxt/*"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt", "*");
|
|
880
929
|
nuxt.options.alias["#wpnuxt/types"] = resolver.resolve("./types");
|
|
930
|
+
nuxt.options.alias["@wpnuxt/core/server-options"] = resolver.resolve("./server-options");
|
|
931
|
+
nuxt.options.alias["@wpnuxt/core/client-options"] = resolver.resolve("./client-options");
|
|
881
932
|
const nitroOpts = nuxt.options;
|
|
882
933
|
nitroOpts.nitro = nitroOpts.nitro || {};
|
|
883
934
|
nitroOpts.nitro.alias = nitroOpts.nitro.alias || {};
|
|
@@ -904,10 +955,11 @@ const module$1 = defineNuxtModule({
|
|
|
904
955
|
await runInstall(nuxt);
|
|
905
956
|
}
|
|
906
957
|
});
|
|
907
|
-
function loadConfig(options, nuxt) {
|
|
958
|
+
async function loadConfig(options, nuxt) {
|
|
908
959
|
const config = defu({
|
|
909
960
|
wordpressUrl: process.env.WPNUXT_WORDPRESS_URL,
|
|
910
961
|
graphqlEndpoint: process.env.WPNUXT_GRAPHQL_ENDPOINT,
|
|
962
|
+
schemaAuthToken: process.env.WPNUXT_SCHEMA_AUTH_TOKEN,
|
|
911
963
|
// Only override downloadSchema if env var is explicitly set
|
|
912
964
|
downloadSchema: process.env.WPNUXT_DOWNLOAD_SCHEMA !== void 0 ? process.env.WPNUXT_DOWNLOAD_SCHEMA === "true" : void 0,
|
|
913
965
|
debug: process.env.WPNUXT_DEBUG ? process.env.WPNUXT_DEBUG === "true" : void 0
|
|
@@ -915,22 +967,55 @@ function loadConfig(options, nuxt) {
|
|
|
915
967
|
if (config.downloadSchema === void 0) {
|
|
916
968
|
config.downloadSchema = true;
|
|
917
969
|
}
|
|
970
|
+
if (!config.wordpressUrl?.trim()) {
|
|
971
|
+
if (nuxt.options._prepare) {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
if (nuxt.options.dev && process.stdout.isTTY) {
|
|
975
|
+
const wordpressUrl = await consola.prompt(
|
|
976
|
+
"Enter your WordPress site URL (must have WPGraphQL installed):",
|
|
977
|
+
{ type: "text", placeholder: "https://your-wordpress-site.com" }
|
|
978
|
+
);
|
|
979
|
+
if (wordpressUrl && typeof wordpressUrl === "string" && wordpressUrl.trim()) {
|
|
980
|
+
const validation = validateWordPressUrl(wordpressUrl);
|
|
981
|
+
if (validation.valid && validation.normalizedUrl) {
|
|
982
|
+
config.wordpressUrl = validation.normalizedUrl;
|
|
983
|
+
const envPath = join(nuxt.options.rootDir, ".env");
|
|
984
|
+
const envLine = `WPNUXT_WORDPRESS_URL=${validation.normalizedUrl}
|
|
985
|
+
`;
|
|
986
|
+
if (existsSync(envPath)) {
|
|
987
|
+
const existing = await readFile(envPath, "utf-8");
|
|
988
|
+
await atomicWriteFile(envPath, existing.trimEnd() + "\n" + envLine);
|
|
989
|
+
} else {
|
|
990
|
+
await atomicWriteFile(envPath, envLine);
|
|
991
|
+
}
|
|
992
|
+
consola.success(`WordPress URL saved to .env: ${validation.normalizedUrl}`);
|
|
993
|
+
} else {
|
|
994
|
+
throw createModuleError("core", `Invalid WordPress URL: ${validation.error}`);
|
|
995
|
+
}
|
|
996
|
+
} else {
|
|
997
|
+
throw createModuleError("core", "WordPress URL is required. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
|
|
998
|
+
}
|
|
999
|
+
} else {
|
|
1000
|
+
throw createModuleError("core", "WordPress URL is required. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (config.wordpressUrl.endsWith("/")) {
|
|
1004
|
+
throw createModuleError("core", `WordPress URL should not have a trailing slash: ${config.wordpressUrl}`);
|
|
1005
|
+
}
|
|
918
1006
|
nuxt.options.runtimeConfig.public.wordpressUrl = config.wordpressUrl;
|
|
919
1007
|
nuxt.options.runtimeConfig.public.wpNuxt = {
|
|
920
1008
|
wordpressUrl: config.wordpressUrl,
|
|
921
1009
|
graphqlEndpoint: config.graphqlEndpoint,
|
|
1010
|
+
replaceLinks: config.replaceLinks ?? true,
|
|
1011
|
+
imageRelativePaths: config.imageRelativePaths ?? false,
|
|
1012
|
+
hasBlocks: hasNuxtModule("@wpnuxt/blocks"),
|
|
922
1013
|
cache: {
|
|
923
1014
|
enabled: config.cache?.enabled ?? true,
|
|
924
1015
|
maxAge: config.cache?.maxAge ?? 300,
|
|
925
1016
|
swr: config.cache?.swr ?? true
|
|
926
1017
|
}
|
|
927
1018
|
};
|
|
928
|
-
if (!config.wordpressUrl?.trim()) {
|
|
929
|
-
throw createModuleError("core", "WordPress URL is required. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
|
|
930
|
-
}
|
|
931
|
-
if (config.wordpressUrl.endsWith("/")) {
|
|
932
|
-
throw createModuleError("core", `WordPress URL should not have a trailing slash: ${config.wordpressUrl}`);
|
|
933
|
-
}
|
|
934
1019
|
return config;
|
|
935
1020
|
}
|
|
936
1021
|
const SERVER_OPTIONS_TEMPLATE = `import { defineGraphqlServerOptions } from '@wpnuxt/core/server-options'
|
|
@@ -1125,6 +1210,14 @@ async function registerModules(nuxt, resolver, wpNuxtConfig, mergedQueriesFolder
|
|
|
1125
1210
|
scalars: {
|
|
1126
1211
|
DateTime: "string",
|
|
1127
1212
|
ID: "string"
|
|
1213
|
+
},
|
|
1214
|
+
// Pass auth headers for schema download when token is configured
|
|
1215
|
+
...wpNuxtConfig.schemaAuthToken && {
|
|
1216
|
+
urlSchemaOptions: {
|
|
1217
|
+
headers: {
|
|
1218
|
+
Authorization: `Bearer ${wpNuxtConfig.schemaAuthToken}`
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1128
1221
|
}
|
|
1129
1222
|
},
|
|
1130
1223
|
experimental: {
|
|
@@ -1132,7 +1225,6 @@ async function registerModules(nuxt, resolver, wpNuxtConfig, mergedQueriesFolder
|
|
|
1132
1225
|
improvedQueryParamEncoding: true
|
|
1133
1226
|
}
|
|
1134
1227
|
});
|
|
1135
|
-
await registerModule("@radya/nuxt-dompurify", "dompurify", {});
|
|
1136
1228
|
}
|
|
1137
1229
|
|
|
1138
1230
|
export { module$1 as default };
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { isInternalLink, toRelativePath } from "../util/links";
|
|
3
|
+
import { ref, resolveComponent, onMounted, onBeforeUnmount, useRuntimeConfig, navigateTo } from "#imports";
|
|
4
|
+
const wpNuxtConfig = useRuntimeConfig().public.wpNuxt;
|
|
5
|
+
const hasBlockRenderer = !!wpNuxtConfig?.hasBlocks;
|
|
6
|
+
const blockRenderer = hasBlockRenderer ? resolveComponent("BlockRenderer") : null;
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
node: { type: Object, required: false, default: void 0 },
|
|
9
|
+
replaceLinks: { type: Boolean, required: false, default: void 0 }
|
|
10
|
+
});
|
|
11
|
+
const containerRef = ref(null);
|
|
12
|
+
function shouldReplaceLinks() {
|
|
13
|
+
if (props.replaceLinks !== void 0) {
|
|
14
|
+
return props.replaceLinks;
|
|
15
|
+
}
|
|
16
|
+
const config = useRuntimeConfig();
|
|
17
|
+
return config.public.wpNuxt?.replaceLinks !== false;
|
|
18
|
+
}
|
|
19
|
+
function onClick(event) {
|
|
20
|
+
if (!shouldReplaceLinks()) return;
|
|
21
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
22
|
+
const anchor = event.target?.closest?.("a");
|
|
23
|
+
if (!anchor) return;
|
|
24
|
+
const href = anchor.getAttribute("href");
|
|
25
|
+
if (!href) return;
|
|
26
|
+
if (anchor.getAttribute("target") === "_blank") return;
|
|
27
|
+
if (anchor.hasAttribute("download")) return;
|
|
28
|
+
if (anchor.getAttribute("rel")?.includes("external")) return;
|
|
29
|
+
const trimmed = href.trim();
|
|
30
|
+
if (/^(?:mailto|tel|javascript|ftp):/i.test(trimmed)) return;
|
|
31
|
+
const config = useRuntimeConfig();
|
|
32
|
+
const wordpressUrl = config.public.wpNuxt?.wordpressUrl;
|
|
33
|
+
if (isInternalLink(href, wordpressUrl)) {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
navigateTo(toRelativePath(href));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
onMounted(() => {
|
|
39
|
+
containerRef.value?.addEventListener("click", onClick);
|
|
40
|
+
});
|
|
41
|
+
onBeforeUnmount(() => {
|
|
42
|
+
containerRef.value?.removeEventListener("click", onClick);
|
|
43
|
+
});
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div ref="containerRef">
|
|
48
|
+
<template v-if="node">
|
|
49
|
+
<component
|
|
50
|
+
:is="blockRenderer"
|
|
51
|
+
v-if="hasBlockRenderer && node.editorBlocks?.length"
|
|
52
|
+
:node="node"
|
|
53
|
+
/>
|
|
54
|
+
<div
|
|
55
|
+
v-else-if="node.content"
|
|
56
|
+
v-sanitize-html="node.content"
|
|
57
|
+
/>
|
|
58
|
+
</template>
|
|
59
|
+
<slot v-else />
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
File without changes
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { transformData, normalizeUriParam } from "../util/content.js";
|
|
2
|
-
import { computed, ref, watch as vueWatch, useAsyncGraphqlQuery } from "#imports";
|
|
2
|
+
import { computed, ref, watch as vueWatch, useAsyncGraphqlQuery, useRuntimeConfig } from "#imports";
|
|
3
|
+
const defaultGetCachedData = (key, app, ctx) => {
|
|
4
|
+
if (app.isHydrating) {
|
|
5
|
+
return app.payload.data[key];
|
|
6
|
+
}
|
|
7
|
+
if (ctx.cause === "refresh:manual" || ctx.cause === "refresh:hook") {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
return app.static?.data?.[key] ?? app.payload.data[key] ?? app.$graphqlCache?.get(key);
|
|
11
|
+
};
|
|
12
|
+
const noCacheGetCachedData = () => void 0;
|
|
3
13
|
export const useWPContent = (queryName, nodes, fixImagePaths, params, options) => {
|
|
14
|
+
const runtimeWpNuxt = useRuntimeConfig().public.wpNuxt;
|
|
15
|
+
const imageRelativePaths = runtimeWpNuxt?.imageRelativePaths ?? fixImagePaths;
|
|
4
16
|
const {
|
|
5
17
|
clientCache,
|
|
6
18
|
getCachedData: userGetCachedData,
|
|
@@ -26,19 +38,11 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
|
|
|
26
38
|
}
|
|
27
39
|
}, timeoutMs);
|
|
28
40
|
}
|
|
29
|
-
const
|
|
30
|
-
if (app.isHydrating) {
|
|
31
|
-
return app.payload.data[key];
|
|
32
|
-
}
|
|
33
|
-
if (ctx.cause === "refresh:manual" || ctx.cause === "refresh:hook") {
|
|
34
|
-
return void 0;
|
|
35
|
-
}
|
|
36
|
-
return app.static?.data?.[key] ?? app.payload.data[key] ?? app.$graphqlCache?.get(key);
|
|
37
|
-
});
|
|
41
|
+
const getCachedDataFn = userGetCachedData ?? (clientCache === false ? noCacheGetCachedData : defaultGetCachedData);
|
|
38
42
|
const asyncDataOptions = {
|
|
39
43
|
...restOptions,
|
|
40
44
|
// Our getCachedData that properly checks static.data for SSG
|
|
41
|
-
getCachedData:
|
|
45
|
+
getCachedData: getCachedDataFn,
|
|
42
46
|
// Enable graphql caching so the LRU cache is populated for subsequent navigations
|
|
43
47
|
graphqlCaching: { client: clientCache !== false },
|
|
44
48
|
// Pass abort signal for timeout support
|
|
@@ -91,7 +95,21 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
|
|
|
91
95
|
try {
|
|
92
96
|
const queryResult = data.value && typeof data.value === "object" && data.value !== null && "data" in data.value ? data.value.data : void 0;
|
|
93
97
|
if (!queryResult) return void 0;
|
|
94
|
-
|
|
98
|
+
const result = transformData(queryResult, nodes, imageRelativePaths);
|
|
99
|
+
if (import.meta.dev && String(queryName) === "Menu" && !result) {
|
|
100
|
+
console.warn(
|
|
101
|
+
`[wpnuxt] Menu not found. This usually means no classic WordPress menu exists with the specified name.
|
|
102
|
+
|
|
103
|
+
If you're using a block theme (WordPress 6.0+), menus are managed differently:
|
|
104
|
+
1. Classic menus: Go to /wp-admin/nav-menus.php to create a menu
|
|
105
|
+
2. Make sure the menu name matches your query parameter (default: "main")
|
|
106
|
+
|
|
107
|
+
Example: useMenu({ name: 'main' }) requires a menu named "main" in WordPress.
|
|
108
|
+
|
|
109
|
+
See: https://wpnuxt.com/guide/menus`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
95
113
|
} catch (err) {
|
|
96
114
|
if (import.meta.dev) {
|
|
97
115
|
console.warn(`[wpnuxt] Data transformation error for "${String(queryName)}":`, err);
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "#imports";
|
|
2
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
3
|
+
let sanitize;
|
|
4
|
+
if (import.meta.server) {
|
|
5
|
+
sanitize = (html) => html;
|
|
6
|
+
} else {
|
|
7
|
+
let purify = null;
|
|
8
|
+
sanitize = (html) => {
|
|
9
|
+
if (purify) {
|
|
10
|
+
return purify.sanitize(html);
|
|
11
|
+
}
|
|
12
|
+
return html;
|
|
13
|
+
};
|
|
14
|
+
import("dompurify").then((DOMPurify) => {
|
|
15
|
+
purify = DOMPurify.default(window);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
nuxtApp.vueApp.directive("sanitize-html", {
|
|
19
|
+
created(el, binding) {
|
|
20
|
+
el.innerHTML = sanitize(binding.value);
|
|
21
|
+
},
|
|
22
|
+
updated(el, binding) {
|
|
23
|
+
el.innerHTML = sanitize(binding.value);
|
|
24
|
+
},
|
|
25
|
+
getSSRProps(binding) {
|
|
26
|
+
return { innerHTML: sanitize(binding.value) };
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
* Actual types are generated at runtime in consuming applications.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { Ref, ComputedRef, WatchSource, WatchCallback, WatchOptions } from 'vue'
|
|
7
|
+
import type { Ref, ComputedRef, Component, WatchSource, WatchCallback, WatchOptions } from 'vue'
|
|
8
8
|
import type { NuxtApp } from 'nuxt/app'
|
|
9
9
|
|
|
10
|
-
// Stub for #imports - Vue reactivity
|
|
10
|
+
// Stub for #imports - Vue reactivity & lifecycle
|
|
11
11
|
export function computed<T>(getter: () => T): ComputedRef<T>
|
|
12
12
|
export function ref<T>(value: T): Ref<T>
|
|
13
|
+
export function resolveComponent(name: string): Component | string
|
|
14
|
+
export function onMounted(callback: () => void): void
|
|
15
|
+
export function onBeforeUnmount(callback: () => void): void
|
|
13
16
|
export function watch<T>(
|
|
14
17
|
source: WatchSource<T> | WatchSource<T>[],
|
|
15
18
|
callback: WatchCallback<T>,
|
|
@@ -21,6 +24,7 @@ export function defineNuxtPlugin(plugin: (nuxtApp: NuxtApp) => void | Promise<vo
|
|
|
21
24
|
export function useNuxtApp(): NuxtApp
|
|
22
25
|
export function useRuntimeConfig(): Record<string, unknown>
|
|
23
26
|
export function useRoute(): { path: string, params: Record<string, string>, query: Record<string, string> }
|
|
27
|
+
export function navigateTo(to: string): Promise<void>
|
|
24
28
|
|
|
25
29
|
// Stub for #imports - nuxt-graphql-middleware
|
|
26
30
|
export function useAsyncGraphqlQuery<T = unknown>(
|
|
@@ -8,15 +8,23 @@ export const findData = (data, nodes) => {
|
|
|
8
8
|
return void 0;
|
|
9
9
|
}, data);
|
|
10
10
|
};
|
|
11
|
+
function addRelativePath(item) {
|
|
12
|
+
if (!item || typeof item !== "object" || !("featuredImage" in item)) return;
|
|
13
|
+
const featuredImage = item.featuredImage;
|
|
14
|
+
if (featuredImage && typeof featuredImage === "object" && "node" in featuredImage) {
|
|
15
|
+
const node = featuredImage.node;
|
|
16
|
+
if (node && typeof node === "object" && "sourceUrl" in node && typeof node.sourceUrl === "string") {
|
|
17
|
+
node.relativePath = getRelativeImagePath(node.sourceUrl);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
export const transformData = (data, nodes, fixImagePaths) => {
|
|
12
22
|
const transformedData = findData(data, nodes);
|
|
13
|
-
if (fixImagePaths && transformedData
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
node.relativePath = getRelativeImagePath(node.sourceUrl);
|
|
19
|
-
}
|
|
23
|
+
if (fixImagePaths && transformedData) {
|
|
24
|
+
if (Array.isArray(transformedData)) {
|
|
25
|
+
transformedData.forEach(addRelativePath);
|
|
26
|
+
} else {
|
|
27
|
+
addRelativePath(transformedData);
|
|
20
28
|
}
|
|
21
29
|
}
|
|
22
30
|
return transformedData;
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function isInternalLink(url, wordpressUrl) {
|
|
2
|
+
if (!url || typeof url !== "string") {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
const trimmed = url.trim();
|
|
6
|
+
if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
if (/^(?!https?:\/\/)/i.test(trimmed) && !trimmed.startsWith("//")) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (!wordpressUrl || typeof wordpressUrl !== "string") {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const fullUrl = trimmed.startsWith("//") ? `https:${trimmed}` : trimmed;
|
|
17
|
+
const parsedUrl = new URL(fullUrl);
|
|
18
|
+
const parsedWp = new URL(wordpressUrl);
|
|
19
|
+
return parsedUrl.hostname === parsedWp.hostname;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function toRelativePath(url) {
|
|
25
|
+
if (!url || typeof url !== "string") {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
const trimmed = url.trim();
|
|
29
|
+
if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
|
|
30
|
+
return trimmed;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const fullUrl = trimmed.startsWith("//") ? `https:${trimmed}` : trimmed;
|
|
34
|
+
const parsed = new URL(fullUrl);
|
|
35
|
+
return parsed.pathname + parsed.search + parsed.hash;
|
|
36
|
+
} catch {
|
|
37
|
+
return trimmed;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export { isInternalLink, toRelativePath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wpnuxt/core",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
4
|
"description": "Nuxt module for WordPress integration via GraphQL (WPGraphQL)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
@@ -23,15 +23,18 @@
|
|
|
23
23
|
"exports": {
|
|
24
24
|
".": {
|
|
25
25
|
"types": "./dist/types.d.mts",
|
|
26
|
-
"import": "./dist/module.mjs"
|
|
26
|
+
"import": "./dist/module.mjs",
|
|
27
|
+
"default": "./dist/module.mjs"
|
|
27
28
|
},
|
|
28
29
|
"./server-options": {
|
|
29
30
|
"types": "./dist/server-options.d.mts",
|
|
30
|
-
"import": "./dist/server-options.mjs"
|
|
31
|
+
"import": "./dist/server-options.mjs",
|
|
32
|
+
"default": "./dist/server-options.mjs"
|
|
31
33
|
},
|
|
32
34
|
"./client-options": {
|
|
33
35
|
"types": "./dist/client-options.d.mts",
|
|
34
|
-
"import": "./dist/client-options.mjs"
|
|
36
|
+
"import": "./dist/client-options.mjs",
|
|
37
|
+
"default": "./dist/client-options.mjs"
|
|
35
38
|
}
|
|
36
39
|
},
|
|
37
40
|
"main": "./dist/module.mjs",
|
|
@@ -43,21 +46,21 @@
|
|
|
43
46
|
"access": "public"
|
|
44
47
|
},
|
|
45
48
|
"dependencies": {
|
|
46
|
-
"@nuxt/kit": "4.3.
|
|
47
|
-
"@radya/nuxt-dompurify": "^1.0.5",
|
|
49
|
+
"@nuxt/kit": "4.3.1",
|
|
48
50
|
"consola": "^3.4.0",
|
|
49
51
|
"defu": "^6.1.4",
|
|
52
|
+
"dompurify": "^3.1.7",
|
|
50
53
|
"graphql": "^16.12.0",
|
|
51
54
|
"nuxt-graphql-middleware": "5.3.2",
|
|
52
55
|
"scule": "^1.3.0"
|
|
53
56
|
},
|
|
54
57
|
"devDependencies": {
|
|
55
|
-
"@nuxt/devtools": "^3.
|
|
58
|
+
"@nuxt/devtools": "^3.2.0",
|
|
56
59
|
"@nuxt/module-builder": "^1.0.2",
|
|
57
|
-
"@nuxt/schema": "4.3.
|
|
58
|
-
"@nuxt/test-utils": "^
|
|
59
|
-
"@types/node": "^25.2.
|
|
60
|
-
"nuxt": "4.3.
|
|
60
|
+
"@nuxt/schema": "4.3.1",
|
|
61
|
+
"@nuxt/test-utils": "^4.0.0",
|
|
62
|
+
"@types/node": "^25.2.3",
|
|
63
|
+
"nuxt": "4.3.1",
|
|
61
64
|
"vitest": "^4.0.18",
|
|
62
65
|
"vue-tsc": "^3.2.3"
|
|
63
66
|
},
|