@wpnuxt/blocks 2.0.0-alpha.1 → 2.0.0-alpha.11

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 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
@@ -17,6 +17,11 @@ interface WPNuxtBlocksConfig {
17
17
  * @default 'auto' - uses Nuxt UI if available
18
18
  */
19
19
  nuxtUI?: boolean | 'auto';
20
+ /**
21
+ * Skip the WPGraphQL Content Blocks plugin check
22
+ * @default false
23
+ */
24
+ skipPluginCheck?: boolean;
20
25
  }
21
26
  declare const _default: _nuxt_schema.NuxtModule<WPNuxtBlocksConfig, WPNuxtBlocksConfig, false>;
22
27
 
@@ -0,0 +1,29 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ interface WPNuxtBlocksConfig {
4
+ /**
5
+ * Enable or disable the module
6
+ * @default true
7
+ */
8
+ enabled?: boolean;
9
+ /**
10
+ * Domains to allow for @nuxt/image
11
+ * @default []
12
+ */
13
+ imageDomains?: string[];
14
+ /**
15
+ * Enable Nuxt UI integration for enhanced button styling
16
+ * Set to false to use native HTML elements
17
+ * @default 'auto' - uses Nuxt UI if available
18
+ */
19
+ nuxtUI?: boolean | 'auto';
20
+ /**
21
+ * Skip the WPGraphQL Content Blocks plugin check
22
+ * @default false
23
+ */
24
+ skipPluginCheck?: boolean;
25
+ }
26
+ declare const _default: _nuxt_schema.NuxtModule<WPNuxtBlocksConfig, WPNuxtBlocksConfig, false>;
27
+
28
+ export { _default as default };
29
+ export type { WPNuxtBlocksConfig };
package/dist/module.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@wpnuxt/blocks",
3
3
  "configKey": "wpNuxtBlocks",
