Skip to content

Commit bbe71fa

Browse files
committed
Merge branch 'main' into feat/duplicate-rename-actions
2 parents caac3aa + 2c9b969 commit bbe71fa

31 files changed

+769
-150
lines changed

playground/docus/nuxt.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,12 @@ export default defineNuxtConfig({
1717
},
1818
},
1919
compatibilityDate: '2025-08-26',
20+
contentStudio: {
21+
repository: {
22+
owner: 'nuxt-content',
23+
repo: 'studio',
24+
branch: 'edit-preview',
25+
rootDir: 'playground/docus',
26+
},
27+
},
2028
})

src/app/src/components/panel/base/PanelBaseBodyEditor.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ defineProps({
88
type: Object as PropType<DraftItem>,
99
required: true,
1010
},
11+
readOnly: {
12+
type: Boolean,
13+
required: false,
14+
default: false,
15+
},
1116
})
1217
1318
const { context } = useStudio()
@@ -17,9 +22,11 @@ const { context } = useStudio()
1722
<PanelContentEditor
1823
v-if="context.feature.value === StudioFeature.Content"
1924
:draft-item="draftItem"
25+
:read-only="readOnly"
2026
/>
2127
<PanelMediaEditor
2228
v-else
2329
:draft-item="draftItem"
30+
:read-only="readOnly"
2431
/>
2532
</template>

src/app/src/components/panel/base/PanelBaseFooter.vue

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,30 @@ const { ui, host } = useStudio()
66
77
const uiConfig = ui.config
88
const user = host.user.get()
9+
const repositoryUrl = computed(() => {
10+
switch (host.repository.provider) {
11+
case 'github':
12+
return `https://github.com/${host.repository.owner}/${host.repository.repo}/tree/${host.repository.branch}`
13+
default:
14+
return ''
15+
}
16+
})
917
1018
const userMenuItems = computed(() => [
11-
{
19+
[{
20+
label: 'Open Repository',
21+
icon: 'i-lucide-github',
22+
onClick: () => {
23+
window.open(repositoryUrl.value, '_blank')
24+
},
25+
}],
26+
[{
1227
label: 'Sign out',
1328
icon: 'i-lucide-log-out',
1429
onClick: () => {
1530
alert('TODO: delete cookie manually')
1631
},
17-
},
32+
}],
1833
])
1934
</script>
2035

