diff --git a/playground/docus/app/pages/test.vue b/playground/docus/app/pages/test.vue new file mode 100644 index 00000000..0369a35b --- /dev/null +++ b/playground/docus/app/pages/test.vue @@ -0,0 +1,9 @@ + + + diff --git a/playground/docus/content.config.ts b/playground/docus/content.config.ts new file mode 100644 index 00000000..7633908b --- /dev/null +++ b/playground/docus/content.config.ts @@ -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 = { + 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 }) diff --git a/playground/docus/content/3.custom-case/test.md b/playground/docus/content/3.custom-case/test.md new file mode 100644 index 00000000..2c13f8cf --- /dev/null +++ b/playground/docus/content/3.custom-case/test.md @@ -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: '/', + }, + }), +``` diff --git a/src/app/src/app.vue b/src/app/src/app.vue index 99f99933..ed1cfb6a 100644 --- a/src/app/src/app.vue +++ b/src/app/src/app.vue @@ -25,18 +25,17 @@ watch(ui.sidebar.sidebarWidth, () => { // Nuxt UI Portal element const appPortal = ref() -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() } @@ -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)" /> diff --git a/src/app/src/components/content/ContentCard.vue b/src/app/src/components/content/ContentCard.vue index adea679b..a3b835c0 100644 --- a/src/app/src/components/content/ContentCard.vue +++ b/src/app/src/components/content/ContentCard.vue @@ -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') - diff --git a/src/app/src/components/content/ContentEditor.vue b/src/app/src/components/content/ContentEditor.vue index 69f73b3b..e213f14a 100644 --- a/src/app/src/components/content/ContentEditor.vue +++ b/src/app/src/components/content/ContentEditor.vue @@ -50,7 +50,7 @@ const document = computed({ return } - context.activeTree.value.draft.update(props.draftItem.id, { + context.activeTree.value.draft.update(props.draftItem.fsPath, { ...toRaw(document.value as DatabasePageItem), ...toRaw(value), }) diff --git a/src/app/src/components/shared/item/ItemActionsDropdown.vue b/src/app/src/components/shared/item/ItemActionsDropdown.vue index e0eedb9f..063c39a8 100644 --- a/src/app/src/components/shared/item/ItemActionsDropdown.vue +++ b/src/app/src/components/shared/item/ItemActionsDropdown.vue @@ -40,7 +40,7 @@ const actions = computed(() => { 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 diff --git a/src/app/src/components/shared/item/ItemActionsToolbar.vue b/src/app/src/components/shared/item/ItemActionsToolbar.vue index 8efd06c7..45351b41 100644 --- a/src/app/src/components/shared/item/ItemActionsToolbar.vue +++ b/src/app/src/components/shared/item/ItemActionsToolbar.vue @@ -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 diff --git a/src/app/src/components/shared/item/ItemCard.vue b/src/app/src/components/shared/item/ItemCard.vue index 700e6510..49cb2c52 100644 --- a/src/app/src/components/shared/item/ItemCard.vue +++ b/src/app/src/components/shared/item/ItemCard.vue @@ -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 }) @@ -70,9 +69,8 @@ const displayInfo = computed(() => { -
+
-
diff --git a/src/app/src/components/shared/item/ItemCardReview.vue b/src/app/src/components/shared/item/ItemCardReview.vue index 7a5219d5..4a6a9e88 100644 --- a/src/app/src/components/shared/item/ItemCardReview.vue +++ b/src/app/src/components/shared/item/ItemCardReview.vue @@ -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: { @@ -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() { diff --git a/src/app/src/composables/useContext.ts b/src/app/src/composables/useContext.ts index 329e91a5..f81571dd 100644 --- a/src/app/src/composables/useContext.ts +++ b/src/app/src/composables/useContext.ts @@ -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, @@ -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, @@ -34,6 +35,20 @@ export const useContext = createSharedComposable(( const route = useRoute() const router = useRouter() + /** + * Current feature + */ + const currentFeature = computed(() => { + switch (route.name) { + case 'media': + return StudioFeature.Media + case 'content': + return StudioFeature.Content + default: + return null + } + }) + /** * Drafts */ @@ -94,44 +109,43 @@ 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(navigationDocumentFsPath, navigationDocument) + const rootDocumentDraftItem = await activeTree.value.draft.create(rootDocumentFsPath, rootDocument) unsetActionInProgress() - const rootDocumentDraftItem = await activeTree.value.draft.create(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).create(gitKeepMedia) + await host.media.upsert(gitkeepFsPath, gitKeepMedia) + await (activeTree.value.draft as ReturnType).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) { @@ -139,19 +153,14 @@ export const useContext = createSharedComposable(( } }, [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 } @@ -160,7 +169,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), } }) @@ -171,26 +180,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 } }, @@ -230,6 +235,7 @@ export const useContext = createSharedComposable(( } return { + currentFeature, activeTree, itemActions, itemActionHandler, diff --git a/src/app/src/composables/useDraftBase.ts b/src/app/src/composables/useDraftBase.ts index a217bdbe..4c8468c4 100644 --- a/src/app/src/composables/useDraftBase.ts +++ b/src/app/src/composables/useDraftBase.ts @@ -2,7 +2,7 @@ import type { Storage } from 'unstorage' import { joinURL } from 'ufo' import type { DraftItem, StudioHost, GithubFile, DatabaseItem, MediaItem } from '../types' import { DraftStatus } from '../types/draft' -import { checkConflict, findDescendantsFromId, getDraftStatus } from '../utils/draft' +import { checkConflict, findDescendantsFromFsPath, getDraftStatus } from '../utils/draft' import type { useGit } from './useGit' import { useHooks } from './useHooks' import { ref } from 'vue' @@ -23,21 +23,19 @@ export function useDraftBase( const hooks = useHooks() - async function get(id: string): Promise | undefined> { - return list.value.find(item => item.id === id) as DraftItem + async function get(fsPath: string): Promise | undefined> { + return list.value.find(item => item.fsPath === fsPath) as DraftItem } - async function create(item: T, original?: T, { rerender = true }: { rerender?: boolean } = {}): Promise> { - const existingItem = list.value?.find(draft => draft.id === item.id) + async function create(fsPath: string, item: T, original?: T, { rerender = true }: { rerender?: boolean } = {}): Promise> { + const existingItem = list.value?.find(draft => draft.fsPath === fsPath) if (existingItem) { - throw new Error(`Draft file already exists for document ${item.id}`) + throw new Error(`Draft file already exists for document at ${fsPath}`) } - const fsPath = hostDb.getFileSystemPath(item.id) const githubFile = await git.fetchFile(joinURL(ghPathPrefix, fsPath), { cached: true }) as GithubFile const draftItem: DraftItem = { - id: item.id, fsPath, githubFile, status: getDraftStatus(item, original), @@ -53,7 +51,7 @@ export function useDraftBase( draftItem.conflict = conflict } - await storage.setItem(item.id, draftItem) + await storage.setItem(fsPath, draftItem) list.value.push(draftItem) @@ -64,32 +62,30 @@ export function useDraftBase( return draftItem } - async function remove(ids: string[], { rerender = true }: { rerender?: boolean } = {}) { - for (const id of ids) { - const existingDraftItem = list.value.find(item => item.id === id) as DraftItem | undefined - const fsPath = hostDb.getFileSystemPath(id) - const originalDbItem = await hostDb.get(id) as T + async function remove(fsPaths: string[], { rerender = true }: { rerender?: boolean } = {}) { + for (const fsPath of fsPaths) { + const existingDraftItem = list.value.find(item => item.fsPath === fsPath) as DraftItem | undefined + const originalDbItem = await hostDb.get(fsPath) as T - await storage.removeItem(id) - await hostDb.delete(id) + await storage.removeItem(fsPath) + await hostDb.delete(fsPath) let deleteDraftItem: DraftItem | null = null if (existingDraftItem) { if (existingDraftItem.status === DraftStatus.Deleted) return if (existingDraftItem.status === DraftStatus.Created) { - list.value = list.value.filter(item => item.id !== id) + list.value = list.value.filter(item => item.fsPath !== fsPath) } else { deleteDraftItem = { - id, fsPath: existingDraftItem.fsPath, status: DraftStatus.Deleted, original: existingDraftItem.original, githubFile: existingDraftItem.githubFile, } - list.value = list.value.map(item => item.id === id ? deleteDraftItem! : item) as DraftItem[] + list.value = list.value.map(item => item.fsPath === fsPath ? deleteDraftItem! : item) as DraftItem[] } } else { @@ -97,7 +93,6 @@ export function useDraftBase( const githubFile = await git.fetchFile(joinURL('content', fsPath), { cached: true }) as GithubFile deleteDraftItem = { - id, fsPath, status: DraftStatus.Deleted, original: originalDbItem, @@ -108,7 +103,7 @@ export function useDraftBase( } if (deleteDraftItem) { - await storage.setItem(id, deleteDraftItem) + await storage.setItem(fsPath, deleteDraftItem) } if (rerender) { @@ -117,31 +112,31 @@ export function useDraftBase( } } - async function revert(id: string, { rerender = true }: { rerender?: boolean } = {}) { - const draftItems = findDescendantsFromId(list.value, id) + async function revert(fsPath: string, { rerender = true }: { rerender?: boolean } = {}) { + const draftItems = findDescendantsFromFsPath(list.value, fsPath) for (const draftItem of draftItems) { - const existingItem = list.value.find(item => item.id === draftItem.id) as DraftItem + const existingItem = list.value.find(item => item.fsPath === draftItem.fsPath) as DraftItem if (!existingItem) { return } if (existingItem.status === DraftStatus.Created) { - await hostDb.delete(draftItem.id) - await storage.removeItem(draftItem.id) - list.value = list.value.filter(item => item.id !== draftItem.id) + await hostDb.delete(draftItem.fsPath) + await storage.removeItem(draftItem.fsPath) + list.value = list.value.filter(item => item.fsPath !== draftItem.fsPath) // Renamed draft if (existingItem.original) { - await revert(existingItem.original.id, { rerender: false }) + await revert(existingItem.original.fsPath, { rerender: false }) } } else { // @ts-expect-error upsert type is wrong, second param should be DatabaseItem | MediaItem - await hostDb.upsert(draftItem.id, existingItem.original) + await hostDb.upsert(draftItem.fsPath, existingItem.original) existingItem.modified = existingItem.original existingItem.status = getDraftStatus(existingItem.modified, existingItem.original) - await storage.setItem(draftItem.id, existingItem) + await storage.setItem(draftItem.fsPath, existingItem) } } @@ -154,7 +149,7 @@ export function useDraftBase( const itemsToRevert = [...list.value] for (const draftItem of itemsToRevert) { - await revert(draftItem.id, { rerender: false }) + await revert(draftItem.fsPath, { rerender: false }) } await hooks.callHook(hookName, { caller: 'useDraftBase.revertAll' }) @@ -164,22 +159,22 @@ export function useDraftBase( current.value = null } - async function selectById(id: string) { + async function selectByFsPath(fsPath: string) { isLoading.value = true try { - const existingItem = list.value?.find(item => item.id === id) as DraftItem + const existingItem = list.value?.find(item => item.fsPath === fsPath) as DraftItem if (existingItem) { current.value = existingItem return } - const dbItem = await hostDb.get(id) as T + const dbItem = await hostDb.get(fsPath) as T if (!dbItem) { - throw new Error(`Cannot select item: no corresponding database entry found for id ${id}`) + throw new Error(`Cannot select item: no corresponding database entry found for fsPath ${fsPath}`) } - const draftItem = await create(dbItem, dbItem) + const draftItem = await create(fsPath, dbItem, dbItem) current.value = draftItem } @@ -205,11 +200,11 @@ export function useDraftBase( // Upsert/Delete draft files in database await Promise.all(list.value.map(async (draftItem) => { if (draftItem.status === DraftStatus.Deleted) { - await hostDb.delete(draftItem.id) + await hostDb.delete(draftItem.fsPath) } else { // @ts-expect-error upsert type is wrong, second param should be DatabaseItem | MediaItem - await hostDb.upsert(draftItem.id, draftItem.modified) + await hostDb.upsert(draftItem.fsPath, draftItem.modified) } })) @@ -225,7 +220,7 @@ export function useDraftBase( remove, revert, revertAll, - selectById, + selectByFsPath, unselect, load, checkConflict, diff --git a/src/app/src/composables/useDraftDocuments.ts b/src/app/src/composables/useDraftDocuments.ts index e35561be..23736ec1 100644 --- a/src/app/src/composables/useDraftDocuments.ts +++ b/src/app/src/composables/useDraftDocuments.ts @@ -20,29 +20,29 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: remove, revert, revertAll, - selectById, + selectByFsPath, unselect, load, } = useDraftBase('document', host, git, storage) const hooks = useHooks() - async function update(id: string, document: DatabaseItem): Promise> { - const existingItem = list.value.find(item => item.id === id) as DraftItem + async function update(fsPath: string, document: DatabaseItem): Promise> { + const existingItem = list.value.find(item => item.fsPath === fsPath) as DraftItem if (!existingItem) { - throw new Error(`Draft file not found for document ${id}`) + throw new Error(`Draft file not found for document fsPath: ${fsPath}`) } const oldStatus = existingItem.status existingItem.status = getDraftStatus(document, existingItem.original) existingItem.modified = document - await storage.setItem(id, existingItem) + await storage.setItem(fsPath, existingItem) - list.value = list.value.map(item => item.id === id ? existingItem : item) + list.value = list.value.map(item => item.fsPath === fsPath ? existingItem : item) // Upsert document in database - await host.document.upsert(id, existingItem.modified) + await host.document.upsert(fsPath, existingItem.modified) // Trigger hook to warn that draft list has changed if (existingItem.status !== oldStatus) { @@ -56,14 +56,14 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: return existingItem } - async function rename(items: { id: string, newFsPath: string }[]) { + async function rename(items: { fsPath: string, newFsPath: string }[]) { for (const item of items) { - const { id, newFsPath } = item + const { fsPath, newFsPath } = item - const existingDraftToRename = list.value.find(draftItem => draftItem.id === id) as DraftItem - const dbItemToRename: DatabaseItem = await host.document.get(id) + const existingDraftToRename = list.value.find(draftItem => draftItem.fsPath === fsPath) as DraftItem + const dbItemToRename = await host.document.get(fsPath) if (!dbItemToRename) { - throw new Error(`Database item not found for document ${id}`) + throw new Error(`Database item not found for document fsPath: ${fsPath}`) } const modifiedDbItem = existingDraftToRename?.modified || dbItemToRename @@ -74,28 +74,28 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: const content = await generateContentFromDocument(modifiedDbItem) - await remove([id], { rerender: false }) + await remove([fsPath], { rerender: false }) const newDbItem = await host.document.create(newFsPath, content!) - await create(newDbItem, originalDbItem, { rerender: false }) + await create(newFsPath, newDbItem, originalDbItem, { rerender: false }) } await hooks.callHook('studio:draft:document:updated', { caller: 'useDraftDocuments.rename' }) } - async function duplicate(id: string): Promise> { - let currentDbItem = await host.document.get(id) + async function duplicate(fsPath: string): Promise> { + let currentDbItem = await host.document.get(fsPath) if (!currentDbItem) { - throw new Error(`Database item not found for document ${id}`) + throw new Error(`Database item not found for document fsPath: ${fsPath}`) } - const currentDraftItem = list.value.find(item => item.id === id) + const currentDraftItem = list.value.find(item => item.fsPath === fsPath) if (currentDraftItem) { currentDbItem = currentDraftItem.modified as DatabaseItem } - const currentFsPath = currentDraftItem?.fsPath || host.document.getFileSystemPath(id) + const currentFsPath = currentDraftItem?.fsPath || fsPath const currentContent = await generateContentFromDocument(currentDbItem) || '' const currentName = currentFsPath.split('/').pop()! const currentExtension = getFileExtension(currentName) @@ -105,7 +105,7 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: const newDbItem = await host.document.create(newFsPath, currentContent) - return await create(newDbItem) + return await create(newFsPath, newDbItem) } async function listAsRawFiles(): Promise { @@ -142,7 +142,7 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: duplicate, listAsRawFiles, load, - selectById, + selectByFsPath, unselect, } }) diff --git a/src/app/src/composables/useDraftMedias.ts b/src/app/src/composables/useDraftMedias.ts index 7d01e4e5..91559573 100644 --- a/src/app/src/composables/useDraftMedias.ts +++ b/src/app/src/composables/useDraftMedias.ts @@ -1,13 +1,12 @@ import { joinURL, withLeadingSlash } from 'ufo' import type { DraftItem, StudioHost, MediaItem, RawFile } from '../types' -import { TreeRootId } from '../types' +import { VirtualMediaCollectionName, generateStemFromFsPath } from '../utils/media' import { DraftStatus } from '../types/draft' import type { useGit } from './useGit' import { createSharedComposable } from '@vueuse/core' import { useDraftBase } from './useDraftBase' import { mediaStorage as storage } from '../utils/storage' import { getFileExtension, slugifyFileName } from '../utils/file' -import { generateStemFromFsPath } from '../utils/media' import { useHooks } from './useHooks' const hooks = useHooks() @@ -22,15 +21,15 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret remove, revert, revertAll, - selectById, + selectByFsPath, unselect, load, } = useDraftBase('media', host, git, storage) async function upload(parentFsPath: string, file: File) { const draftItem = await fileToDraftItem(parentFsPath, file) - host.media.upsert(draftItem.id, draftItem.modified!) - await create(draftItem.modified!) + host.media.upsert(draftItem.fsPath, draftItem.modified!) + await create(draftItem.fsPath, draftItem.modified!) } async function fileToDraftItem(parentFsPath: string, file: File): Promise> { @@ -39,49 +38,49 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret const fsPath = parentFsPath !== '/' ? joinURL(parentFsPath, slugifiedFileName) : slugifiedFileName return { - id: joinURL(TreeRootId.Media, fsPath), fsPath, githubFile: undefined, status: DraftStatus.Created, modified: { - id: joinURL(TreeRootId.Media, fsPath), + id: joinURL(VirtualMediaCollectionName, fsPath), fsPath, extension: getFileExtension(fsPath), - stem: fsPath.split('.').join('.'), + stem: generateStemFromFsPath(fsPath), path: withLeadingSlash(fsPath), raw: rawData, }, } } - async function rename(items: { id: string, newFsPath: string }[]) { + async function rename(items: { fsPath: string, newFsPath: string }[]) { for (const item of items) { - const { id, newFsPath } = item + const { fsPath, newFsPath } = item - const existingDraftToRename = list.value.find(draftItem => draftItem.id === id) as DraftItem + const existingDraftToRename = list.value.find(draftItem => draftItem.fsPath === fsPath) as DraftItem - const currentDbItem = await host.media.get(id) + const currentDbItem = await host.media.get(fsPath) if (!currentDbItem) { - throw new Error(`Database item not found for document ${id}`) + throw new Error(`Database item not found for document fsPath: ${fsPath}`) } - await remove([id], { rerender: false }) + await remove([fsPath], { rerender: false }) const newDbItem: MediaItem = { ...currentDbItem, - id: joinURL(TreeRootId.Media, newFsPath), + fsPath: newFsPath, + id: joinURL(VirtualMediaCollectionName, newFsPath), stem: generateStemFromFsPath(newFsPath), path: withLeadingSlash(newFsPath), } - await host.media.upsert(newDbItem.id, newDbItem) + await host.media.upsert(newFsPath, newDbItem) let originalDbItem: MediaItem | undefined = currentDbItem if (existingDraftToRename) { originalDbItem = existingDraftToRename.original } - await create(newDbItem, originalDbItem, { rerender: false }) + await create(newFsPath, newDbItem, originalDbItem, { rerender: false }) } await hooks.callHook('studio:draft:media:updated', { caller: 'useDraftMedias.rename' }) @@ -128,7 +127,7 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret revertAll, rename, load, - selectById, + selectByFsPath, unselect, upload, listAsRawFiles, diff --git a/src/app/src/composables/useStudio.ts b/src/app/src/composables/useStudio.ts index 2183ea82..4b7cc535 100644 --- a/src/app/src/composables/useStudio.ts +++ b/src/app/src/composables/useStudio.ts @@ -88,19 +88,18 @@ function initDevelopmentMode(host: StudioHost, draftDocuments: ReturnType { - const item = draftDocuments.list.value.find(item => item.id === id) + host.on.documentUpdate(async (fsPath: string, type: 'remove' | 'update') => { + const item = draftDocuments.list.value.find(item => item.fsPath === fsPath) if (type === 'remove') { if (item) { - await draftDocuments.remove([id]) + await draftDocuments.remove([fsPath]) } } else if (item) { - const fsPath = host.document.getFileSystemPath(id) // Update draft if the document is not focused or the current item is not the one that was updated if (!window.document.hasFocus() || documentTree.currentItem.value?.fsPath !== fsPath) { - const document = await host.document.get(id) + const document = await host.document.get(fsPath) item.modified = document item.original = document item.status = getDraftStatus(document, item.original) @@ -111,18 +110,17 @@ function initDevelopmentMode(host: StudioHost, draftDocuments: ReturnType { - const item = draftMedias.list.value.find(item => item.id === id) + host.on.mediaUpdate(async (fsPath: string, type: 'remove' | 'update') => { + const item = draftMedias.list.value.find(item => item.fsPath === fsPath) if (type === 'remove') { if (item) { - await draftMedias.remove([id]) + await draftMedias.remove([fsPath]) } } else if (item) { - const fsPath = host.media.getFileSystemPath(id) if (!window.document.hasFocus() || mediaTree.currentItem.value?.fsPath !== fsPath) { - const media = await host.media.get(id) + const media = await host.media.get(fsPath) item.modified = media item.original = media item.status = getDraftStatus(media, item.original) diff --git a/src/app/src/composables/useTree.ts b/src/app/src/composables/useTree.ts index 8b374400..6f885672 100644 --- a/src/app/src/composables/useTree.ts +++ b/src/app/src/composables/useTree.ts @@ -1,12 +1,12 @@ -import { StudioFeature, TreeStatus, type StudioHost, type TreeItem, DraftStatus } from '../types' +import type { DatabaseItem, StudioHost, TreeItem } from '../types' +import { StudioFeature, TreeStatus, DraftStatus } from '../types' import { ref, computed } from 'vue' import type { useDraftDocuments } from './useDraftDocuments' import type { useDraftMedias } from './useDraftMedias' -import { buildTree, findItemFromFsPath, findItemFromRoute, findParentFromFsPath, generateIdFromFsPath } from '../utils/tree' +import { buildTree, findItemFromFsPath, findItemFromRoute, findParentFromFsPath } from '../utils/tree' import type { RouteLocationNormalized } from 'vue-router' import { useHooks } from './useHooks' import { useStudioState } from './useStudioState' -import { TreeRootId } from '../types/tree' export const useTree = (type: StudioFeature, host: StudioHost, draft: ReturnType) => { const hooks = useHooks() @@ -22,7 +22,6 @@ export const useTree = (type: StudioFeature, host: StudioHost, draft: ReturnType fsPath: '/', children: tree.value, status: draftedTreeItems.length > 0 ? TreeStatus.Updated : null, - collections: [type === StudioFeature.Content ? TreeRootId.Content : TreeRootId.Media], prefix: null, } as TreeItem }) @@ -53,7 +52,7 @@ export const useTree = (type: StudioFeature, host: StudioHost, draft: ReturnType setLocation(type, currentItem.value.fsPath) if (item?.type === 'file') { - await draft.selectById(generateIdFromFsPath(item.fsPath, item.collections![0])) + await draft.selectByFsPath(item.fsPath) if ( !preferences.value.syncEditorAndRoute @@ -100,13 +99,9 @@ export const useTree = (type: StudioFeature, host: StudioHost, draft: ReturnType // Trigger tree rebuild to update files status async function handleDraftUpdate(selectItem: boolean = true) { const api = type === StudioFeature.Content ? host.document : host.media - const list = await api.list() - const listWithFsPath = list.map((item) => { - const fsPath = api.getFileSystemPath(item.id) - return { ...item, fsPath } - }) + const list = await api.list() as DatabaseItem[] - tree.value = buildTree(listWithFsPath, draft.list.value) + tree.value = buildTree(list, draft.list.value) // Reselect current item to update status if (selectItem) { diff --git a/src/app/src/pages/review.vue b/src/app/src/pages/review.vue index e3199618..588f7a67 100644 --- a/src/app/src/pages/review.vue +++ b/src/app/src/pages/review.vue @@ -79,7 +79,7 @@ const statusConfig = {