Skip to content
Merged
9 changes: 9 additions & 0 deletions playground/docus/app/pages/test.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script setup lang="ts">
const { data } = await useAsyncData('pages', () => queryCollection('custom').first())
</script>

<template>
<div>
<ContentRenderer :value="data" />
</div>
</template>
38 changes: 38 additions & 0 deletions playground/docus/content.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { DefinedCollection } from '@nuxt/content'
import { defineContentConfig, defineCollection, z } from '@nuxt/content'

const createDocsSchema = () => z.object({
layout: z.string().optional(),
links: z.array(z.object({
label: z.string(),
icon: z.string(),
to: z.string(),
target: z.string().optional(),
})).optional(),
})

const collections: Record<string, DefinedCollection> = {
custom: defineCollection({
type: 'page',
source: {
include: '3.custom-case/**/*.md',
prefix: '/',
},
}),
landing: defineCollection({
type: 'page',
source: {
include: 'index.md',
},
}),
docs: defineCollection({
type: 'page',
source: {
include: '**',
exclude: ['index.md', '3.custom-case/**/*.md'],
},
schema: createDocsSchema(),
}),
}

export default defineContentConfig({ collections })
18 changes: 18 additions & 0 deletions playground/docus/content/3.custom-case/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Test
description: Test
navigation:
icon: i-lucide-test
---

This test file is corresponding to a custom case collection:

