Skip to content

Commit f2a21d3

Browse files
committed
fix: content matching
1 parent 4f2369e commit f2a21d3

File tree

13 files changed

+2166
-1819
lines changed

13 files changed

+2166
-1819
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"@iconify-json/simple-icons": "^1.2.57",
5656
"@nuxt/content": "^3.8.0",
5757
"@nuxt/eslint-config": "^1.10.0",
58-
"@nuxt/kit": "^4.2.0",
58+
"@nuxt/kit": "^4.2.1",
5959
"@nuxt/module-builder": "^1.0.2",
6060
"@nuxt/ui": "^4.1.0",
6161
"@octokit/types": "^15.0.1",
@@ -86,7 +86,7 @@
8686
"remark-mdc": "3.8.1",
8787
"@nuxtjs/mdc": "https://pkg.pr.new/@nuxtjs/mdc@cb6a227"
8888
},
89-
"packageManager": "pnpm@10.20.0",
89+
"packageManager": "pnpm@10.21.0",
9090
"keywords": [
9191
"nuxt",
9292
"content",

playground/docus/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"dependencies": {
99
"docus": "^5.2.1",
1010
"better-sqlite3": "^12.4.1",
11-
"nuxt": "latest",
11+
"nuxt": "^4.2.1",
1212
"@nuxt/content": "latest",
1313
"@nuxt/ui": "4.1.0",
1414
"nuxt-studio": "workspace:*"

pnpm-lock.yaml

Lines changed: 1853 additions & 1647 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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { computed, ref, watch } from 'vue'
33
import { ContentFileExtension, type DatabasePageItem, type DraftItem, DraftStatus, type DatabaseItem } from '../../types'
44
import type { PropType } from 'vue'
5-
import { generateContentFromDocument, generateDocumentFromContent, isEqual, pickReservedKeysFromDocument } from '../../utils/content'
5+
import { generateContentFromDocument, isEqual, pickReservedKeysFromDocument } from '../../utils/content'
66
import { setupSuggestion } from '../../utils/monaco'
77
import { useStudio } from '../../composables/useStudio'
88
import { useMonaco } from '../../composables/useMonaco'
@@ -67,7 +67,7 @@ const { editor, setContent: setEditorContent } = useMonaco(editorRef, {
6767
6868
content.value = newContent
6969
70-
generateDocumentFromContent(document.value!.id, content.value).then((doc) => {
70+
host.document.generateDocumentFromContent(document.value!.id, content.value).then((doc) => {
7171
localStatus.value = DraftStatus.Updated
7272
7373
document.value = {

src/app/src/composables/useDraftBase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function useDraftBase<T extends DatabaseItem | MediaItem>(
4646
draftItem.original = original
4747
}
4848

49-
const conflict = await checkConflict(draftItem)
49+
const conflict = await checkConflict(host, draftItem)
5050
if (conflict) {
5151
draftItem.conflict = conflict
5252
}

src/app/src/shared.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { generateContentFromDocument, generateDocumentFromContent, removeReservedKeysFromDocument } from './utils/content'
1+
export { generateContentFromDocument, removeReservedKeysFromDocument } from './utils/content'
22
export { VirtualMediaCollectionName } from './utils/media'

src/app/src/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export interface StudioHost {
4747
create: (fsPath: string, content: string) => Promise<DatabaseItem>
4848
delete: (fsPath: string) => Promise<void>
4949
detectActives: () => Array<{ fsPath: string, title: string }>
50+
isEqual: (content: string, document: DatabaseItem) => Promise<boolean>
51+
generateDocumentFromContent: (id: string, content: string) => Promise<DatabaseItem>
5052
}
5153
media: {
5254
get: (fsPath: string) => Promise<MediaItem>

src/app/src/utils/content.ts

Lines changed: 3 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
// import type { ParsedContentFile } from '@nuxt/content'
2-
import { parseMarkdown, stringifyMarkdown } from '@nuxtjs/mdc/runtime'
3-
import { parseFrontMatter, stringifyFrontMatter } from 'remark-mdc'
2+
import { stringifyMarkdown } from '@nuxtjs/mdc/runtime'
3+
import { stringifyFrontMatter } from 'remark-mdc'
44
import type { MDCElement, MDCRoot } from '@nuxtjs/mdc'
55
import { type DatabasePageItem, type DatabaseItem, ContentFileExtension } from '../types'
66
import { omit, pick } from './object'
7-
import { compressTree, decompressTree } from '@nuxt/content/runtime'
7+
import { decompressTree } from '@nuxt/content/runtime'
88
import { visit } from 'unist-util-visit'
99
import type { Node } from 'unist'
10-
import type { MarkdownRoot } from '@nuxt/content'
11-
import { destr } from 'destr'
1210
import { getFileExtension } from './file'
1311

1412
const reservedKeys = ['id', 'fsPath', 'stem', 'extension', '__hash__', 'path', 'body', 'meta', 'rawbody']
1513

16-
export function generateStemFromId(id: string) {
17-
return id.split('/').slice(1).join('/').split('.').slice(0, -1).join('.')
18-
}
19-
2014
export function pickReservedKeysFromDocument(document: DatabaseItem) {
2115
return pick(document, reservedKeys)
2216
}
@@ -69,102 +63,6 @@ export function isEqual(content1: string | null, content2: string | null): boole
6963
return false
7064
}
7165

72-
export async function generateDocumentFromContent(id: string, content: string): Promise<DatabaseItem | null> {
73-
const [_id, _hash] = id.split('#')
74-
const extension = getFileExtension(id)
75-
76-
if (extension === ContentFileExtension.Markdown) {
77-
return await generateDocumentFromMarkdownContent(id, content)
78-
}
79-
80-
if (extension === ContentFileExtension.YAML || extension === ContentFileExtension.YML) {
81-
return await generateDocumentFromYAMLContent(id, content)
82-
}
83-
84-
if (extension === ContentFileExtension.JSON) {
85-
return await generateDocumentFromJSONContent(id, content)
86-
}
87-
88-
return null
89-
}
90-
91-
export async function generateDocumentFromYAMLContent(id: string, content: string): Promise<DatabaseItem> {
92-
const { data } = parseFrontMatter(`---\n${content}\n---`)
93-
94-
// Keep array contents under `body` key
95-
let parsed = data
96-
if (Array.isArray(data)) {
97-
console.warn(`YAML array is not supported in ${id}, moving the array into the \`body\` key`)
98-
parsed = { body: data }
99-
}
100-
101-
return {
102-
id,
103-
extension: getFileExtension(id),
104-
stem: generateStemFromId(id),
105-
meta: {},
106-
...parsed,
107-
body: parsed.body || parsed,
108-
} as never as DatabaseItem
109-
}
110-
111-
export async function generateDocumentFromJSONContent(id: string, content: string): Promise<DatabaseItem> {
112-
let parsed: Record<string, unknown> = destr(content)
113-
114-
// Keep array contents under `body` key
115-
if (Array.isArray(parsed)) {
116-
console.warn(`JSON array is not supported in ${id}, moving the array into the \`body\` key`)
117-
parsed = {
118-
body: parsed,
119-
}
120-
}
121-
122-
// fsPath will be overridden by host
123-
return {
124-
id,
125-
extension: ContentFileExtension.JSON,
126-
stem: generateStemFromId(id),
127-
meta: {},
128-
...parsed,
129-
body: parsed.body || parsed,
130-
} as never as DatabaseItem
131-
}
132-
133-
export async function generateDocumentFromMarkdownContent(id: string, content: string): Promise<DatabaseItem> {
134-
const document = await parseMarkdown(content, {
135-
remark: {
136-
plugins: {
137-
'remark-mdc': {
138-
options: {
139-
autoUnwrap: true,
140-
},
141-
},
142-
},
143-
},
144-
})
145-
146-
// Remove nofollow from links
147-
visit(document.body, (node: Node) => (node as MDCElement).type === 'element' && (node as MDCElement).tag === 'a', (node: Node) => {
148-
if ((node as MDCElement).props?.rel?.join(' ') === 'nofollow') {
149-
Reflect.deleteProperty((node as MDCElement).props!, 'rel')
150-
}
151-
})
152-
153-
const body = document.body.type === 'root' ? compressTree(document.body) : document.body as never as MarkdownRoot
154-
155-
return {
156-
id,
157-
meta: {},
158-
extension: ContentFileExtension.Markdown,
159-
stem: generateStemFromId(id),
160-
body: {
161-
...body,
162-
toc: document.toc,
163-
},
164-
...document.data,
165-
} as never as DatabaseItem
166-
}
167-
16866
export async function generateContentFromDocument(document: DatabaseItem): Promise<string | null> {
16967
const [id, _hash] = document.id.split('#')
17068
const extension = getFileExtension(id)

src/app/src/utils/draft.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { DatabaseItem, MediaItem, DatabasePageItem, DraftItem, BaseItem, ContentConflict } from '../types'
1+
import type { DatabaseItem, MediaItem, DatabasePageItem, DraftItem, BaseItem, ContentConflict, StudioHost } from '../types'
22
import { DraftStatus, ContentFileExtension } from '../types'
33
import { isEqual } from './database'
44
import { studioFlags } from '../composables/useStudio'
5-
import { generateContentFromDocument, generateDocumentFromContent } from './content'
5+
import { generateContentFromDocument } from './content'
66
import { fromBase64ToUTF8 } from '../utils/string'
77
import { isMediaFile } from './file'
88

9-
export async function checkConflict(draftItem: DraftItem<DatabaseItem | MediaItem>): Promise<ContentConflict | undefined> {
9+
export async function checkConflict(host: StudioHost, draftItem: DraftItem<DatabaseItem | MediaItem>): Promise<ContentConflict | undefined> {
1010
if (isMediaFile(draftItem.fsPath) || draftItem.fsPath.endsWith('.gitkeep')) {
1111
return
1212
}
@@ -27,14 +27,13 @@ export async function checkConflict(draftItem: DraftItem<DatabaseItem | MediaIte
2727
return
2828
}
2929

30-
const localContent = await generateContentFromDocument(draftItem.original as DatabaseItem) as string
3130
const githubContent = fromBase64ToUTF8(draftItem.githubFile.content)
32-
const githubDocument = await generateDocumentFromContent(draftItem.modified!.id!, githubContent) as DatabaseItem
3331

34-
if (isEqual(draftItem.original as DatabasePageItem, githubDocument as DatabasePageItem)) {
32+
if (await host.document.isEqual(githubContent, draftItem.original! as DatabaseItem)) {
3533
return
3634
}
3735

36+
const localContent = await generateContentFromDocument(draftItem.original as DatabaseItem) as string
3837
if (localContent.trim() === githubContent.trim()) {
3938
return
4039
}

src/module/src/runtime/host.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import { ensure } from './utils/ensure'
33
import type { CollectionInfo, CollectionItemBase, CollectionSource, DatabaseAdapter } from '@nuxt/content'
44
import type { ContentDatabaseAdapter } from '../types/content'
55
import { getCollectionByFilePath, generateIdFromFsPath, generateRecordDeletion, generateRecordInsert, generateFsPathFromId, getCollectionById } from './utils/collection'
6-
import { createCollectionDocument, normalizeDocument } from './utils/document'
6+
import { createCollectionDocument, isDocumentMatchContent, normalizeDocument } from './utils/document'
77
import { kebabCase } from 'scule'
88
import type { StudioHost, StudioUser, DatabaseItem, MediaItem, Repository } from 'nuxt-studio/app'
99
import type { RouteLocationNormalized, Router } from 'vue-router'
10-
import { generateDocumentFromContent } from 'nuxt-studio/app/utils'
1110
// @ts-expect-error queryCollection is not defined in .nuxt/imports.d.ts
1211
import { clearError, getAppManifest, queryCollection, queryCollectionItemSurroundings, queryCollectionNavigation, queryCollectionSearchSections } from '#imports'
1312
import { collections } from '#content/preview'
1413
import { publicAssetsStorage } from '#build/studio-public-assets'
1514
import { useHostMeta } from './composables/useMeta'
15+
import { generateDocumentFromContent } from './utils/document'
1616
import { generateIdFromFsPath as generateMediaIdFromFsPath } from './utils/media'
1717
import { getCollectionSourceById } from './utils/source'
1818

@@ -273,6 +273,8 @@ export function useStudioHost(user: StudioUser, repository: Repository): StudioH
273273
}
274274
})
275275
},
276+
isEqual: async (content: string, document: DatabaseItem) => isDocumentMatchContent(content, document),
277+
generateDocumentFromContent: async (id: string, content: string) => generateDocumentFromContent(id, content) as Promise<DatabaseItem>,
276278
},
277279

278280
media: {

0 commit comments

Comments
 (0)