4
- "version": "2.0.0-alpha.0",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "2.0.0-alpha.11",
5
8
  "builder": {
6
9
  "@nuxt/module-builder": "1.0.2",
7
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,16 +1,21 @@
1
1
  import { existsSync, cpSync, promises } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { defineNuxtModule, createResolver, hasNuxtModule, installModule, addComponentsDir, addTemplate } from '@nuxt/kit';
3
+ import { useLogger, defineNuxtModule, createResolver, hasNuxtModule, installModule, addComponentsDir, addTemplate } from '@nuxt/kit';
4
4
 
5
+ const logger = useLogger("@wpnuxt/blocks");
5
6
  const module$1 = defineNuxtModule({
6
7
  meta: {
7
8
  name: "@wpnuxt/blocks",
8
- configKey: "wpNuxtBlocks"
9
+ configKey: "wpNuxtBlocks",
10
+ compatibility: {
11
+ nuxt: ">=3.0.0"
12
+ }
9
13
  },
10
14
  defaults: {
11
15
  enabled: true,
12
16
  imageDomains: [],
13
- nuxtUI: "auto"
17
+ nuxtUI: "auto",
18
+ skipPluginCheck: false
14
19
  },
15
20
  async setup(options, nuxt) {
16
21
  if (!options.enabled) {
@@ -26,6 +31,42 @@ const module$1 = defineNuxtModule({
26
31
  domains: options.imageDomains
27
32
  });
28
33
  await installModule("@radya/nuxt-dompurify");
34
+ if (!options.skipPluginCheck) {
35
+ const wpNuxtConfig = nuxt.options.runtimeConfig?.public?.wpNuxt;
36
+ const wordpressUrl = wpNuxtConfig?.wordpressUrl || process.env.WPNUXT_WORDPRESS_URL;
37
+ const graphqlEndpoint = wpNuxtConfig?.graphqlEndpoint || process.env.WPNUXT_GRAPHQL_ENDPOINT || "/graphql";
38
+ if (wordpressUrl) {
39
+ const graphqlUrl = `${wordpressUrl}${graphqlEndpoint}`;
40
+ const introspectionQuery = `
41
+ query CheckEditorBlocks {
42
+ __type(name: "Post") {
43
+ fields {
44
+ name
45
+ }
46
+ }
47
+ }
48
+ `;
49
+ try {
50
+ const response = await fetch(graphqlUrl, {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify({ query: introspectionQuery })
54
+ });
55
+ if (response.ok) {
56
+ const data = await response.json();
57
+ const fields = data?.data?.__type?.fields || [];
58
+ const hasEditorBlocks = fields.some((f) => f.name === "editorBlocks");
59
+ if (!hasEditorBlocks) {
60
+ logger.warn("WPGraphQL Content Blocks plugin not detected on WordPress.");
61
+ logger.warn("Install it from: https://github.com/wpengine/wp-graphql-content-blocks");
62
+ logger.warn("Without this plugin, BlockRenderer will not work correctly.");
63
+ }
64
+ }
65
+ } catch (error) {
66
+ logger.debug("Could not check for WPGraphQL Content Blocks plugin:", error);
67
+ }
68
+ }
69
+ }
29
70
  const blocksQueriesPath = resolveRuntimeModule("./queries");
30
71
  const mergedQueriesFolder = join(nuxt.options.srcDir, ".queries");
31
72
  if (existsSync(mergedQueriesFolder) && existsSync(blocksQueriesPath)) {
@@ -1,22 +1,45 @@
1
1
  <script setup>
2
2
  import { pascalCase } from "scule";
3
- import { resolveComponent } from "#imports";
3
+ import { computed, resolveComponent, getCurrentInstance } from "vue";
4
4
  const props = defineProps({
5
5
  block: { type: null, required: true }
6
6
  });
7
- const componentToRender = (() => {
7
+ const registrationCache = /* @__PURE__ */ new Map();
8
+ const instance = getCurrentInstance();
9
+ function isComponentRegistered(name) {
10
+ if (registrationCache.has(name)) {
11
+ return registrationCache.get(name);
12
+ }
13
+ if (!instance) {
14
+ registrationCache.set(name, false);
15
+ return false;
16
+ }
17
+ const appComponents = instance.appContext.components;
18
+ const isGlobal = name in appComponents;
19
+ const localComponents = instance.type.components;
20
+ const isLocal = !!(localComponents && name in localComponents);
21
+ const result = isGlobal || isLocal;
22
+ registrationCache.set(name, result);
23
+ return result;
24
+ }
25
+ const hot = import.meta.hot;
26
+ if (hot) {
27
+ hot.on("vite:beforeUpdate", () => {
28
+ registrationCache.clear();
29
+ });
30
+ }
31
+ const componentToRender = computed(() => {
8
32
  if (props.block.parentClientId === null || props.block.parentClientId === void 0) {
9
33
  if (props.block.name) {
10
34
  const componentName = pascalCase(props.block.name);
11
- const resolved = resolveComponent(componentName);
12
- if (typeof resolved !== "string") {
13
- return resolved;
35
+ if (isComponentRegistered(componentName)) {
36
+ return resolveComponent(componentName);
14
37
  }
15
38
  }
16
39
  return resolveComponent("EditorBlock");
17
40
  }
18
41
  return void 0;
19
- })();
42
+ });
20
43
  </script>
21
44
 
22
45
  <template>
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { convertFontSize, getCssClasses } from "../../util";
3
- import { ref, useNuxtApp } from "#imports";
3
+ import { computed, ref, useNuxtApp, useRuntimeConfig, isInternalLink, toRelativePath } from "#imports";
4
4
  const props = defineProps({
5
5
  block: { type: null, required: true }
6
6
  });
@@ -13,13 +13,24 @@ if (props.block.attributes?.metadata) {
13
13
  }
14
14
  const nuxtApp = useNuxtApp();
15
15
  const hasNuxtUI = !!nuxtApp.vueApp.component("UButton");
16
+ const config = useRuntimeConfig();
17
+ const wordpressUrl = config.public.wpNuxt?.wordpressUrl;
18
+ const isInternal = computed(() => {
19
+ const url = props.block.attributes?.url;
20
+ return !!url && isInternalLink(url, wordpressUrl);
21
+ });
22
+ const buttonUrl = computed(() => {
23
+ const url = props.block.attributes?.url;
24
+ if (!url) return void 0;
25
+ return isInternal.value ? toRelativePath(url) : url;
26
+ });
16
27
  </script>
17
28
 
18
29
  <template>
19
30
  <!-- Use Nuxt UI UButton when available -->
20
31
  <UButton
21
32
  v-if="hasNuxtUI"
22
- :to="block.attributes.url ?? void 0"
33
+ :to="buttonUrl"
23
34
  :target="block.attributes.linkTarget"
24
35
  :rel="block.attributes.rel"
25
36
  :style="block.attributes.style"
@@ -30,7 +41,17 @@ const hasNuxtUI = !!nuxtApp.vueApp.component("UButton");
30
41
  <span v-sanitize-html="block.attributes.text" />
31
42
  </UButton>
32
43
 
33
- <!-- Fallback to native link/button when Nuxt UI is not available -->
44
+ <!-- Fallback: use NuxtLink for internal, <a> for external -->
45
+ <NuxtLink
46
+ v-else-if="block.attributes.url && isInternal"
47
+ :to="buttonUrl"
48
+ :target="block.attributes.linkTarget ?? void 0"
49
+ :rel="block.attributes.rel ?? void 0"
50
+ :style="block.attributes.style"
51
+ :class="['wp-block-button__link', getCssClasses(block)]"
52
+ >
53
+ <span v-sanitize-html="block.attributes.text" />
54
+ </NuxtLink>
34
55
  <a
35
56
  v-else-if="block.attributes.url"
36
57
  :href="block.attributes.url"
@@ -1,13 +1,13 @@
1
1
  <script setup>
2
+ import { computed } from "vue";
2
3
  const props = defineProps({
3
4
  block: { type: null, required: true }
4
5
  });
5
- const imageBlocks = [];
6
- props.block?.innerBlocks?.forEach((innerBlock) => {
7
- if (innerBlock && innerBlock.name === "core/image") {
8
- const imgBlock = innerBlock;
9
- imageBlocks.push(imgBlock);
10
- }
6
+ const imageBlocks = computed(() => {
7
+ if (!props.block?.innerBlocks) return [];
8
+ return props.block.innerBlocks.filter(
9
+ (block) => block !== null && block.__typename === "CoreImage"
10
+ );
11
11
  });
12
12
  </script>
13
13
 
@@ -0,0 +1,18 @@
1
+ #import "~/.queries/fragments/ContentNode.fragment.gql"
2
+ #import "~/.queries/fragments/NodeWithFeaturedImage.fragment.gql"
3
+ #import "~/.queries/fragments/NodeWithContentEditor.fragment.gql"
4
+ #import "~/.queries/fragments/NodeWithEditorBlocks.fragment.gql"
5
+
6
+ fragment Page on Page {
7
+ ...ContentNode
8
+ ...NodeWithFeaturedImage
9
+ ...NodeWithContentEditor
10
+ ...NodeWithEditorBlocks
11
+ isFrontPage
12
+ isPostsPage
13
+ isPreview
14
+ isPrivacyPage
15
+ isRestricted
16
+ isRevision
17
+ title
18
+ }
@@ -0,0 +1,22 @@
1
+ #import "~/.queries/fragments/NodeWithExcerpt.fragment.gql"
2
+ #import "~/.queries/fragments/ContentNode.fragment.gql"
3
+ #import "~/.queries/fragments/NodeWithFeaturedImage.fragment.gql"
4
+ #import "~/.queries/fragments/NodeWithContentEditor.fragment.gql"
5
+ #import "~/.queries/fragments/NodeWithEditorBlocks.fragment.gql"
6
+
7
+ fragment Post on Post {
8
+ ...NodeWithExcerpt
9
+ ...ContentNode
10
+ ...NodeWithFeaturedImage
11
+ ...NodeWithContentEditor
12
+ ...NodeWithEditorBlocks
13
+ title
14
+ categories {
15
+ nodes {
16
+ id
17
+ name
18
+ uri
19
+ description
20
+ }
21
+ }
22
+ }
@@ -1,9 +1,10 @@
1
1
  import { convertFontSize } from "./attributeFontSize.js";
2
2
  import { getColor } from "./attributeColor.js";
3
3
  const getCssClasses = function getCssClasses2(block) {
4
- const text = convertFontSize(block.attributes?.fontSize, "text-");
5
- const color = getColor(block.attributes?.textColor);
6
- const passedOn = block?.attributes?.className != null ? block?.attributes?.className + " " : " ";
4
+ const attrs = block.attributes;
5
+ const text = convertFontSize(attrs?.fontSize, "text-");
6
+ const color = getColor(attrs?.textColor);
7
+ const passedOn = attrs?.className != null ? attrs.className + " " : " ";
7
8
  return passedOn + text + " " + color;
8
9
  };
9
10
  export { getCssClasses, convertFontSize, getColor };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpnuxt/blocks",
3
- "version": "2.0.0-alpha.1",
3
+ "version": "2.0.0-alpha.11",
4
4
  "description": "Vue components for rendering WordPress Gutenberg blocks in Nuxt",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -33,21 +33,23 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@nuxt/kit": "4.2.2",
37
36
  "@nuxt/image": "^2.0.0",
37
+ "@nuxt/kit": "4.3.1",
38
38
  "@radya/nuxt-dompurify": "^1.0.5",
39
39
  "scule": "^1.3.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@nuxt/module-builder": "^1.0.2",
43
- "@nuxt/schema": "4.2.2",
44
- "@types/node": "^25.0.3",
45
- "nuxt": "4.2.2",
46
- "vue-tsc": "^3.2.1"
43
+ "@nuxt/schema": "4.3.1",
44
+ "@nuxt/test-utils": "^4.0.0",
45
+ "@types/node": "^25.2.2",
46
+ "nuxt": "4.3.1",
47
+ "vitest": "^4.0.18",
48
+ "vue-tsc": "^3.2.3"
47
49
  },
48
50
  "peerDependencies": {
49
- "nuxt": "^4.0.0",
50
- "@nuxt/ui": "^4.0.0"
51
+ "@nuxt/ui": "^4.0.0",
52
+ "nuxt": "^4.0.0"
51
53
  },
52
54
  "peerDependenciesMeta": {
53
55
  "@nuxt/ui": {
@@ -56,7 +58,9 @@
56
58
  },
57
59
  "scripts": {
58
60
  "build": "nuxt-module-build build",
59
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare",
61
+ "dev:prepare": "nuxt-module-build build --stub",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest watch",
60
64
  "typecheck": "vue-tsc --noEmit",
61
65
  "clean": "rm -rf dist .nuxt node_modules"
62
66
  }