src/app/src/components/panel/base/PanelBaseHeader.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useStudio } from '../../../composables/useStudio'
33
import { StudioFeature } from '../../../types'
44
import { ROOT_ITEM } from '../../../utils/tree'
55
6-
const { ui, context, documentTree, mediaTree } = useStudio()
6+
const { ui, git, context, documentTree, mediaTree, draftDocuments, draftMedias } = useStudio()
77
88
const features = [{
99
label: 'Content',
@@ -28,6 +28,16 @@ const features = [{
2828
ui.openPanel(StudioFeature.Media)
2929
},
3030
}]
31+
32+
async function publishFiles() {
33+
const files = await Promise.all([
34+
draftDocuments.generateRawFiles(),
35+
draftMedias.generateRawFiles(),
36+
]).then(([documents, medias]) => ([...documents, ...medias]))
37+
38+
const message = window.prompt('Enter a commit message')
39+
await git.commitFiles(files, message || 'Publish files')
40+
}
3141
</script>
3242

3343
<template>
@@ -46,6 +56,7 @@ const features = [{
4656
color="primary"
4757
variant="solid"
4858
size="sm"
59+
@click="publishFiles"
4960
/>
5061
<!-- <USeparator
5162
orientation="vertical"

src/app/src/components/panel/content/editor/PanelContentEditor.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ const props = defineProps({
1010
type: Object as PropType<DraftItem>,
1111
required: true,
1212
},
13+
readOnly: {
14+
type: Boolean,
15+
required: false,
16+
default: false,
17+
},
1318
})
1419
1520
const { draftDocuments } = useStudio()
@@ -38,6 +43,10 @@ const document = computed<DatabasePageItem>({
3843
return result
3944
},
4045
set(value) {
46+
if (props.readOnly) {
47+
return
48+
}
49+
4150
draftDocuments.update(props.draftItem.id, {
4251
...toRaw(document.value as DatabasePageItem),
4352
...toRaw(value),
@@ -51,6 +60,7 @@ const document = computed<DatabasePageItem>({
5160
<PanelContentEditorCode
5261
v-model="document"
5362
:draft-item="draftItem"
63+
:read-only="readOnly"
5464
/>
5565
</div>
5666
<!-- <MDCEditorAST v-model="document" /> -->

src/app/src/components/panel/content/editor/PanelContentEditorCode.vue

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
<script setup lang="ts">
22
import { onMounted, ref, shallowRef, watch } from 'vue'
33
import type { DatabasePageItem, DraftItem } from '../../../../types'
4+
import { DraftStatus } from '../../../../types/draft'
45
import type { PropType } from 'vue'
5-
import { setupMonaco, type Editor } from '../../../../utils/monaco'
6+
import { setupMonaco, setupSuggestion, type Editor } from '../../../../utils/monaco/index'
67
import { generateContentFromDocument, generateDocumentFromContent, pickReservedKeysFromDocument } from '../../../../utils/content'
8+
import { useStudio } from '../../../../composables/useStudio'
79
810
const props = defineProps({
911
draftItem: {
1012
type: Object as PropType<DraftItem>,
1113
required: true,
1214
},
15+
readOnly: {
16+
type: Boolean,
17+
required: false,
18+
default: false,
19+
},
1320
})
1421
1522
const document = defineModel<DatabasePageItem>()
23+
const { mediaTree, host } = useStudio()
1624
1725
const editor = shallowRef<Editor.IStandaloneCodeEditor | null>(null)
1826
const editorRef = ref()
1927
const content = ref<string>('')
2028
const currentDocumentId = ref<string | null>(null)
29+
const localStatus = ref<DraftStatus>(props.draftItem.status)
2130
2231
// Trigger on action events
23-
watch(() => props.draftItem.status, () => {
24-
if (editor.value) {
32+
watch(() => props.draftItem.status, (newStatus) => {
33+
if (editor.value && newStatus !== localStatus.value) {
34+
localStatus.value = newStatus
2535
setContent(props.draftItem.modified as DatabasePageItem)
2636
}
2737
})
@@ -35,10 +45,24 @@ watch(() => document.value?.id, async () => {
3545
3646
onMounted(async () => {
3747
const monaco = await setupMonaco()
48+
setupSuggestion(monaco.monaco, host.meta.components(), mediaTree.root.value)
3849
3950
// create a Monaco editor instance
40-
editor.value = monaco.createEditor(editorRef.value)
51+
editor.value = monaco.createEditor(editorRef.value, {
52+
readOnly: props.readOnly,
53+
scrollbar: props.readOnly
54+
? {
55+
vertical: 'hidden',
56+
horizontal: 'hidden',
57+
handleMouseWheel: false,
58+
}
59+
: undefined,
60+
})
4161
editor.value.onDidChangeModelContent(() => {
62+
if (props.readOnly) {
63+
return
64+
}
65+
4266
// Do not trigger model updates if the document id has changed
4367
if (currentDocumentId.value !== document.value?.id) {
4468
return
@@ -52,6 +76,10 @@ onMounted(async () => {
5276
content.value = newContent
5377
5478
generateDocumentFromContent(document.value!.id, content.value).then((doc) => {
79+
// Update local status
80+
localStatus.value = DraftStatus.Updated
81+
82+
// Update document
5583
document.value = {
5684
...pickReservedKeysFromDocument(props.draftItem.original as DatabasePageItem || document.value!),
5785
...doc,
@@ -68,7 +96,11 @@ function setContent(document: DatabasePageItem) {
6896
content.value = md || ''
6997
7098
if (editor.value && editor.value.getModel()?.getValue() !== md) {
99+
// Keep the cursor position
100+
const position = editor.value.getPosition()
71101
editor.value.getModel()?.setValue(md || '')
102+
// Restore the cursor position
103+
position && editor.value.setPosition(position)
72104
}
73105
74106
currentDocumentId.value = document.id

src/app/src/composables/useContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export const useContext = createSharedComposable((
7676
},
7777
[StudioItemActionId.DeleteItem]: async (id: string) => {
7878
modal.openConfirmActionModal(id, StudioItemActionId.DeleteItem, async () => {
79-
const ids: string[] = findDescendantsFileItemsFromId(tree.root.value, id).map(item => item.id)
79+
const ids = findDescendantsFileItemsFromId(tree.root.value, id).map(item => item.id)
8080
await draft.value.remove(ids)
8181
await tree.selectParentById(id)
8282
})

src/app/src/composables/useDraftDocuments.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { createStorage } from 'unstorage'
22
import indexedDbDriver from 'unstorage/drivers/indexedb'
33
import { ref } from 'vue'
4-
import type { DatabaseItem, DraftItem, StudioHost, GithubFile, DatabasePageItem } from '../types'
4+
import type { DatabaseItem, DraftItem, StudioHost, GithubFile, DatabasePageItem, RawFile } from '../types'
55
import { DraftStatus } from '../types/draft'
66
import type { useGit } from './useGit'
77
import { generateContentFromDocument } from '../utils/content'
88
import { getDraftStatus, findDescendantsFromId } from '../utils/draft'
99
import { createSharedComposable } from '@vueuse/core'
1010
import { useHooks } from './useHooks'
1111
import { stripNumericPrefix } from '../utils/string'
12+
import { joinURL } from 'ufo'
1213

1314
const storage = createStorage({
1415
driver: indexedDbDriver({
15-
dbName: 'nuxt-content-studio-document',
16+
dbName: 'content-studio-document',
1617
storeName: 'drafts',
1718
}),
1819
})
@@ -41,7 +42,7 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git:
4142
}
4243

4344
const fsPath = host.document.getFileSystemPath(document.id)
44-
const githubFile = await git.fetchFile(fsPath, { cached: true }) as GithubFile
45+
const githubFile = await git.fetchFile(joinURL('content', fsPath), { cached: true }) as GithubFile
4546

4647
const item: DraftItem<DatabaseItem> = {
4748
id: document.id,
@@ -119,7 +120,7 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git:
119120
}
120121
else {
121122
// TODO: check if gh file has been updated
122-
const githubFile = await git.fetchFile(fsPath, { cached: true }) as GithubFile
123+
const githubFile = await git.fetchFile(joinURL('content', fsPath), { cached: true }) as GithubFile
123124

124125
deleteDraftItem = {
125126
id,
@@ -277,6 +278,21 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git:
277278
select(draftItem)
278279
}
279280

281+
async function generateRawFiles(): Promise<RawFile[]> {
282+
const files = [] as RawFile[]
283+
for (const draftItem of list.value) {
284+
if (draftItem.status === DraftStatus.Deleted) {
285+
files.push({ path: joinURL('content', draftItem.fsPath), content: null, status: draftItem.status, encoding: 'utf-8' })
286+
continue
287+
}
288+
289+
const content = await generateContentFromDocument(draftItem.modified!)
290+
files.push({ path: joinURL('content', draftItem.fsPath), content: content!, status: draftItem.status, encoding: 'utf-8' })
291+
}
292+
293+
return files
294+
}
295+
280296
return {
281297
get,
282298
create,
@@ -290,5 +306,6 @@ export const useDraftDocuments = createSharedComposable((host: StudioHost, git:
290306
current,
291307
select,
292308
selectById,
309+
generateRawFiles,
293310
}
294311
})

src/app/src/composables/useDraftMedias.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ref } from 'vue'
22
import { createStorage } from 'unstorage'
33
import indexedDbDriver from 'unstorage/drivers/indexedb'
44
import { joinURL, withLeadingSlash } from 'ufo'
5-
import type { DraftItem, StudioHost, GithubFile, MediaItem } from '../types'
5+
import type { DraftItem, StudioHost, GithubFile, MediaItem, RawFile } from '../types'
66
import { DraftStatus } from '../types/draft'
77
import type { useGit } from './useGit'
88
import { getDraftStatus } from '../utils/draft'
@@ -11,7 +11,7 @@ import { useHooks } from './useHooks'
1111

1212
const storage = createStorage({
1313
driver: indexedDbDriver({
14-
dbName: 'nuxt-content-studio-media',
14+
dbName: 'content-studio-media',
1515
storeName: 'drafts',
1616
}),
1717
})
@@ -34,7 +34,7 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret
3434
}
3535

3636
const fsPath = host.media.getFileSystemPath(media.id)
37-
const githubFile = await git.fetchFile(fsPath, { cached: true }) as GithubFile
37+
const githubFile = await git.fetchFile(joinURL('public', fsPath), { cached: true }) as GithubFile
3838

3939
const item: DraftItem = {
4040
id: media.id,
@@ -95,7 +95,7 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret
9595
}
9696
else {
9797
// Fetch github file before creating draft to detect non deployed changes
98-
const githubFile = await git.fetchFile(fsPath, { cached: true }) as GithubFile
98+
const githubFile = await git.fetchFile(joinURL('public', fsPath), { cached: true }) as GithubFile
9999
const original = await host.media.get(id)
100100

101101
const deleteItem: DraftItem = {
@@ -312,6 +312,21 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret
312312
})
313313
}
314314

315+
async function generateRawFiles(): Promise<RawFile[]> {
316+
const files = [] as RawFile[]
317+
for (const draftItem of list.value) {
318+
if (draftItem.status === DraftStatus.Deleted) {
319+
files.push({ path: joinURL('public', draftItem.fsPath), content: null, status: draftItem.status, encoding: 'base64' })
320+
continue
321+
}
322+
323+
const content = (await draftItem.modified?.raw as string).replace(/^data:\w+\/\w+;base64,/, '')
324+
files.push({ path: joinURL('public', draftItem.fsPath), content, status: draftItem.status, encoding: 'base64' })
325+
}
326+
327+
return files
328+
}
329+
315330
return {
316331
get,
317332
create,
@@ -326,5 +341,6 @@ export const useDraftMedias = createSharedComposable((host: StudioHost, git: Ret
326341
select,
327342
selectById,
328343
upload,
344+
generateRawFiles,
329345
}
330346
})

0 commit comments

Comments
 (0)