From caac3aa8ffff749eef6f3b7cc5297f3066b74698 Mon Sep 17 00:00:00 2001 From: Baptiste Leproux Date: Mon, 29 Sep 2025 22:19:27 +0200 Subject: [PATCH 1/7] feat(context): duplicate and rename actions --- src/app/src/composables/useContext.ts | 3 +- src/app/src/composables/useDraftDocuments.ts | 86 +++++++++++++++----- src/app/src/composables/useDraftMedias.ts | 81 +++++++++++++++--- src/app/src/utils/context.ts | 1 + src/app/src/utils/draft.ts | 1 + 5 files changed, 136 insertions(+), 36 deletions(-) diff --git a/src/app/src/composables/useContext.ts b/src/app/src/composables/useContext.ts index 8ecdb21a..380ae022 100644 --- a/src/app/src/composables/useContext.ts +++ b/src/app/src/composables/useContext.ts @@ -82,7 +82,8 @@ export const useContext = createSharedComposable(( }) }, [StudioItemActionId.DuplicateItem]: async (id: string) => { - alert(`duplicate file ${id}`) + const draftItem = await draft.value.duplicate(id) + await tree.selectItemById(draftItem.id) }, } diff --git a/src/app/src/composables/useDraftDocuments.ts b/src/app/src/composables/useDraftDocuments.ts index e768c1a4..db7b1ca8 100644 --- a/src/app/src/composables/useDraftDocuments.ts +++ b/src/app/src/composables/useDraftDocuments.ts @@ -8,6 +8,7 @@ import { generateContentFromDocument } from '../utils/content' import { getDraftStatus, findDescendantsFromId } from '../utils/draft' import { createSharedComposable } from '@vueuse/core' import { useHooks } from './useHooks' +import { stripNumericPrefix } from '../utils/string' const storage = createStorage({ driver: indexedDbDriver({ @@ -22,15 +23,15 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: const hooks = useHooks() - async function get(id: string, { generateContent = false }: { generateContent?: boolean } = {}) { - const item = list.value.find(item => item.id === id) - if (item && generateContent) { - return { - ...item, - content: await generateContentFromDocument(item!.modified as DatabasePageItem) || '', - } - } - return item + async function get(id: string): Promise | undefined> { + return list.value.find(item => item.id === id) + // if (item && generateContent) { + // return { + // ...item, + // content: await generateContentFromDocument(item!.modified as DatabasePageItem) || '', + // } + // } + // return item } async function create(document: DatabaseItem, status: DraftStatus = DraftStatus.Created) { @@ -60,7 +61,7 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: return item } - async function update(id: string, document: DatabaseItem) { + async function update(id: string, document: DatabaseItem): Promise> { const existingItem = list.value.find(item => item.id === id) if (!existingItem) { throw new Error(`Draft file not found for document ${id}`) @@ -168,18 +169,58 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: host.app.requestRerender() } - async function revertAll() { - await storage.clear() - for (const item of list.value) { - if (item.original) { - await host.document.upsert(item.id, item.original) - } - else if (item.status === DraftStatus.Created) { - await host.document.delete(item.id) - } + async function rename(id: string, newNameWithExtension: string) { + let currentDbItem: DatabaseItem = await host.document.get(id) + if (!currentDbItem) { + throw new Error(`Database item not found for document ${id}`) } - list.value = [] - host.app.requestRerender() + + const currentDraftItem: DraftItem | undefined = list.value.find(item => item.id === id) + if (currentDraftItem) { + currentDbItem = currentDraftItem.modified as DatabasePageItem + } + + const newNameWithoutExtension = newNameWithExtension.split('.').slice(0, -1).join('.') + const newId = `${currentDbItem.id.split('/').slice(0, -1).join('/')}/${newNameWithExtension}` + const newPath = `${currentDbItem.path!.split('/').slice(0, -1).join('/')}/${newNameWithExtension}` + const newStem = `${currentDbItem.stem.split('/').slice(0, -1).join('/')}/${newNameWithoutExtension}` + const newExtension = newNameWithExtension.split('.').pop()! + + const newDbItem: DatabaseItem = { + ...currentDbItem, + id: newId, + path: newPath, + stem: newStem, + extension: newExtension, + } + + return await update(id, newDbItem) + } + + async function duplicate(id: string): Promise> { + let currentDbItem = await host.document.get(id) + if (!currentDbItem) { + throw new Error(`Database item not found for document ${id}`) + } + + const currentDraftItem = list.value.find(item => item.id === id) + if (currentDraftItem) { + currentDbItem = currentDraftItem.modified! + } + + const currentFsPath = currentDraftItem?.fsPath || host.document.getFileSystemPath(id) + const currentRoutePath = currentDbItem.path! + const currentContent = await generateContentFromDocument(currentDbItem) || '' + const currentName = currentFsPath.split('/').pop()! + const currentExtension = currentName.split('.').pop()! + const currentNameWithoutExtension = currentName.split('.').slice(0, -1).join('.') + + const newFsPath = `${currentFsPath.split('/').slice(0, -1).join('/')}/${currentNameWithoutExtension}-copy.${currentExtension}` + const newRoutePath = `${currentRoutePath.split('/').slice(0, -1).join('/')}/${stripNumericPrefix(currentNameWithoutExtension)}-copy` + + const newDbItem = await host.document.create(newFsPath, newRoutePath, currentContent) + + return await create(newDbItem, DraftStatus.Created) } async function load() { @@ -242,7 +283,8 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git: update, remove, revert, - revertAll, + rename, + duplicate, list, load, current, diff --git a/src/app/src/composables/useDraftMedias.ts b/src/app/src/composables/useDraftMedias.ts index da1b8090..393e6716 100644 --- a/src/app/src/composables/useDraftMedias.ts +++ b/src/app/src/composables/useDraftMedias.ts @@ -139,18 +139,72 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret host.app.requestRerender() } - async function revertAll() { - await storage.clear() - for (const item of list.value) { - if (item.original) { - await host.media.upsert(item.id, item.original) - } - else if (item.status === DraftStatus.Created) { - await host.media.delete(item.id) - } - } - list.value = [] - host.app.requestRerender() + // async function revertAll() { + // await storage.clear() + // for (const item of list.value) { + // if (item.original) { + // await host.media.upsert(item.id, item.original) + // } + // else if (item.status === DraftStatus.Created) { + // await host.media.delete(item.id) + // } + // } + // list.value = [] + // host.app.requestRerender() + // } + + async function rename(_id: string, _newNameWithExtension: string) { + // let currentDbItem: MediaItem = await host.document.get(id) + // if (!currentDbItem) { + // throw new Error(`Database item not found for document ${id}`) + // } + + // const currentDraftItem: DraftItem | undefined = list.value.find(item => item.id === id) + // if (currentDraftItem) { + // currentDbItem = currentDraftItem.modified as DatabasePageItem + // } + + // const newNameWithoutExtension = newNameWithExtension.split('.').slice(0, -1).join('.') + // const newId = `${currentDbItem.id.split('/').slice(0, -1).join('/')}/${newNameWithExtension}` + // const newPath = `${currentDbItem.path!.split('/').slice(0, -1).join('/')}/${newNameWithExtension}` + // const newStem = `${currentDbItem.stem.split('/').slice(0, -1).join('/')}/${newNameWithoutExtension}` + // const newExtension = newNameWithExtension.split('.').pop()! + + // const newDbItem = { + // ...currentDbItem, + // id: newId, + // path: newPath, + // stem: newStem, + // extension: newExtension, + // } + + // return await update(id, newDbItem) + } + + async function duplicate(_id: string): Promise> { + return { } as DraftItem + // let currentDbItem = await host.media.get(id) + // if (!currentDbItem) { + // throw new Error(`Database item not found for document ${id}`) + // } + + // const currentDraftItem = list.value.find(item => item.id === id) + // if (currentDraftItem) { + // currentDbItem = currentDraftItem.modified! + // } + + // const currentFsPath = currentDraftItem?.fsPath || host.document.getFileSystemPath(id) + // const currentRoutePath = currentDbItem.path! + // const currentContent = '' + // const currentName = currentFsPath.split('/').pop()! + // const currentExtension = currentName.split('.').pop()! + // const currentNameWithoutExtension = currentName.split('.').slice(0, -1).join('.') + + // const newFsPath = `${currentFsPath.split('/').slice(0, -1).join('/')}/${currentNameWithoutExtension}-copy.${currentExtension}` + // const newRoutePath = `${currentRoutePath.split('/').slice(0, -1).join('/')}/${currentNameWithoutExtension}-copy` + + // const newDbItem = await host.media.create(newFsPath, newRoutePath, currentContent) + // return await create(newDbItem, DraftStatus.Created) } async function load() { @@ -264,7 +318,8 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret update, remove, revert, - revertAll, + rename, + duplicate, list, load, current, diff --git a/src/app/src/utils/context.ts b/src/app/src/utils/context.ts index 60ea4199..6b6bf2e0 100644 --- a/src/app/src/utils/context.ts +++ b/src/app/src/utils/context.ts @@ -94,6 +94,7 @@ export function computeActionParams(action: StudioItemActionId, { item }: { item switch (action) { case StudioItemActionId.RevertItem: case StudioItemActionId.DeleteItem: + case StudioItemActionId.DuplicateItem: return item.id default: return {} diff --git a/src/app/src/utils/draft.ts b/src/app/src/utils/draft.ts index cd88e5a3..472a4d96 100644 --- a/src/app/src/utils/draft.ts +++ b/src/app/src/utils/draft.ts @@ -68,6 +68,7 @@ function isEqual(document1: DatabasePageItem, document2: DatabasePageItem) { if (document1.body?.type === 'minimark') { document1 = removeLastStyle(document1) } + if (document2.body?.type === 'minimark') { document2 = removeLastStyle(document2) } From 8af76e4b8281720ed60673a1790a563a5f5934f0 Mon Sep 17 00:00:00 2001 From: Baptiste Leproux Date: Tue, 30 Sep 2025 15:37:33 +0200 Subject: [PATCH 2/7] up --- src/module/src/runtime/utils/content.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/module/src/runtime/utils/content.ts b/src/module/src/runtime/utils/content.ts index 59af166b..5248c5a0 100644 --- a/src/module/src/runtime/utils/content.ts +++ b/src/module/src/runtime/utils/content.ts @@ -35,6 +35,7 @@ export async function generateDocumentFromContent(id: string, fsPath: string, ro // TODO expose document creation logic from content module and use it there const stem = generateStemFromFsPath(fsPath) + // TODO: minify parsed response const parsed = await parseMarkdown(content) return { From d9556b9ff5026c407ff83548154616186120c52d Mon Sep 17 00:00:00 2001 From: Baptiste Leproux Date: Wed, 1 Oct 2025 15:34:46 +0200 Subject: [PATCH 3/7] up --- .../panel/base/PanelBaseBodyTree.vue | 17 ++++++++++----- .../components/shared/item/ItemCardForm.vue | 11 ++++++++-- src/app/src/composables/useContext.ts | 21 +++++++++++++------ src/app/src/types/context.ts | 9 ++++++-- src/app/src/utils/context.ts | 7 ++++++- 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/app/src/components/panel/base/PanelBaseBodyTree.vue b/src/app/src/components/panel/base/PanelBaseBodyTree.vue index 6a5a0a66..a15feedf 100644 --- a/src/app/src/components/panel/base/PanelBaseBodyTree.vue +++ b/src/app/src/components/panel/base/PanelBaseBodyTree.vue @@ -7,7 +7,7 @@ const { documentTree, mediaTree, context } = useStudio() const treeApi = computed(() => context.feature.value === StudioFeature.Content ? documentTree : mediaTree) -defineProps({ +const props = defineProps({ type: { type: String as PropType<'directory' | 'file'>, required: true, @@ -16,11 +16,17 @@ defineProps({ type: Array as PropType, default: () => [], }, - showCreationForm: { + showForm: { type: Boolean, default: false, }, }) + +const filteredTree = computed(() => { + if (!context.actionInProgress.value?.item) return props.tree + + return props.tree.filter(item => item.id !== context.actionInProgress.value!.item?.id) +})