diff --git a/dev/import-tool/src/notion.ts b/dev/import-tool/src/notion.ts index a25555f27b..c07da93685 100644 --- a/dev/import-tool/src/notion.ts +++ b/dev/import-tool/src/notion.ts @@ -326,7 +326,7 @@ async function createDBPageWithAttachments ( documentMetaMap?: Map ): Promise { const pageId = docMeta.id as Ref - const collabId = makeCollaborativeDoc(pageId, 'description') + const collabId = makeCollaborativeDoc(pageId, 'content') const parentId = parentMeta !== undefined ? (parentMeta.id as Ref) : document.ids.NoParent @@ -335,7 +335,7 @@ async function createDBPageWithAttachments ( const object: Data = { title: docMeta.name, - description: collabId, + content: collabId, parent: parentId, attachments: 0, embeddings: 0, @@ -482,7 +482,7 @@ async function importPageDocument ( const attachedData: Data = { title: docMeta.name, - description: collabId, + content: collabId, parent, attachments: 0, embeddings: 0, diff --git a/models/document/src/index.ts b/models/document/src/index.ts index 5bea148225..6fe9c9db38 100644 --- a/models/document/src/index.ts +++ b/models/document/src/index.ts @@ -41,7 +41,7 @@ import { } from '@hcengineering/model' import attachment, { TAttachment } from '@hcengineering/model-attachment' import chunter from '@hcengineering/model-chunter' -import core, { TCard, TTypedSpace } from '@hcengineering/model-core' +import core, { TDoc, TTypedSpace } from '@hcengineering/model-core' import { createPublicLinkAction } from '@hcengineering/model-guest' import { generateClassNotificationTypes } from '@hcengineering/model-notification' import preference, { TPreference } from '@hcengineering/model-preference' @@ -69,24 +69,24 @@ export class TDocumentEmbedding extends TAttachment implements DocumentEmbedding declare attachedToClass: Ref> } -@Model(document.class.Document, core.class.Card, DOMAIN_DOCUMENT) +@Model(document.class.Document, core.class.Doc, DOMAIN_DOCUMENT) @UX(document.string.Document, document.icon.Document, undefined, 'name', undefined, document.string.Documents) -export class TDocument extends TCard implements Document, Todoable { +export class TDocument extends TDoc implements Document, Todoable { + @Prop(TypeString(), document.string.Name) + @Index(IndexKind.FullText) + title!: string + + @Prop(TypeCollaborativeDoc(), document.string.Document) + content!: CollaborativeDoc + @Prop(TypeRef(document.class.Document), document.string.ParentDocument) - declare parent: Ref + parent!: Ref @Prop(TypeRef(core.class.Space), core.string.Space) @Index(IndexKind.Indexed) @Hidden() declare space: Ref - @Prop(TypeString(), document.string.Name) - @Index(IndexKind.FullText) - declare title: string - - @Prop(TypeCollaborativeDoc(), document.string.Document) - declare description: CollaborativeDoc - @Prop(TypeRef(core.class.Account), document.string.LockedBy) @Hidden() lockedBy?: Ref @@ -125,12 +125,9 @@ export class TDocument extends TCard implements Document, Todoable { rank!: Rank } -@Model(document.class.DocumentSnapshot, core.class.Card, DOMAIN_DOCUMENT) +@Model(document.class.DocumentSnapshot, core.class.Doc, DOMAIN_DOCUMENT) @UX(document.string.Version) -export class TDocumentSnapshot extends TCard implements DocumentSnapshot { - @Prop(TypeRef(document.class.Document), document.string.ParentDocument) - declare parent: Ref - +export class TDocumentSnapshot extends TDoc implements DocumentSnapshot { @Prop(TypeRef(core.class.Space), core.string.Space) @Index(IndexKind.Indexed) @Hidden() @@ -138,11 +135,13 @@ export class TDocumentSnapshot extends TCard implements DocumentSnapshot { @Prop(TypeString(), document.string.Name) @Index(IndexKind.FullText) - declare title: string + title!: string @Prop(TypeCollaborativeDocVersion(), document.string.Document) - @Hidden() - declare description: CollaborativeDoc + content!: CollaborativeDoc + + @Prop(TypeRef(document.class.Document), document.string.ParentDocument) + parent!: Ref } @Model(document.class.SavedDocument, preference.class.Preference) @@ -444,7 +443,7 @@ function defineDocument (builder: Builder): void { allowedForAuthor: false, label: document.string.Document, group: document.ids.DocumentNotificationGroup, - field: 'description', + field: 'content', txClasses: [core.class.TxUpdateDoc], objectClass: document.class.Document, defaultEnabled: false, diff --git a/models/document/src/migration.ts b/models/document/src/migration.ts index 84ed089391..39792b4b56 100644 --- a/models/document/src/migration.ts +++ b/models/document/src/migration.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import { DOMAIN_TX, MeasureMetricsContext, SortingOrder } from '@hcengineering/core' +import { type CollaborativeDoc, DOMAIN_TX, MeasureMetricsContext, SortingOrder } from '@hcengineering/core' import { type DocumentSnapshot, type Document, type Teamspace } from '@hcengineering/document' import { tryMigrate, @@ -111,7 +111,7 @@ async function migrateContentField (client: MigrationClient): Promise { for (const document of documents) { try { - const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.description, ctx) + const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.content, ctx) if (ydoc === undefined) { ctx.error('document content not found', { document: document.title }) continue @@ -123,7 +123,7 @@ async function migrateContentField (client: MigrationClient): Promise { yDocCopyXmlField(ydoc, '', 'content') - await saveCollaborativeDoc(storage, client.workspaceId, document.description, ydoc, ctx) + await saveCollaborativeDoc(storage, client.workspaceId, document.content, ydoc, ctx) } catch (err) { ctx.error('error document content migration', { error: err, document: document.title }) } @@ -202,6 +202,68 @@ async function renameFields (client: MigrationClient): Promise { } } +async function renameFieldsRevert (client: MigrationClient): Promise { + const ctx = new MeasureMetricsContext('renameFieldsRevert', {}) + const storage = client.storageAdapter + + type ExDocument = Document & { + description: CollaborativeDoc + } + + const documents = await client.find(DOMAIN_DOCUMENT, { + _class: document.class.Document, + description: { $exists: true } + }) + + for (const document of documents) { + await client.update( + DOMAIN_DOCUMENT, + { _id: document._id }, + { + $rename: { + description: 'content' + } + } + ) + + if (document.description.includes('%description:')) { + try { + const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.description, ctx) + if (ydoc === undefined) { + continue + } + + if (!ydoc.share.has('description') || ydoc.share.has('content')) { + continue + } + + yDocCopyXmlField(ydoc, 'description', 'content') + + await saveCollaborativeDoc(storage, client.workspaceId, document.description, ydoc, ctx) + } catch (err) { + ctx.error('error document content migration', { error: err, document: document.title }) + } + } + } + + const snapshots = await client.find(DOMAIN_DOCUMENT, { + _class: document.class.DocumentSnapshot, + description: { $exists: true } + }) + + for (const snapshot of snapshots) { + await client.update( + DOMAIN_DOCUMENT, + { _id: snapshot._id }, + { + $rename: { + description: 'content' + } + } + ) + } +} + export const documentOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await tryMigrate(client, documentId, [ @@ -234,6 +296,10 @@ export const documentOperation: MigrateOperation = { func: async (client: MigrationClient): Promise => { await client.update(DOMAIN_DOCUMENT, { '%hash%': { $exists: true } }, { $set: { '%hash%': null } }) } + }, + { + state: 'renameFieldsRevert', + func: renameFieldsRevert } ]) }, diff --git a/models/server-document/src/index.ts b/models/server-document/src/index.ts index ad51427a03..10bd0b9f71 100644 --- a/models/server-document/src/index.ts +++ b/models/server-document/src/index.ts @@ -5,9 +5,9 @@ import { type Builder } from '@hcengineering/model' -import core from '@hcengineering/core' +import core, { type Class, type Doc } from '@hcengineering/core' import document from '@hcengineering/document' -import serverCore from '@hcengineering/server-core' +import serverCore, { type ObjectDDParticipant } from '@hcengineering/server-core' import serverDocument from '@hcengineering/server-document' import serverNotification from '@hcengineering/server-notification' import serverView from '@hcengineering/server-view' @@ -36,4 +36,13 @@ export function createModel (builder: Builder): void { title: 'title' } }) + + builder.mixin, ObjectDDParticipant>( + document.class.Document, + core.class.Class, + serverCore.mixin.ObjectDDParticipant, + { + collectDocs: serverDocument.function.FindChildDocuments + } + ) } diff --git a/plugins/document-resources/src/components/DocumentEditor.svelte b/plugins/document-resources/src/components/DocumentEditor.svelte index de5546a934..afc26cc971 100644 --- a/plugins/document-resources/src/components/DocumentEditor.svelte +++ b/plugins/document-resources/src/components/DocumentEditor.svelte @@ -56,12 +56,12 @@ = { title, - description: makeCollaborativeDoc(id, 'description'), + content: makeCollaborativeDoc(id, 'content'), attachments: 0, embeddings: 0, labels: 0, diff --git a/plugins/document/src/types.ts b/plugins/document/src/types.ts index f0675ccd7f..8b650bcc49 100644 --- a/plugins/document/src/types.ts +++ b/plugins/document/src/types.ts @@ -14,7 +14,7 @@ // import { Attachment } from '@hcengineering/attachment' -import { Account, Card, Class, CollaborativeDoc, Rank, Ref, TypedSpace } from '@hcengineering/core' +import { Account, Class, CollaborativeDoc, Doc, Rank, Ref, TypedSpace } from '@hcengineering/core' import { Preference } from '@hcengineering/preference' import { IconProps } from '@hcengineering/view' @@ -22,11 +22,10 @@ import { IconProps } from '@hcengineering/view' export interface Teamspace extends TypedSpace, IconProps {} /** @public */ -export interface Document extends Card, IconProps { +export interface Document extends Doc, IconProps { + title: string + content: CollaborativeDoc parent: Ref - - description: CollaborativeDoc - space: Ref lockedBy?: Ref | null @@ -42,10 +41,10 @@ export interface Document extends Card, IconProps { } /** @public */ -export interface DocumentSnapshot extends Card { - parent: Ref +export interface DocumentSnapshot extends Doc { title: string - description: CollaborativeDoc + content: CollaborativeDoc + parent: Ref } /** @public */ diff --git a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte index 934667367c..aaa4a66110 100644 --- a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte +++ b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte @@ -365,15 +365,7 @@ remoteProvider.awareness?.setLocalStateField('lastUpdate', Date.now()) } - function parseField (collaborativeDoc: CollaborativeDoc): string | undefined { - if (collaborativeDoc === undefined) return undefined - const _id = collaborativeDoc.split(':') - if (_id === undefined) return undefined - return _id[0]?.split('%')?.[1] - } - onMount(async () => { - const _field = parseField(collaborativeDoc) ?? field await ph editor = new Editor({ @@ -406,7 +398,7 @@ Placeholder.configure({ placeholder: placeHolderStr }), Collaboration.configure({ document: ydoc, - field: _field + field }), CollaborationCursor.configure({ provider: remoteProvider, diff --git a/server-plugins/document-resources/src/index.ts b/server-plugins/document-resources/src/index.ts index 14a1941020..849ea9a77d 100644 --- a/server-plugins/document-resources/src/index.ts +++ b/server-plugins/document-resources/src/index.ts @@ -3,8 +3,8 @@ // // -import { Doc, concatLink } from '@hcengineering/core' -import { Document, documentId } from '@hcengineering/document' +import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref, concatLink } from '@hcengineering/core' +import document, { Document, documentId } from '@hcengineering/document' import { getMetadata } from '@hcengineering/platform' import { workbenchId } from '@hcengineering/workbench' import serverCore, { TriggerControl } from '@hcengineering/server-core' @@ -38,11 +38,27 @@ export async function documentTextPresenter (doc: Doc): Promise { return document.title } +/** + * @public + */ +export async function findChildDocuments ( + doc: Doc, + hiearachy: Hierarchy, + findAll: ( + clazz: Ref>, + query: DocumentQuery, + options?: FindOptions + ) => Promise> +): Promise { + return await findAll(document.class.Document, { parent: doc._id as Ref }) +} + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ function: { DocumentHTMLPresenter: documentHTMLPresenter, DocumentTextPresenter: documentTextPresenter, - DocumentLinkIdProvider: documentLinkIdProvider + DocumentLinkIdProvider: documentLinkIdProvider, + FindChildDocuments: findChildDocuments } }) diff --git a/server-plugins/document/src/index.ts b/server-plugins/document/src/index.ts index 3c255fc8b0..b4c28c5665 100644 --- a/server-plugins/document/src/index.ts +++ b/server-plugins/document/src/index.ts @@ -6,6 +6,7 @@ import { Doc } from '@hcengineering/core' import type { Plugin, Resource } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform' +import { ObjectDDParticipantFunc } from '@hcengineering/server-core' import { Presenter } from '@hcengineering/server-notification' /** @@ -20,6 +21,7 @@ export default plugin(serverDocumentId, { function: { DocumentHTMLPresenter: '' as Resource, DocumentTextPresenter: '' as Resource, - DocumentLinkIdProvider: '' as Resource<(doc: Doc) => Promise> + DocumentLinkIdProvider: '' as Resource<(doc: Doc) => Promise>, + FindChildDocuments: '' as Resource } })