1
1
import { createApp } from 'vue'
2
- import type { Storage } from 'unstorage'
3
2
import type { ParsedContent } from '@nuxt/content/dist/runtime/types'
4
- import { createDefu } from 'defu'
5
3
import type { RouteLocationNormalized } from 'vue-router'
6
4
import type { AppConfig } from 'nuxt/schema'
7
5
import ContentPreviewMode from '../components/ContentPreviewMode.vue'
8
- import { createSingleton , deepAssign , deepDelete , mergeDraft , StudioConfigFiles } from '../utils'
6
+ import { createSingleton , deepAssign , deepDelete , defu , mergeDraft , StudioConfigFiles } from '../utils'
9
7
import type { PreviewFile , PreviewResponse , FileChangeMessagePayload } from '../types'
8
+ import { useContentStorage } from './useContentStorage'
10
9
import { callWithNuxt } from '#app'
11
- import { useAppConfig , useNuxtApp , useRuntimeConfig , useState , useContentState , queryContent , ref , toRaw , useRoute , useRouter } from '#imports'
10
+ import { useAppConfig , useNuxtApp , useRuntimeConfig , useContentState , ref , toRaw , useRoute , useRouter } from '#imports'
12
11
13
12
const useDefaultAppConfig = createSingleton ( ( ) => JSON . parse ( JSON . stringify ( ( useAppConfig ( ) ) ) ) )
14
13
15
- const defu = createDefu ( ( obj , key , value ) => {
16
- if ( Array . isArray ( obj [ key ] ) && Array . isArray ( value ) ) {
17
- obj [ key ] = value
18
- return true
19
- }
20
- } )
21
-
22
14
let dbFiles : PreviewFile [ ] = [ ]
23
15
24
16
export const useStudio = ( ) => {
25
17
const nuxtApp = useNuxtApp ( )
18
+ const { storage, findContentItem, updateContentItem, removeContentItem, removeAllContentItems, setPreviewMetaItems } = useContentStorage ( )
26
19
const { studio : studioConfig , content : contentConfig } = useRuntimeConfig ( ) . public
27
- const contentPathMap = { } as Record < string , ParsedContent >
28
20
const apiURL = window . sessionStorage . getItem ( 'previewAPI' ) || studioConfig ?. apiURL
29
21
30
22
// App config (required)
31
23
const initialAppConfig = useDefaultAppConfig ( )
32
- const storage = useState < Storage | null > ( 'studio-client-db' , ( ) => null )
33
-
34
- if ( ! storage . value ) {
35
- nuxtApp . hook ( 'content:storage' , ( _storage : Storage ) => {
36
- storage . value = _storage
37
- } )
38
24
39
- // Call `queryContent` to trigger `content:storage` hook
40
- queryContent ( '/non-existing-path' ) . findOne ( )
41
- }
25
+ const syncPreviewFiles = async ( files : PreviewFile [ ] ) => {
26
+ const previewToken = window . sessionStorage . getItem ( 'previewToken' ) as string
42
27
43
- const syncPreviewFiles = async ( contentStorage : Storage , files : PreviewFile [ ] ) => {
44
- const previewToken = window . sessionStorage . getItem ( 'previewToken' )
45
28
// Remove previous preview data
46
- const keys : string [ ] = await contentStorage . getKeys ( `${ previewToken } :` )
47
- await Promise . all ( keys . map ( key => contentStorage . removeItem ( key ) ) )
29
+ removeAllContentItems ( previewToken )
48
30
49
31
// Set preview meta
50
- const sources = new Set < string > ( files . map ( file => file . parsed ! . _id . split ( ':' ) . shift ( ) ! ) )
51
- await contentStorage . setItem ( `${ previewToken } $` , JSON . stringify ( { ignoreSources : Array . from ( sources ) } ) )
32
+ setPreviewMetaItems ( previewToken , files )
52
33
53
34
// Handle content files
54
35
await Promise . all (
55
- files . map ( ( item ) => {
56
- contentPathMap [ item . parsed ! . _path ! ] = item . parsed !
57
- return contentStorage . setItem ( `${ previewToken } :${ item . parsed ! . _id } ` , JSON . stringify ( item . parsed ) )
36
+ files . map ( ( file ) => {
37
+ updateContentItem ( previewToken , file )
58
38
} ) ,
59
39
)
60
40
}
@@ -94,7 +74,7 @@ export const useStudio = () => {
94
74
95
75
// Handle content files
96
76
const contentFiles = mergedFiles . filter ( item => ! ( [ StudioConfigFiles . appConfig , StudioConfigFiles . nuxtConfig ] . includes ( item . path ) ) )
97
- await syncPreviewFiles ( storage . value , contentFiles )
77
+ await syncPreviewFiles ( contentFiles )
98
78
99
79
const appConfig = mergedFiles . find ( item => item . path === StudioConfigFiles . appConfig )
100
80
syncPreviewAppConfig ( appConfig ?. parsed as ParsedContent )
@@ -130,58 +110,12 @@ export const useStudio = () => {
130
110
} ) . mount ( el )
131
111
}
132
112
133
- // Content Helpers
134
- const findContentWithId = async ( path : string ) : Promise < ParsedContent | null > => {
135
- const previewToken = window . sessionStorage . getItem ( 'previewToken' )
136
- if ( ! path ) {
137
- return null
138
- }
139
- path = path . replace ( / \/ $ / , '' )
140
- let content = await storage . value ?. getItem ( `${ previewToken } :${ path } ` )
141
- if ( ! content ) {
142
- content = await storage . value ?. getItem ( `cached:${ path } ` )
143
- }
144
- if ( ! content ) {
145
- content = content = await storage . value ?. getItem ( path )
146
- }
147
-
148
- // try finding content from contentPathMap
149
- if ( ! content ) {
150
- content = contentPathMap [ path || '/' ]
151
- }
152
-
153
- return content as ParsedContent
154
- }
155
-
156
- const updateContent = ( content : PreviewFile ) => {
157
- const previewToken = window . sessionStorage . getItem ( 'previewToken' )
158
- if ( ! storage . value ) return
159
-
160
- contentPathMap [ content . parsed ! . _path ! ] = content . parsed !
161
- storage . value . setItem ( `${ previewToken } :${ content . parsed ?. _id } ` , JSON . stringify ( content . parsed ) )
162
- }
163
-
164
- const removeContentWithId = async ( path : string ) => {
165
- const previewToken = window . sessionStorage . getItem ( 'previewToken' )
166
- const content = await findContentWithId ( path )
167
- await storage . value ?. removeItem ( `${ previewToken } :${ path } ` )
168
-
169
- if ( content ) {
170
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
171
- delete contentPathMap [ content . _path ! ]
172
- const nonDraftContent = await findContentWithId ( content . _id )
173
- if ( nonDraftContent ) {
174
- contentPathMap [ nonDraftContent . _path ! ] = nonDraftContent
175
- }
176
- }
177
- }
178
-
179
113
const requestRerender = async ( ) => {
180
114
if ( contentConfig ?. documentDriven ) {
181
115
const { pages } = callWithNuxt ( nuxtApp , useContentState )
182
116
183
117
const contents = await Promise . all ( Object . keys ( pages . value ) . map ( async ( key ) => {
184
- return await findContentWithId ( pages . value [ key ] ?. _id ?? key )
118
+ return await findContentItem ( pages . value [ key ] ?. _id ?? key )
185
119
} ) )
186
120
187
121
pages . value = contents . reduce ( ( acc , item , index ) => {
@@ -196,19 +130,6 @@ export const useStudio = () => {
196
130
}
197
131
198
132
return {
199
- apiURL,
200
- contentStorage : storage ,
201
-
202
- syncPreviewFiles,
203
- syncPreviewAppConfig,
204
- requestPreviewSynchronization,
205
-
206
- findContentWithId,
207
- updateContent,
208
- removeContentWithId,
209
-
210
- requestRerender,
211
-
212
133
mountPreviewUI,
213
134
initiateIframeCommunication,
214
135
}
@@ -258,7 +179,7 @@ export const useStudio = () => {
258
179
259
180
switch ( type ) {
260
181
case 'nuxt-studio:editor:file-selected' : {
261
- const content = await findContentWithId ( payload . path )
182
+ const content = await findContentItem ( payload . path )
262
183
if ( ! content ) {
263
184
// Do not navigate to another page if content is not found
264
185
// This makes sure that user stays on the same page when navigation through directories in the editor
@@ -273,21 +194,19 @@ export const useStudio = () => {
273
194
}
274
195
break
275
196
}
197
+ case 'nuxt-studio:editor:media-changed' :
276
198
case 'nuxt-studio:editor:file-changed' : {
199
+ const previewToken = window . sessionStorage . getItem ( 'previewToken' ) as string
277
200
const { additions = [ ] , deletions = [ ] } = payload as FileChangeMessagePayload
278
201
for ( const addition of additions ) {
279
- await updateContent ( addition )
202
+ await updateContentItem ( previewToken , addition )
280
203
}
281
204
for ( const deletion of deletions ) {
282
- await removeContentWithId ( deletion . path )
205
+ await removeContentItem ( previewToken , deletion . path )
283
206
}
284
207
requestRerender ( )
285
208
break
286
209
}
287
- case 'nuxt-studio:preview:sync' : {
288
- syncPreview ( payload )
289
- break
290
- }
291
210
case 'nuxt-studio:config:file-changed' : {
292
211
const { additions = [ ] , deletions = [ ] } = payload as FileChangeMessagePayload
293
212
0 commit comments