@dyrected/nuxt 2.3.3 → 2.3.4

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/dist/module.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@dyrected/nuxt",
3
3
  "configKey": "dyrected",
4
- "version": "2.3.2"
4
+ "version": "2.3.4"
5
5
  }
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addServerHandler, addComponent, addImports, addServerPlugin } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addServerHandler, addPlugin, addComponent, addImports, addServerPlugin } from '@nuxt/kit';
2
2
  import { join } from 'path';
3
3
  import { existsSync } from 'fs';
4
4
 
@@ -18,6 +18,7 @@ const module = defineNuxtModule({
18
18
  handler: resolver.resolve("./runtime/server/handler")
19
19
  });
20
20
  }
21
+ addPlugin(resolver.resolve("./runtime/plugin"));
21
22
  addComponent({
22
23
  name: "DyrectedMedia",
23
24
  filePath: resolver.resolve("./runtime/components/DyrectedMedia.vue")
@@ -28,6 +29,10 @@ const module = defineNuxtModule({
28
29
  });
29
30
  addImports([
30
31
  { name: "useDyrected", from: resolver.resolve("./runtime/composables/useDyrected") },
32
+ { name: "useDyrectedClient", from: resolver.resolve("./runtime/composables/useDyrected") },
33
+ { name: "useDyrectedData", from: resolver.resolve("./runtime/composables/useDyrected") },
34
+ { name: "useDyrectedCollectionData", from: resolver.resolve("./runtime/composables/useDyrected") },
35
+ { name: "useDyrectedGlobalData", from: resolver.resolve("./runtime/composables/useDyrected") },
31
36
  { name: "useDyrectedDoc", from: resolver.resolve("./runtime/composables/useDyrected") },
32
37
  { name: "useDyrectedCollection", from: resolver.resolve("./runtime/composables/useDyrected") },
33
38
  { name: "useDyrectedGlobal", from: resolver.resolve("./runtime/composables/useDyrected") },
@@ -82,6 +87,9 @@ const module = defineNuxtModule({
82
87
  if (!nuxt.options.vite.optimizeDeps.exclude.includes("@dyrected/admin")) {
83
88
  nuxt.options.vite.optimizeDeps.exclude.push("@dyrected/admin");
84
89
  }
90
+ if (!nuxt.options.vite.optimizeDeps.exclude.includes("@dyrected/vue")) {
91
+ nuxt.options.vite.optimizeDeps.exclude.push("@dyrected/vue");
92
+ }
85
93
  }
86
94
  nuxt.hook("vite:extendConfig", (config) => {
87
95
  const plugins = config.plugins ?? [];
@@ -1,14 +1,16 @@
1
1
  <template>
2
- <div ref="container" class="dyrected-admin-wrapper"></div>
2
+ <DyrectedAdmin
3
+ :config="config"
4
+ :basename="basename || '/cms-admin'"
5
+ />
3
6
  </template>
4
7
 
5
8
  <script setup lang="ts">
6
- import { ref, onMounted, onUnmounted, nextTick } from "vue";
7
- import "@dyrected/admin/styles";
8
9
  // @ts-ignore
9
10
  import { useRuntimeConfig } from "#imports";
11
+ import { DyrectedAdmin } from "@dyrected/vue";
10
12
 
11
- const props = defineProps<{
13
+ defineProps<{
12
14
  /**
13
15
  * The base path where the admin is mounted.
14
16
  * @default "/cms-admin"
@@ -16,41 +18,10 @@ const props = defineProps<{
16
18
  basename?: string;
17
19
  }>();
18
20
 
19
- const config = useRuntimeConfig();
20
- const container = ref<HTMLElement | null>(null);
21
- let unmount: (() => void) | null = null;
22
-
23
- onMounted(async () => {
24
- await nextTick();
25
-
26
- if (container.value) {
27
- try {
28
- // 1. Dynamically import to ensure isolation from Vue's early bundle phase
29
- const { renderAdminUI } = await import("@dyrected/admin");
30
-
31
- // 2. Deep clone config to strip ALL Vue proxies/reactivity
32
- const dyrectedRaw = JSON.parse(JSON.stringify(config.public.dyrected));
33
-
34
- unmount = renderAdminUI(container.value, {
35
- apiKey: String(dyrectedRaw.apiKey || ""),
36
- siteId: String(dyrectedRaw.siteId || ""),
37
- baseUrl: String(dyrectedRaw.baseUrl || ""),
38
- basename: props.basename || "/cms-admin",
39
- isEmbedded: false,
40
- });
41
- } catch (err) {
42
- console.error("[DyrectedAdmin] Failed to mount admin UI:", err);
43
- }
44
- }
45
- });
46
-
47
- onUnmounted(() => {
48
- if (unmount) {
49
- unmount();
50
- }
51
- });
21
+ const runtimeConfig = useRuntimeConfig();
22
+ const config = {
23
+ apiKey: String(runtimeConfig.public.dyrected.apiKey || ""),
24
+ siteId: String(runtimeConfig.public.dyrected.siteId || ""),
25
+ baseUrl: String(runtimeConfig.public.dyrected.baseUrl || ""),
26
+ };
52
27
  </script>
53
-
54
- <style scoped>
55
- .dyrected-admin-wrapper{height:100vh;width:100vw}
56
- </style>
@@ -1,23 +1,9 @@
1
1
  <template>
2
- <img
3
- v-if="typeof media === 'string'"
4
- :src="media"
5
- :width="width || 500"
6
- :height="height || 500"
7
- :alt="alt || ''"
8
- v-bind="$attrs"
9
- />
10
- <img
11
- v-else
12
- :src="media.url"
13
- :width="width || media.width || 500"
14
- :height="height || media.height || 500"
15
- :alt="alt || media.filename"
16
- v-bind="$attrs"
17
- />
2
+ <BaseDyrectedImage v-bind="$props" />
18
3
  </template>
19
4
 
20
5
  <script setup lang="ts">
6
+ import { DyrectedImage as BaseDyrectedImage } from "@dyrected/vue";
21
7
  import type { Media } from '@dyrected/sdk';
22
8
 
23
9
  defineProps<{
@@ -1,68 +1,19 @@
1
1
  <template>
2
- <div v-if="youtubeId" class="dyrected-media-video" style="position: relative; padding-bottom: 56.25%; height: 0;">
3
- <iframe
4
- :src="`https://www.youtube.com/embed/${youtubeId}`"
5
- frameborder="0"
6
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
7
- allowfullscreen
8
- style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
9
- />
10
- </div>
11
-
12
- <img
13
- v-else-if="isImage"
14
- :src="url"
15
- :width="width || 500"
16
- :height="height || 500"
17
- :alt="alt || filename"
18
- v-bind="$attrs"
19
- />
20
-
21
- <video
22
- v-else-if="isVideo"
23
- :src="url"
24
- controls
25
- :width="width || 500"
26
- :height="height || 500"
27
- class="dyrected-media-video"
28
- v-bind="$attrs"
29
- />
30
-
31
- <div v-else class="dyrected-media-file">
32
- <slot name="fallback">
33
- <a :href="url" target="_blank" rel="noopener noreferrer" class="dyrected-file-link">
34
- Download {{ filename || 'File' }}
35
- </a>
36
- </slot>
37
- </div>
2
+ <BaseDyrectedMedia v-bind="$props">
3
+ <template #fallback>
4
+ <slot name="fallback" />
5
+ </template>
6
+ </BaseDyrectedMedia>
38
7
  </template>
39
8
 
40
9
  <script setup lang="ts">
41
- import { computed } from 'vue';
10
+ import { DyrectedMedia as BaseDyrectedMedia } from "@dyrected/vue";
42
11
  import type { Media } from '@dyrected/sdk';
43
12
 
44
- const props = defineProps<{
13
+ defineProps<{
45
14
  media: Media | string;
46
15
  width?: number | string;
47
16
  height?: number | string;
48
17
  alt?: string;
49
18
  }>();
50
-
51
- const url = computed(() => typeof props.media === 'string' ? props.media : props.media.url);
52
- const filename = computed(() => typeof props.media === 'string' ? '' : props.media.filename);
53
- const mimeType = computed(() => typeof props.media === 'string' ? null : props.media.mimeType);
54
-
55
- const youtubeId = computed(() => {
56
- const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
57
- const match = url.value.match(regExp);
58
- return (match && match[2].length === 11) ? match[2] : null;
59
- });
60
-
61
- const isImage = computed(() => {
62
- return mimeType.value?.startsWith('image/') || url.value.match(/\.(jpg|jpeg|png|gif|webp|avif|svg)$/i);
63
- });
64
-
65
- const isVideo = computed(() => {
66
- return mimeType.value?.startsWith('video/') || url.value.match(/\.(mp4|webm|ogg)$/i);
67
- });
68
19
  </script>
@@ -1,5 +1,10 @@
1
1
  import { type DyrectedClient, type BaseSchema } from '@dyrected/sdk';
2
+ import { useDyrected as useGenericDyrected, useDyrectedCollection as useGenericCollection, useDyrectedGlobal as useGenericGlobal } from '@dyrected/vue';
3
+ export declare const useDyrectedClient: <TSchema extends BaseSchema = any>() => DyrectedClient<TSchema>;
2
4
  export declare const useDyrected: <TSchema extends BaseSchema = any>() => DyrectedClient<TSchema>;
5
+ export declare const useDyrectedData: typeof useGenericDyrected;
6
+ export declare const useDyrectedCollectionData: typeof useGenericCollection;
7
+ export declare const useDyrectedGlobalData: typeof useGenericGlobal;
3
8
  export declare const useDyrectedDoc: <T = any, TSchema extends BaseSchema = any>(collection: keyof TSchema["collections"] | string, id: string, options?: {
4
9
  depth?: number;
5
10
  initialData?: T;
@@ -1,5 +1,10 @@
1
1
  import { useRuntimeConfig, useAsyncData, useRequestFetch } from "#app";
2
2
  import { createClient } from "@dyrected/sdk";
3
+ import {
4
+ useDyrected as useGenericDyrected,
5
+ useDyrectedCollection as useGenericCollection,
6
+ useDyrectedGlobal as useGenericGlobal
7
+ } from "@dyrected/vue";
3
8
  function getClient() {
4
9
  const config = useRuntimeConfig().public.dyrected;
5
10
  const fetcher = useRequestFetch();
@@ -10,9 +15,13 @@ function getClient() {
10
15
  fetch: fetcher
11
16
  });
12
17
  }
13
- export const useDyrected = () => {
18
+ export const useDyrectedClient = () => {
14
19
  return getClient();
15
20
  };
21
+ export const useDyrected = useDyrectedClient;
22
+ export const useDyrectedData = useGenericDyrected;
23
+ export const useDyrectedCollectionData = useGenericCollection;
24
+ export const useDyrectedGlobalData = useGenericGlobal;
16
25
  export const useDyrectedDoc = (collection, id, options) => {
17
26
  const client = getClient();
18
27
  return useAsyncData(
@@ -1,23 +1,12 @@
1
- export interface DyrectedAuthUser {
2
- id: string;
3
- email: string;
4
- [key: string]: any;
5
- }
6
1
  /**
7
- * useDyrectedAuth — composable for auth collections.
8
- *
9
- * Usage:
10
- * const { login, logout, user, isLoggedIn } = useDyrectedAuth('customers')
11
- *
12
- * The JWT is persisted in a cookie (`dyrected_token_<collection>`) so it
13
- * survives page reloads and is available server-side on the next request.
2
+ * useDyrectedAuth — Nuxt-specific wrapper around @dyrected/vue auth logic.
14
3
  */
15
4
  export declare function useDyrectedAuth(collection: string): {
16
- /** Reactive: the authenticated user, or null. */
17
- user: any;
18
- /** Reactive: true when a valid JWT cookie exists. */
19
- isLoggedIn: any;
20
- login: (email: string, password: string) => Promise<DyrectedAuthUser>;
5
+ user: import("vue").Ref<import("@dyrected/vue").DyrectedAuthUser | null, import("@dyrected/vue").DyrectedAuthUser | null>;
6
+ isLoggedIn: import("vue").ComputedRef<boolean>;
7
+ token: import("vue").Ref<string | null, string | null>;
8
+ login: (email: string, password: string) => Promise<import("@dyrected/vue").DyrectedAuthUser>;
21
9
  logout: () => Promise<void>;
22
- fetchMe: () => Promise<DyrectedAuthUser | null>;
10
+ fetchMe: () => Promise<import("@dyrected/vue").DyrectedAuthUser | null>;
11
+ client: import("@dyrected/sdk").DyrectedClient<any>;
23
12
  };
@@ -1,65 +1,26 @@
1
- import { useRuntimeConfig, useState, useCookie, computed } from "#app";
2
- import { createClient } from "@dyrected/sdk";
1
+ import { useRuntimeConfig, useCookie } from "#app";
2
+ import { useDyrectedAuth as useGenericAuth } from "@dyrected/vue";
3
3
  export function useDyrectedAuth(collection) {
4
4
  const config = useRuntimeConfig().public.dyrected;
5
- const cookieName = `dyrected_token_${collection}`;
6
- const tokenCookie = useCookie(cookieName, {
5
+ const tokenCookie = useCookie(`dyrected_token_${collection}`, {
7
6
  default: () => null,
8
7
  maxAge: 60 * 60 * 24 * 7,
9
8
  // 7 days
10
9
  sameSite: "lax"
11
10
  });
12
- const user = useState(`dyrected:auth:user:${collection}`, () => null);
13
- const isLoggedIn = computed(() => !!tokenCookie.value);
14
- function buildClient() {
15
- const client = createClient({
16
- baseUrl: config.baseUrl,
17
- apiKey: config.apiKey,
18
- siteId: config.siteId
19
- });
20
- if (tokenCookie.value) {
21
- client.setToken(tokenCookie.value);
22
- }
23
- return client;
24
- }
25
- async function login(email, password) {
26
- const client = buildClient();
27
- const { token, user: userData } = await client.collection(collection).login(email, password);
28
- tokenCookie.value = token;
29
- user.value = userData;
30
- return userData;
31
- }
32
- async function logout() {
33
- if (tokenCookie.value) {
34
- try {
35
- const client = buildClient();
36
- await client.collection(collection).logout();
37
- } catch {
38
- }
39
- }
40
- tokenCookie.value = null;
41
- user.value = null;
42
- }
43
- async function fetchMe() {
44
- if (!tokenCookie.value) return null;
45
- try {
46
- const client = buildClient();
47
- const me = await client.collection(collection).me();
48
- user.value = me;
49
- return me;
50
- } catch {
11
+ const storage = {
12
+ getItem: () => tokenCookie.value || null,
13
+ setItem: (key, value) => {
14
+ tokenCookie.value = value;
15
+ },
16
+ removeItem: () => {
51
17
  tokenCookie.value = null;
52
- user.value = null;
53
- return null;
54
18
  }
55
- }
56
- return {
57
- /** Reactive: the authenticated user, or null. */
58
- user,
59
- /** Reactive: true when a valid JWT cookie exists. */
60
- isLoggedIn,
61
- login,
62
- logout,
63
- fetchMe
64
19
  };
20
+ return useGenericAuth(collection, {
21
+ baseUrl: config.baseUrl,
22
+ apiKey: config.apiKey,
23
+ siteId: config.siteId,
24
+ storage
25
+ });
65
26
  }
@@ -1,35 +1 @@
1
- import { type Ref } from 'vue';
2
- export interface LivePreviewOptions<T> {
3
- /**
4
- * The initial data to show before any postMessage arrives.
5
- * Typically the server-fetched document passed from useAsyncData.
6
- */
7
- initialData: T;
8
- /**
9
- * The origin of the Dyrected Admin UI that will send preview messages.
10
- * Defaults to '*' (accepts from any origin).
11
- * Set this to your Admin URL in production for security.
12
- */
13
- serverURL?: string;
14
- /**
15
- * The depth at which to resolve relationships in the preview data.
16
- */
17
- depth?: number;
18
- }
19
- /**
20
- * useLivePreview — Vue composable for Dyrected live preview (postMessage mode).
21
- *
22
- * The Admin UI sends postMessage events with draft document data when the
23
- * editor changes. This composable listens for those messages and reactively
24
- * updates `data` so the preview page re-renders in real time.
25
- *
26
- * Usage:
27
- * const { data, isLive } = useLivePreview({ initialData: serverData.value })
28
- *
29
- * The parent page/component must be rendered inside an iframe by the Admin UI.
30
- * Set `admin.previewUrl` in your collection config to point to that page.
31
- */
32
- export declare function useLivePreview<T = any>(options: LivePreviewOptions<T>): {
33
- data: Ref<T>;
34
- isLive: Ref<boolean>;
35
- };
1
+ export { useLivePreview, type LivePreviewOptions } from "@dyrected/vue";
@@ -1,33 +1 @@
1
- import { ref, onMounted, onUnmounted } from "vue";
2
- export function useLivePreview(options) {
3
- const data = ref(options.initialData);
4
- const isLive = ref(false);
5
- function handleMessage(event) {
6
- const allowedOrigin = options.serverURL || "*";
7
- if (allowedOrigin !== "*" && event.origin !== allowedOrigin) return;
8
- const { type, data: payload } = event.data || {};
9
- if (type === "dyrected-live-preview") {
10
- data.value = payload;
11
- isLive.value = true;
12
- }
13
- if (type === "dyrected-live-preview-ready") {
14
- window.parent.postMessage({ type: "dyrected-live-preview-ack" }, allowedOrigin);
15
- }
16
- }
17
- onMounted(() => {
18
- window.addEventListener("message", handleMessage);
19
- window.parent.postMessage({ type: "dyrected-live-preview-ready" }, options.serverURL || "*");
20
- });
21
- onUnmounted(() => {
22
- window.removeEventListener("message", handleMessage);
23
- });
24
- return {
25
- /** Reactively updated document data from the Admin UI editor. */
26
- data,
27
- /**
28
- * True once the first postMessage from the Admin has been received.
29
- * Useful to show a "Live preview active" indicator.
30
- */
31
- isLive
32
- };
33
- }
1
+ export { useLivePreview } from "@dyrected/vue";
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig, useRequestFetch } from "#app";
2
+ import { createClient } from "@dyrected/sdk";
3
+ import { DYRECTED_CLIENT_KEY } from "@dyrected/vue";
4
+ export default defineNuxtPlugin((nuxtApp) => {
5
+ const config = useRuntimeConfig().public.dyrected;
6
+ const fetcher = useRequestFetch();
7
+ const client = createClient({
8
+ baseUrl: config.baseUrl,
9
+ apiKey: config.apiKey,
10
+ siteId: config.siteId,
11
+ fetch: fetcher
12
+ });
13
+ nuxtApp.vueApp.provide(DYRECTED_CLIENT_KEY, client);
14
+ return {
15
+ provide: {
16
+ dyrected: client
17
+ }
18
+ };
19
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/nuxt",
3
- "version": "2.3.3",
3
+ "version": "2.3.4",
4
4
  "type": "module",
5
5
  "main": "./dist/module.mjs",
6
6
  "types": "./dist/module.d.ts",
@@ -10,9 +10,10 @@
10
10
  "dependencies": {
11
11
  "@nuxt/kit": "^3.11.2",
12
12
  "h3": "^1.15.0",
13
- "@dyrected/admin": "2.4.2",
14
- "@dyrected/core": "2.4.1",
15
- "@dyrected/sdk": "2.4.1"
13
+ "@dyrected/admin": "2.5.0",
14
+ "@dyrected/core": "2.5.0",
15
+ "@dyrected/sdk": "2.4.2",
16
+ "@dyrected/vue": "2.5.0"
16
17
  },
17
18
  "peerDependencies": {
18
19
  "nuxt": "^3.0.0",
@@ -27,7 +28,7 @@
27
28
  "vue": "^3.5.34"
28
29
  },
29
30
  "license": "BSL-1.1",
30
- "author": "Ajola Technologies Ltd",
31
+ "author": "Busola Okeowo <busolaokemoney@gmail.com>",
31
32
  "homepage": "https://dyrected.com",
32
33
  "repository": {
33
34
  "type": "git",