Skip to content

Commit 1f22adb

Browse files
committed
feat(context): create folder action
1 parent aed8a2d commit 1f22adb

17 files changed

Lines changed: 215 additions & 75 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@5111",
4545
"@octokit/types": "^15.0.0",
4646
"@tailwindcss/typography": "latest",
47+
"@types/js-yaml": "^4.0.9",
4748
"@vitejs/plugin-vue": "^6.0.1",
4849
"eslint": "^9.36.0",
4950
"modern-monaco": "^0.2.2",
@@ -73,6 +74,7 @@
7374
"defu": "^6.1.4",
7475
"destr": "^2.0.5",
7576
"idb-keyval": "^6.2.2",
77+
"js-yaml": "^4.1.0",
7678
"minimark": "^0.2.0",
7779
"ofetch": "^1.4.1",
7880
"unstorage": "^1.17.1"

pnpm-lock.yaml

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/src/components/content/ContentEditorCode.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ watch(() => props.draftItem.status, (newStatus) => {
3838
const language = computed(() => {
3939
switch (document.value?.extension) {
4040
case ContentFileExtension.Markdown:
41-
return 'mdc';
41+
return 'mdc'
4242
case ContentFileExtension.YAML:
4343
case ContentFileExtension.YML:
44-
return 'yaml';
44+
return 'yaml'
4545
case ContentFileExtension.JSON:
46-
return 'javascript';
46+
return 'javascript'
4747
default:
4848
return 'text'
4949
}

src/app/src/components/shared/item/ItemCard.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { computed } from 'vue'
55
import { Image } from '@unpic/vue'
66
import { titleCase } from 'scule'
77
import { COLOR_UI_STATUS_MAP } from '../../../utils/tree'
8-
import { DraftStatus } from '../../../types/draft'
98
109
const props = defineProps({
1110
item: {
@@ -60,7 +59,10 @@ const statusRingColor = computed(() => props.item.status ? `ring-(--ui-${COLOR_U
6059
alt="Card placeholder"
6160
class="z-[-1] rounded-t-lg aspect-video object-cover"
6261
/>
63-
<div v-else class="z-[-1] aspect-video bg-muted" />
62+
<div
63+
v-else
64+
class="z-[-1] aspect-video bg-elevated"
65+
/>
6466
<div
6567
v-if="itemExtensionIcon"
6668
class="absolute inset-0 flex items-center justify-center"
@@ -72,7 +74,7 @@ const statusRingColor = computed(() => props.item.status ? `ring-(--ui-${COLOR_U
7274
</div>
7375
</div>
7476
<ItemBadge
75-
v-if="item.status && item.status !== DraftStatus.Opened"
77+
v-if="item.status && item.status !== TreeStatus.Opened"
7678
:status="item.status"
7779
class="absolute top-2 right-2"
7880
/>

src/app/src/components/shared/item/ItemCardForm.vue

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
<script setup lang="ts">
22
import { computed, reactive, type PropType } from 'vue'
3-
import { Image } from '@unpic/vue'
43
import * as z from 'zod'
54
import type { FormSubmitEvent } from '@nuxt/ui'
65
import { type CreateFileParams, type CreateFolderParams, type RenameFileParams, type StudioAction, type TreeItem, ContentFileExtension } from '../../../types'
7-
import { joinURL, withLeadingSlash } from 'ufo'
6+
import { joinURL, withLeadingSlash, withoutLeadingSlash } from 'ufo'
87
import { contentFileExtensions } from '../../../utils/content'
98
import { useStudio } from '../../../composables/useStudio'
109
import { StudioItemActionId } from '../../../types'
1110
import { stripNumericPrefix } from '../../../utils/string'
11+
import { defineShortcuts } from '#imports'
1212
1313
const { context } = useStudio()
1414
15+
defineShortcuts({
16+
escape: () => {
17+
context.unsetActionInProgress()
18+
},
19+
})
20+
1521
const props = defineProps({
1622
actionId: {
1723
type: String as PropType<StudioItemActionId.CreateDocument | StudioItemActionId.CreateFolder | StudioItemActionId.RenameItem>,
@@ -48,6 +54,14 @@ const action = computed<StudioAction>(() => {
4854
return context.itemActions.value.find(action => action.id === props.actionId)!
4955
})
5056
57+
const isFolderAction = computed(() => {
58+
return props.actionId === StudioItemActionId.CreateFolder
59+
|| (
60+
props.actionId === StudioItemActionId.RenameItem
61+
&& props.renamedItem.type === 'directory'
62+
)
63+
})
64+
5165
const itemExtensionIcon = computed<string>(() => {
5266
return {
5367
md: 'i-ph-markdown-logo',
@@ -74,26 +88,25 @@ const tooltipText = computed(() => {
7488
})
7589
7690
function onSubmit(_event: FormSubmitEvent<Schema>) {
77-
const fsPath = joinURL(props.parentItem.fsPath, `${state.name}.${state.extension}`)
78-
7991
let params: CreateFileParams | CreateFolderParams | RenameFileParams
8092
switch (props.actionId) {
8193
case StudioItemActionId.CreateDocument:
8294
params = {
95+
fsPath: withoutLeadingSlash(joinURL(props.parentItem.fsPath, `${state.name}.${state.extension}`)),
8396
routePath: routePath.value,
84-
fsPath,
8597
content: `New ${state.name} file`,
8698
}
8799
break
88100
case StudioItemActionId.CreateFolder:
89101
params = {
90-
fsPath,
102+
fsPath: withoutLeadingSlash(joinURL(props.parentItem.fsPath, state.name)),
103+
routePath: routePath.value,
91104
}
92105
break
93106
case StudioItemActionId.RenameItem:
94107
params = {
108+
newFsPath: withoutLeadingSlash(joinURL(props.parentItem.fsPath, `${state.name}.${state.extension}`)),
95109
id: props.renamedItem.id,
96-
newFsPath: joinURL(props.parentItem.fsPath, `${state.name}.${state.extension}`),
97110
}
98111
break
99112
}
@@ -113,14 +126,11 @@ function onSubmit(_event: FormSubmitEvent<Schema>) {
113126
reverse
114127
class="hover:bg-white relative w-full min-w-0"
115128
>
116-
<div class="relative">
117-
<Image
118-
src="https://placehold.co/1920x1080/f9fafc/f9fafc"
119-
width="426"
120-
height="240"
121-
alt="Card placeholder"
122-
class="z-[-1] rounded-t-lg"
123-
/>
129+
<div
130+
v-if="!isFolderAction"
131+
class="relative"
132+
>
133+
<div class="z-[-1] aspect-video rounded-lg bg-elevated" />
124134
<div class="absolute inset-0 flex items-center justify-center">
125135
<UIcon
126136
:name="itemExtensionIcon"
@@ -170,15 +180,23 @@ function onSubmit(_event: FormSubmitEvent<Schema>) {
170180
<template #error>
171181
<span />
172182
</template>
173-
<UInput
174-
v-model="state.name"
175-
variant="soft"
176-
autofocus
177-
placeholder="File name"
178-
class="w-full"
179-
/>
183+
<div class="flex items-center gap-1">
184+
<UIcon
185+
v-if="isFolderAction"
186+
name="i-lucide-folder"
187+
class="h-4 w-4 shrink-0 text-muted"
188+
/>
189+
<UInput
190+
v-model="state.name"
191+
variant="soft"
192+
autofocus
193+
:placeholder="isFolderAction ? 'Folder name' : 'File name'"
194+
class="w-full"
195+
/>
196+
</div>
180197
</UFormField>
181198
<UFormField
199+
v-if="!isFolderAction"
182200
name="extension"
183201
:ui="{ error: 'hidden' }"
184202
>

src/app/src/composables/useContext.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { useTree } from './useTree'
1818
import { useRoute } from 'vue-router'
1919
import { findDescendantsFileItemsFromId } from '../utils/tree'
2020
import type { useDraftMedias } from './useDraftMedias'
21+
import { joinURL } from 'ufo'
2122

2223
export const useContext = createSharedComposable((
2324
host: StudioHost,
@@ -73,11 +74,22 @@ export const useContext = createSharedComposable((
7374

7475
const itemActionHandler: { [K in StudioItemActionId]: (args: ActionHandlerParams[K]) => Promise<void> } = {
7576
[StudioItemActionId.CreateFolder]: async (params: CreateFolderParams) => {
76-
alert(`create folder in ${params.fsPath}`)
77+
const { fsPath } = params
78+
const folderName = fsPath.split('/').pop()!
79+
const rootDocumentFsPath = joinURL(fsPath, 'index.md')
80+
const navigationDocumentFsPath = joinURL(fsPath, '.navigation.yml')
81+
82+
const navigationDocument = await host.document.create(navigationDocumentFsPath, `title: ${folderName}`)
83+
const rootDocument = await host.document.create(rootDocumentFsPath, `New ${folderName} root file`)
84+
85+
await activeTree.value.draft.create(navigationDocument)
86+
const rootDocumentDraftItem = await activeTree.value.draft.create(rootDocument)
87+
88+
await activeTree.value.selectItemById(rootDocumentDraftItem.id)
7789
},
7890
[StudioItemActionId.CreateDocument]: async (params: CreateFileParams) => {
79-
const { fsPath, routePath, content } = params
80-
const document = await host.document.create(fsPath, routePath, content)
91+
const { fsPath, content } = params
92+
const document = await host.document.create(fsPath, content)
8193
const draftItem = await activeTree.value.draft.create(document)
8294
await activeTree.value.selectItemById(draftItem.id)
8395
},

src/app/src/types/context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export interface StudioAction<K extends StudioItemActionId = StudioItemActionId>
3131
export interface CreateFolderParams {
3232
fsPath: string
3333
}
34+
3435
export interface CreateFileParams {
3536
fsPath: string
36-
routePath: string
3737
content: string
3838
}
3939

src/app/src/types/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { DatabaseItem } from './database'
33
import type { RouteLocationNormalized } from 'vue-router'
44
import type { MediaItem } from './media'
55
import type { Repository } from './git'
6-
import type { ComponentMeta } from './components'
6+
import type { ComponentMeta } from './component'
77

88
export * from './item'
99
export * from './draft'
@@ -14,7 +14,7 @@ export * from './tree'
1414
export * from './git'
1515
export * from './context'
1616
export * from './content'
17-
export * from './components'
17+
export * from './component'
1818

1919
export interface StudioHost {
2020
meta: {
@@ -40,7 +40,7 @@ export interface StudioHost {
4040
getFileSystemPath: (id: string) => string
4141
list: () => Promise<DatabaseItem[]>
4242
upsert: (id: string, upsertedDocument: DatabaseItem) => Promise<void>
43-
create: (fsPath: string, routePath: string, content: string) => Promise<DatabaseItem>
43+
create: (fsPath: string, content: string) => Promise<DatabaseItem>
4444
delete: (id: string) => Promise<void>
4545
detectActives: () => Array<{ id: string, title: string }>
4646
}

src/app/src/utils/content.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ export function removeReservedKeysFromDocument(document: DatabaseItem) {
6363
return result
6464
}
6565

66-
// MARK: - Generate Document - Parse Content
67-
6866
export async function generateDocumentFromContent(id: string, content: string): Promise<DatabaseItem | null> {
6967
const [_id, _hash] = id.split('#')
7068
const extension = _id!.split('.').pop()

0 commit comments

Comments
 (0)