```[content.config.ts]
pages: defineCollection({
type: 'page',
source: {
include: '3.custom-case/**/*.md',
prefix: '/',
},
}),
```
9 changes: 4 additions & 5 deletions src/app/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,17 @@ watch(ui.sidebar.sidebarWidth, () => {
// Nuxt UI Portal element
const appPortal = ref<HTMLElement>()

const activeDocuments = ref<{ id: string, title: string }[]>([])
const activeDocuments = ref<{ fsPath: string, title: string }[]>([])
function detectActiveDocuments() {
activeDocuments.value = host.document.detectActives().map((content) => {
return {
id: content.id,
fsPath: content.fsPath,
title: content.title,
}
})
}

async function editContentFile(id: string) {
const fsPath = host.document.getFileSystemPath(id)
async function editContentFile(fsPath: string) {
await context.activeTree.value.selectItemByFsPath(fsPath)
ui.open()
}
Expand Down Expand Up @@ -140,7 +139,7 @@ router.beforeEach((to, from) => {
variant="outline"
class="bg-transparent backdrop-blur-md px-2"
label="Edit this page"
@click="editContentFile(activeDocuments[0].id)"
@click="editContentFile(activeDocuments[0].fsPath)"
/>
</UFieldGroup>
</div>
Expand Down
10 changes: 0 additions & 10 deletions src/app/src/components/content/ContentCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ const props = defineProps({
})

const itemExtensionIcon = computed(() => getFileIcon(props.item.fsPath))
const collectionName = computed(() => props.item.collections[0])
const isDirectory = computed(() => props.item.type === 'directory')
</script>

<template>
Expand Down Expand Up @@ -58,13 +56,5 @@ const isDirectory = computed(() => props.item.type === 'directory')
class="bg-elevated"
/>
</template>
<template #bottom-right>
<UBadge
v-if="!isDirectory"
:label="collectionName"
size="xs"
variant="soft"
/>
</template>
</ItemCard>
</template>
2 changes: 1 addition & 1 deletion src/app/src/components/content/ContentEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const document = computed<DatabasePageItem>({
return
}

context.activeTree.value.draft.update(props.draftItem.id, {
context.activeTree.value.draft.update(props.draftItem.fsPath, {
...toRaw(document.value as DatabasePageItem),
...toRaw(value),
})
Expand Down
2 changes: 1 addition & 1 deletion src/app/src/components/shared/item/ItemActionsDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const actions = computed<DropdownMenuItem[]>(() => {
const hasPendingAction = pendingAction.value !== null
const hasLoadingAction = loadingAction.value !== null

return computeItemActions(context.itemActions.value, props.item).map((action) => {
return computeItemActions(context.itemActions.value, props.item, context.currentFeature.value).map((action) => {
const isOneStepAction = oneStepActions.includes(action.id)
const isPending = pendingAction.value?.id === action.id
const isLoading = loadingAction.value?.id === action.id
Expand Down
2 changes: 1 addition & 1 deletion src/app/src/components/shared/item/ItemActionsToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const actions = computed(() => {
const hasPendingAction = pendingAction.value !== null
const hasLoadingAction = loadingAction.value !== null

return computeItemActions(context.itemActions.value, item.value).map((action) => {
return computeItemActions(context.itemActions.value, item.value, context.currentFeature.value).map((action) => {
const isOneStepAction = oneStepActions.includes(action.id)
const isPending = pendingAction.value?.id === action.id
const isLoading = loadingAction.value?.id === action.id
Expand Down
6 changes: 2 additions & 4 deletions src/app/src/components/shared/item/ItemCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ const statusRingColor = computed(() => props.item.status && props.item.status !=
const displayInfo = computed(() => {
if (isDirectory.value) {
const itemcount = props.item.children?.filter(child => !child.hide).length || 0
const collectionCount = props.item.collections.length
return `${itemcount} ${itemcount === 1 ? 'item' : 'items'} from ${collectionCount} ${collectionCount === 1 ? 'collection' : 'collections'}`
return `${itemcount} ${itemcount === 1 ? 'item' : 'items'}`
}
return props.item.routePath || props.item.fsPath
})
Expand Down Expand Up @@ -70,9 +69,8 @@ const displayInfo = computed(() => {
</div>
</div>

<div class="flex flex-col items-end justify-between self-stretch">
<div class="flex items-end">
<ItemActionsDropdown :item="item" />
<slot name="bottom-right" />
</div>
</div>
</UTooltip>
Expand Down
9 changes: 2 additions & 7 deletions src/app/src/components/shared/item/ItemCardReview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
import type { DraftItem } from '../../../types'
import type { PropType } from 'vue'
import { computed } from 'vue'
import { DraftStatus, TreeRootId } from '../../../types'
import { DraftStatus } from '../../../types'
import { getFileIcon } from '../../../utils/file'
import { COLOR_UI_STATUS_MAP } from '../../../utils/tree'
import { useStudio } from '../../../composables/useStudio'

const { host } = useStudio()

const props = defineProps({
draftItem: {
Expand All @@ -28,9 +25,7 @@ const originalPath = computed(() => {
return null
}

const isMedia = props.draftItem.original.id.startsWith(TreeRootId.Media)
const hostApi = isMedia ? host.media : host.document
return hostApi.getFileSystemPath(props.draftItem.original.id)
return props.draftItem.original.fsPath
})

function toggleOpen() {
Expand Down
67 changes: 37 additions & 30 deletions src/app/src/composables/useContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createSharedComposable } from '@vueuse/core'
import { computed, ref } from 'vue'
import { StudioItemActionId, DraftStatus, StudioBranchActionId, TreeRootId } from '../types'
import { StudioItemActionId, DraftStatus, StudioBranchActionId, StudioFeature,
} from '../types'
import type {
PublishBranchParams,
RenameFileParams,
Expand All @@ -15,15 +16,15 @@ import type {
DatabaseItem,
MediaItem,
} from '../types'
import { VirtualMediaCollectionName, generateStemFromFsPath } from '../utils/media'
import { oneStepActions, STUDIO_ITEM_ACTION_DEFINITIONS, twoStepActions, STUDIO_BRANCH_ACTION_DEFINITIONS } from '../utils/context'
import type { useTree } from './useTree'
import type { useGit } from './useGit'
import type { useDraftMedias } from './useDraftMedias'
import { useRoute, useRouter } from 'vue-router'
import { findDescendantsFileItemsFromFsPath, generateIdFromFsPath } from '../utils/tree'
import { findDescendantsFileItemsFromFsPath } from '../utils/tree'
import { joinURL } from 'ufo'
import { upperFirst } from 'scule'
import { generateStemFromFsPath } from '../utils/media'

export const useContext = createSharedComposable((
host: StudioHost,
Expand All @@ -34,6 +35,20 @@ export const useContext = createSharedComposable((
const route = useRoute()
const router = useRouter()

/**
* Current feature
*/
const currentFeature = computed<StudioFeature | null>(() => {
switch (route.name) {
case 'media':
return StudioFeature.Media
case 'content':
return StudioFeature.Content
default:
return null
}
})

/**
* Drafts
*/
Expand Down Expand Up @@ -94,64 +109,59 @@ export const useContext = createSharedComposable((
const navigationDocument = await host.document.create(navigationDocumentFsPath, `title: ${folderName}`)
const rootDocument = await host.document.create(rootDocumentFsPath, `# ${upperFirst(folderName)} root file`)

await activeTree.value.draft.create(navigationDocument)
await activeTree.value.draft.create(fsPath, navigationDocument)

unsetActionInProgress()

const rootDocumentDraftItem = await activeTree.value.draft.create(rootDocument)
const rootDocumentDraftItem = await activeTree.value.draft.create(rootDocumentFsPath, rootDocument)

await activeTree.value.selectItemByFsPath(rootDocumentDraftItem.fsPath)
},
[StudioItemActionId.CreateMediaFolder]: async (params: CreateFolderParams) => {
const { fsPath } = params
const gitkeepFsPath = joinURL(fsPath, '.gitkeep')
const gitKeepId = joinURL(VirtualMediaCollectionName, gitkeepFsPath)
const gitKeepMedia: MediaItem = {
id: generateIdFromFsPath(gitkeepFsPath, TreeRootId.Media),
id: gitKeepId,
fsPath: gitkeepFsPath,
stem: generateStemFromFsPath(gitkeepFsPath),
extension: '',
}

await host.media.upsert(gitKeepMedia.id, gitKeepMedia)
await (activeTree.value.draft as ReturnType<typeof useDraftMedias>).create(gitKeepMedia)
await host.media.upsert(gitkeepFsPath, gitKeepMedia)
await (activeTree.value.draft as ReturnType<typeof useDraftMedias>).create(gitkeepFsPath, gitKeepMedia)

unsetActionInProgress()

await activeTree.value.selectParentByFsPath(gitKeepMedia.id)
await activeTree.value.selectParentByFsPath(gitkeepFsPath)
},
[StudioItemActionId.CreateDocument]: async (params: CreateFileParams) => {
const { fsPath, content } = params
const document = await host.document.create(fsPath, content)
const draftItem = await activeTree.value.draft.create(document as DatabaseItem)
const draftItem = await activeTree.value.draft.create(fsPath, document as DatabaseItem)
await activeTree.value.selectItemByFsPath(draftItem.fsPath)
},
[StudioItemActionId.UploadMedia]: async ({ parentFsPath, files }: UploadMediaParams) => {
// Remove .gitkeep draft in folder if exists
const gitkeepFsPath = parentFsPath === '/' ? '.gitkeep' : joinURL(parentFsPath, '.gitkeep')
const gitkeepId = generateIdFromFsPath(gitkeepFsPath, TreeRootId.Media)
const gitkeepDraft = await activeTree.value.draft.get(gitkeepId)
const gitkeepDraft = await activeTree.value.draft.get(gitkeepFsPath)
if (gitkeepDraft) {
await activeTree.value.draft.remove([gitkeepId], { rerender: false })
await activeTree.value.draft.remove([gitkeepFsPath], { rerender: false })
}

for (const file of files) {
await (activeTree.value.draft as ReturnType<typeof useDraftMedias>).upload(parentFsPath, file)
}
},
[StudioItemActionId.RevertItem]: async (item: TreeItem) => {
// Get collections from document item or use default media collection
for (const collection of item.collections) {
const id = generateIdFromFsPath(item.fsPath, collection)
await activeTree.value.draft.revert(id)
}
await activeTree.value.draft.revert(item.fsPath)
},
[StudioItemActionId.RenameItem]: async (params: TreeItem | RenameFileParams) => {
const { item, newFsPath } = params as RenameFileParams

// Revert file
if (item.type === 'file') {
const id = generateIdFromFsPath(item.fsPath, item.collections[0])
await activeTree.value.draft.rename([{ id, newFsPath }])
await activeTree.value.draft.rename([{ fsPath: item.fsPath, newFsPath }])
return
}

Expand All @@ -160,7 +170,7 @@ export const useContext = createSharedComposable((
if (descendants.length > 0) {
const itemsToRename = descendants.map((descendant) => {
return {
id: generateIdFromFsPath(descendant.fsPath, descendant.collections[0]),
fsPath: descendant.fsPath,
newFsPath: descendant.fsPath.replace(item.fsPath, newFsPath),
}
})
Expand All @@ -171,26 +181,22 @@ export const useContext = createSharedComposable((
[StudioItemActionId.DeleteItem]: async (item: TreeItem) => {
// Delete file
if (item.type === 'file') {
const id = generateIdFromFsPath(item.fsPath, item.collections![0])
await activeTree.value.draft.remove([id])
await activeTree.value.draft.remove([item.fsPath])
return
}

// Delete folder
const descendants = findDescendantsFileItemsFromFsPath(activeTree.value.root.value, item.fsPath)
if (descendants.length > 0) {
const ids: string[] = descendants.map((descendant) => {
return generateIdFromFsPath(descendant.fsPath, descendant.collections![0])
})
await activeTree.value.draft.remove(ids)
const fsPaths: string[] = descendants.map(descendant => descendant.fsPath)
await activeTree.value.draft.remove(fsPaths)
}
},
[StudioItemActionId.DuplicateItem]: async (item: TreeItem) => {
// Duplicate file
if (item.type === 'file') {
const id = generateIdFromFsPath(item.fsPath, item.collections![0])
const draftItem = await activeTree.value.draft.duplicate(id)
await activeTree.value.selectItemByFsPath(draftItem!.id)
const draftItem = await activeTree.value.draft.duplicate(item.fsPath)
await activeTree.value.selectItemByFsPath(draftItem!.fsPath)
return
}
},
Expand Down Expand Up @@ -230,6 +236,7 @@ export const useContext = createSharedComposable((
}

return {
currentFeature,
activeTree,
itemActions,
itemActionHandler,
Expand Down
Loading
Loading