Indexer step 1 (#6798)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2024-10-04 15:07:02 +05:00 committed by GitHub
parent ec696d029b
commit 70a1666a57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 207 additions and 149 deletions

View File

@ -19,7 +19,8 @@ import {
makeCollaborativeDoc, makeCollaborativeDoc,
type TxOperations, type TxOperations,
type Blob, type Blob,
collaborativeDocParse collaborativeDocParse,
type Data
} from '@hcengineering/core' } from '@hcengineering/core'
import { yDocToBuffer } from '@hcengineering/collaboration' import { yDocToBuffer } from '@hcengineering/collaboration'
import document, { type Document, type Teamspace, getFirstRank } from '@hcengineering/document' import document, { type Document, type Teamspace, getFirstRank } from '@hcengineering/document'
@ -325,16 +326,17 @@ async function createDBPageWithAttachments (
documentMetaMap?: Map<string, DocumentMetadata> documentMetaMap?: Map<string, DocumentMetadata>
): Promise<void> { ): Promise<void> {
const pageId = docMeta.id as Ref<Document> const pageId = docMeta.id as Ref<Document>
const collabId = makeCollaborativeDoc(pageId, 'content') const collabId = makeCollaborativeDoc(pageId, 'description')
const parentId = parentMeta !== undefined ? (parentMeta.id as Ref<Document>) : document.ids.NoParent const parentId = parentMeta !== undefined ? (parentMeta.id as Ref<Document>) : document.ids.NoParent
const lastRank = await getFirstRank(client, space, parentId) const lastRank = await getFirstRank(client, space, parentId)
const rank = makeRank(lastRank, undefined) const rank = makeRank(lastRank, undefined)
const object: AttachedData<Document> = { const object: Data<Document> = {
name: docMeta.name, title: docMeta.name,
content: collabId, description: collabId,
parent: parentId,
attachments: 0, attachments: 0,
children: 0, children: 0,
embeddings: 0, embeddings: 0,
@ -344,15 +346,7 @@ async function createDBPageWithAttachments (
rank rank
} }
await client.addCollection( await client.createDoc(document.class.Document, space, object, pageId)
document.class.Document,
space,
parentId,
document.class.Document,
'children',
object,
pageId
)
const dbPage: DocumentMetadata = { const dbPage: DocumentMetadata = {
id: pageId, id: pageId,
@ -466,7 +460,7 @@ async function importPageDocument (
} }
const id = docMeta.id as Ref<Document> const id = docMeta.id as Ref<Document>
const collabId = makeCollaborativeDoc(id, 'content') const collabId = makeCollaborativeDoc(id, 'description')
const yDoc = jsonToYDocNoSchema(json, 'content') const yDoc = jsonToYDocNoSchema(json, 'content')
const { documentId } = collaborativeDocParse(collabId) const { documentId } = collaborativeDocParse(collabId)
const buffer = yDocToBuffer(yDoc) const buffer = yDocToBuffer(yDoc)
@ -482,14 +476,15 @@ async function importPageDocument (
await uploadFile(docMeta.id, form) await uploadFile(docMeta.id, form)
const parentId = parentMeta?.id ?? document.ids.NoParent const parent = (parentMeta?.id as Ref<Document>) ?? document.ids.NoParent
const lastRank = await getFirstRank(client, space, parentId as Ref<Document>) const lastRank = await getFirstRank(client, space, parent)
const rank = makeRank(lastRank, undefined) const rank = makeRank(lastRank, undefined)
const attachedData: AttachedData<Document> = { const attachedData: Data<Document> = {
name: docMeta.name, title: docMeta.name,
content: collabId, description: collabId,
parent,
attachments: 0, attachments: 0,
children: 0, children: 0,
embeddings: 0, embeddings: 0,
@ -499,15 +494,7 @@ async function importPageDocument (
rank rank
} }
await client.addCollection( await client.createDoc(document.class.Document, space, attachedData, id)
document.class.Document,
space,
parentId as Ref<Document>,
document.class.Document,
'children',
attachedData,
id
)
} }
function preProcessMarkdown (json: MarkupNode, documentMetaMap: Map<string, DocumentMetadata>): void { function preProcessMarkdown (json: MarkupNode, documentMetaMap: Map<string, DocumentMetadata>): void {

View File

@ -14,6 +14,8 @@
// //
import { import {
type Card,
type CollaborativeDoc,
DOMAIN_BLOB, DOMAIN_BLOB,
DOMAIN_CONFIGURATION, DOMAIN_CONFIGURATION,
DOMAIN_DOC_INDEX_STATE, DOMAIN_DOC_INDEX_STATE,
@ -60,6 +62,7 @@ import {
Prop, Prop,
ReadOnly, ReadOnly,
TypeBoolean, TypeBoolean,
TypeCollaborativeDoc,
TypeFileSize, TypeFileSize,
TypeIntlString, TypeIntlString,
TypeRecord, TypeRecord,
@ -111,6 +114,22 @@ export class TDoc extends TObj implements Doc {
createdOn!: Timestamp createdOn!: Timestamp
} }
@Model(core.class.Card, core.class.Obj)
@UX(core.string.Object)
export class TCard extends TDoc implements Card {
@Prop(TypeString(), core.string.Name)
title!: string
@Prop(TypeCollaborativeDoc(), core.string.Description)
description!: CollaborativeDoc | null
@Prop(TypeString(), core.string.Id)
identifier?: string | undefined
@Prop(TypeRef(core.class.Card), core.string.AttachedTo)
parent?: Ref<Card> | null
}
@Model(core.class.AttachedDoc, core.class.Doc) @Model(core.class.AttachedDoc, core.class.Doc)
export class TAttachedDoc extends TDoc implements AttachedDoc { export class TAttachedDoc extends TDoc implements AttachedDoc {
@Prop(TypeRef(core.class.Doc), core.string.AttachedTo) @Prop(TypeRef(core.class.Doc), core.string.AttachedTo)

View File

@ -44,6 +44,7 @@ import {
TConfiguration, TConfiguration,
TConfigurationElement, TConfigurationElement,
TDoc, TDoc,
TCard,
TDocIndexState, TDocIndexState,
TDomainIndexConfiguration, TDomainIndexConfiguration,
TEnum, TEnum,
@ -160,6 +161,7 @@ export function createModel (builder: Builder): void {
TEnum, TEnum,
TTypeAny, TTypeAny,
TTypeRelatedDocument, TTypeRelatedDocument,
TCard,
TDocIndexState, TDocIndexState,
TFullTextSearchContext, TFullTextSearchContext,
TConfiguration, TConfiguration,

View File

@ -15,7 +15,7 @@
import activity from '@hcengineering/activity' import activity from '@hcengineering/activity'
import type { Class, CollaborativeDoc, CollectionSize, Domain, Rank, Role, RolesAssignment } from '@hcengineering/core' import type { Class, CollaborativeDoc, CollectionSize, Domain, Rank, Role, RolesAssignment } from '@hcengineering/core'
import { IndexKind, Account, Ref, AccountRole } from '@hcengineering/core' import { Account, AccountRole, IndexKind, Ref } from '@hcengineering/core'
import { import {
type Document, type Document,
type DocumentEmbedding, type DocumentEmbedding,
@ -29,19 +29,19 @@ import {
Collection, Collection,
Hidden, Hidden,
Index, Index,
Mixin,
Model, Model,
Prop, Prop,
TypeCollaborativeDoc,
TypeCollaborativeDocVersion,
TypeNumber, TypeNumber,
TypeRef, TypeRef,
TypeString, TypeString,
UX, UX
TypeCollaborativeDoc,
TypeCollaborativeDocVersion,
Mixin
} from '@hcengineering/model' } from '@hcengineering/model'
import attachment, { TAttachment } from '@hcengineering/model-attachment' import attachment, { TAttachment } from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter' import chunter from '@hcengineering/model-chunter'
import core, { TAttachedDoc, TTypedSpace } from '@hcengineering/model-core' import core, { TCard, TTypedSpace } from '@hcengineering/model-core'
import { createPublicLinkAction } from '@hcengineering/model-guest' import { createPublicLinkAction } from '@hcengineering/model-guest'
import { generateClassNotificationTypes } from '@hcengineering/model-notification' import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import preference, { TPreference } from '@hcengineering/model-preference' import preference, { TPreference } from '@hcengineering/model-preference'
@ -50,7 +50,7 @@ import tracker from '@hcengineering/model-tracker'
import view, { actionTemplates, createAction } from '@hcengineering/model-view' import view, { actionTemplates, createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench' import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification' import notification from '@hcengineering/notification'
import { getEmbeddedLabel, type Asset } from '@hcengineering/platform' import { type Asset, getEmbeddedLabel } from '@hcengineering/platform'
import tags from '@hcengineering/tags' import tags from '@hcengineering/tags'
import time, { type ToDo, type Todoable } from '@hcengineering/time' import time, { type ToDo, type Todoable } from '@hcengineering/time'
import document from './plugin' import document from './plugin'
@ -69,31 +69,23 @@ export class TDocumentEmbedding extends TAttachment implements DocumentEmbedding
declare attachedToClass: Ref<Class<Document>> declare attachedToClass: Ref<Class<Document>>
} }
@Model(document.class.Document, core.class.AttachedDoc, DOMAIN_DOCUMENT) @Model(document.class.Document, core.class.Card, DOMAIN_DOCUMENT)
@UX(document.string.Document, document.icon.Document, undefined, 'name', undefined, document.string.Documents) @UX(document.string.Document, document.icon.Document, undefined, 'name', undefined, document.string.Documents)
export class TDocument extends TAttachedDoc implements Document, Todoable { export class TDocument extends TCard implements Document, Todoable {
@Prop(TypeRef(document.class.Document), document.string.ParentDocument) @Prop(TypeRef(document.class.Document), document.string.ParentDocument)
declare attachedTo: Ref<Document> declare parent: Ref<Document>
@Prop(TypeRef(core.class.Class), core.string.AttachedToClass)
@Hidden()
declare attachedToClass: Ref<Class<Document>>
@Prop(TypeRef(core.class.Space), core.string.Space) @Prop(TypeRef(core.class.Space), core.string.Space)
@Index(IndexKind.Indexed) @Index(IndexKind.Indexed)
@Hidden() @Hidden()
declare space: Ref<Teamspace> declare space: Ref<Teamspace>
@Prop(TypeString(), core.string.Collection)
@Hidden()
override collection: 'children' = 'children'
@Prop(TypeString(), document.string.Name) @Prop(TypeString(), document.string.Name)
@Index(IndexKind.FullText) @Index(IndexKind.FullText)
name!: string declare title: string
@Prop(TypeCollaborativeDoc(), document.string.Document) @Prop(TypeCollaborativeDoc(), document.string.Document)
content!: CollaborativeDoc declare description: CollaborativeDoc
@Prop(TypeRef(core.class.Account), document.string.LockedBy) @Prop(TypeRef(core.class.Account), document.string.LockedBy)
@Hidden() @Hidden()
@ -136,31 +128,24 @@ export class TDocument extends TAttachedDoc implements Document, Todoable {
rank!: Rank rank!: Rank
} }
@Model(document.class.DocumentSnapshot, core.class.AttachedDoc, DOMAIN_DOCUMENT) @Model(document.class.DocumentSnapshot, core.class.Card, DOMAIN_DOCUMENT)
@UX(document.string.Version) @UX(document.string.Version)
export class TDocumentSnapshot extends TAttachedDoc implements DocumentSnapshot { export class TDocumentSnapshot extends TCard implements DocumentSnapshot {
@Prop(TypeRef(document.class.Document), document.string.ParentDocument) @Prop(TypeRef(document.class.Document), document.string.ParentDocument)
declare attachedTo: Ref<Document> declare parent: Ref<Document>
@Prop(TypeRef(core.class.Class), core.string.AttachedToClass)
declare attachedToClass: Ref<Class<Document>>
@Prop(TypeRef(core.class.Space), core.string.Space) @Prop(TypeRef(core.class.Space), core.string.Space)
@Index(IndexKind.Indexed) @Index(IndexKind.Indexed)
@Hidden() @Hidden()
declare space: Ref<Teamspace> declare space: Ref<Teamspace>
@Prop(TypeString(), core.string.Collection)
@Hidden()
override collection: 'snapshots' = 'snapshots'
@Prop(TypeString(), document.string.Name) @Prop(TypeString(), document.string.Name)
@Index(IndexKind.FullText) @Index(IndexKind.FullText)
name!: string declare title: string
@Prop(TypeCollaborativeDocVersion(), document.string.Document) @Prop(TypeCollaborativeDocVersion(), document.string.Document)
@Hidden() @Hidden()
content!: CollaborativeDoc declare description: CollaborativeDoc
} }
@Model(document.class.SavedDocument, preference.class.Preference) @Model(document.class.SavedDocument, preference.class.Preference)

View File

@ -14,21 +14,21 @@
// //
import { DOMAIN_TX, MeasureMetricsContext, SortingOrder } from '@hcengineering/core' import { DOMAIN_TX, MeasureMetricsContext, SortingOrder } from '@hcengineering/core'
import { type Document, type Teamspace } from '@hcengineering/document' import { type DocumentSnapshot, type Document, type Teamspace } from '@hcengineering/document'
import { import {
tryMigrate,
type MigrateOperation, type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient,
type MigrateUpdate, type MigrateUpdate,
type MigrationClient,
type MigrationDocumentQuery, type MigrationDocumentQuery,
tryMigrate type MigrationUpgradeClient
} from '@hcengineering/model' } from '@hcengineering/model'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core' import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import { type Asset } from '@hcengineering/platform' import { type Asset } from '@hcengineering/platform'
import { makeRank } from '@hcengineering/rank' import { makeRank } from '@hcengineering/rank'
import document, { documentId, DOMAIN_DOCUMENT } from './index'
import { loadCollaborativeDoc, saveCollaborativeDoc, yDocCopyXmlField } from '@hcengineering/collaboration' import { loadCollaborativeDoc, saveCollaborativeDoc, yDocCopyXmlField } from '@hcengineering/collaboration'
import document, { documentId, DOMAIN_DOCUMENT } from './index'
async function migrateDocumentIcons (client: MigrationClient): Promise<void> { async function migrateDocumentIcons (client: MigrationClient): Promise<void> {
await client.update<Teamspace>( await client.update<Teamspace>(
@ -111,9 +111,9 @@ async function migrateContentField (client: MigrationClient): Promise<void> {
for (const document of documents) { for (const document of documents) {
try { try {
const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.content, ctx) const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.description, ctx)
if (ydoc === undefined) { if (ydoc === undefined) {
ctx.error('document content not found', { document: document.name }) ctx.error('document content not found', { document: document.title })
continue continue
} }
@ -123,9 +123,9 @@ async function migrateContentField (client: MigrationClient): Promise<void> {
yDocCopyXmlField(ydoc, '', 'content') yDocCopyXmlField(ydoc, '', 'content')
await saveCollaborativeDoc(storage, client.workspaceId, document.content, ydoc, ctx) await saveCollaborativeDoc(storage, client.workspaceId, document.description, ydoc, ctx)
} catch (err) { } catch (err) {
ctx.error('error document content migration', { error: err, document: document.name }) ctx.error('error document content migration', { error: err, document: document.title })
} }
} }
} }
@ -154,6 +154,54 @@ async function migrateRank (client: MigrationClient): Promise<void> {
await client.bulk(DOMAIN_DOCUMENT, operations) await client.bulk(DOMAIN_DOCUMENT, operations)
} }
async function renameFields (client: MigrationClient): Promise<void> {
const documents = await client.find<Document>(DOMAIN_DOCUMENT, {
_class: document.class.Document,
content: { $exists: true }
})
for (const document of documents) {
await client.update(
DOMAIN_DOCUMENT,
{ _id: document._id },
{
$rename: {
attachedTo: 'parent',
content: 'description',
name: 'title'
},
$unset: {
attachedToClass: '',
collection: ''
}
}
)
}
const spnapshots = await client.find<DocumentSnapshot>(DOMAIN_DOCUMENT, {
_class: document.class.DocumentSnapshot,
content: { $exists: true }
})
for (const snapshot of spnapshots) {
await client.update(
DOMAIN_DOCUMENT,
{ _id: snapshot._id },
{
$rename: {
attachedTo: 'parent',
content: 'description',
name: 'title'
},
$unset: {
attachedToClass: '',
collection: ''
}
}
)
}
}
export const documentOperation: MigrateOperation = { export const documentOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> { async migrate (client: MigrationClient): Promise<void> {
await tryMigrate(client, documentId, [ await tryMigrate(client, documentId, [
@ -176,6 +224,10 @@ export const documentOperation: MigrateOperation = {
{ {
state: 'migrateRank', state: 'migrateRank',
func: migrateRank func: migrateRank
},
{
state: 'renameFields',
func: renameFields
} }
]) ])
}, },

View File

@ -16,6 +16,7 @@
import type { Asset, IntlString, Plugin } from '@hcengineering/platform' import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
import type { DocumentQuery } from './storage' import type { DocumentQuery } from './storage'
import { CollaborativeDoc } from './collaboration'
/** /**
* @public * @public
@ -73,6 +74,13 @@ export interface Doc<S extends Space = Space> extends Obj {
createdOn?: Timestamp // Marked as optional since it will be filled by platform. createdOn?: Timestamp // Marked as optional since it will be filled by platform.
} }
export interface Card extends Doc {
title: string
description: CollaborativeDoc | null
identifier?: string
parent?: Ref<Card> | null
}
/** /**
* @public * @public
*/ */

View File

@ -21,6 +21,7 @@ import type {
ArrOf, ArrOf,
AttachedDoc, AttachedDoc,
Blob, Blob,
Card,
Class, Class,
Collection, Collection,
Configuration, Configuration,
@ -82,6 +83,7 @@ export default plugin(coreId, {
class: { class: {
Obj: '' as Ref<Class<Obj>>, Obj: '' as Ref<Class<Obj>>,
Doc: '' as Ref<Class<Doc>>, Doc: '' as Ref<Class<Doc>>,
Card: '' as Ref<Class<Card>>,
Blob: '' as Ref<Class<Blob>>, Blob: '' as Ref<Class<Blob>>,
AttachedDoc: '' as Ref<Class<AttachedDoc>>, AttachedDoc: '' as Ref<Class<AttachedDoc>>,
Class: '' as Ref<Class<Class<Obj>>>, Class: '' as Ref<Class<Class<Obj>>>,

View File

@ -15,9 +15,10 @@
// //
--> -->
<script lang="ts"> <script lang="ts">
import { AttachedData, Ref, generateId } from '@hcengineering/core' import { Analytics } from '@hcengineering/analytics'
import { Document, Teamspace, DocumentEvents } from '@hcengineering/document' import { Data, generateId, Ref } from '@hcengineering/core'
import { Card, SpaceSelector, getClient } from '@hcengineering/presentation' import { Document, DocumentEvents, Teamspace } from '@hcengineering/document'
import { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
import { import {
Button, Button,
createFocusManager, createFocusManager,
@ -31,14 +32,13 @@
import view from '@hcengineering/view' import view from '@hcengineering/view'
import { IconPicker, ObjectBox } from '@hcengineering/view-resources' import { IconPicker, ObjectBox } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { Analytics } from '@hcengineering/analytics'
import document from '../plugin' import document from '../plugin'
import { createEmptyDocument } from '../utils' import { createEmptyDocument } from '../utils'
import TeamspacePresenter from './teamspace/TeamspacePresenter.svelte' import TeamspacePresenter from './teamspace/TeamspacePresenter.svelte'
export function canClose (): boolean { export function canClose (): boolean {
return object.name === '' return object.title === ''
} }
export let space: Ref<Teamspace> export let space: Ref<Teamspace>
@ -46,8 +46,8 @@
const id: Ref<Document> = generateId() const id: Ref<Document> = generateId()
const object: Pick<AttachedData<Document>, 'name' | 'icon' | 'color'> = { const object: Pick<Data<Document>, 'title' | 'icon' | 'color'> = {
name: '' title: ''
} }
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -57,7 +57,7 @@
let _parent = parent let _parent = parent
$: if (_space !== space) _parent = undefined $: if (_space !== space) _parent = undefined
$: canSave = getTitle(object.name).length > 0 && _space !== undefined $: canSave = getTitle(object.title).length > 0 && _space !== undefined
function chooseIcon (): void { function chooseIcon (): void {
const { icon, color } = object const { icon, color } = object
@ -75,7 +75,7 @@
} }
async function create (): Promise<void> { async function create (): Promise<void> {
await createEmptyDocument(client, id, _space, _parent ?? document.ids.NoParent, object) await createEmptyDocument(client, id, _space, _parent, object)
Analytics.handleEvent(DocumentEvents.DocumentCreated, { id, parent: _parent }) Analytics.handleEvent(DocumentEvents.DocumentCreated, { id, parent: _parent })
dispatch('close', id) dispatch('close', id)
} }
@ -140,7 +140,7 @@
</div> </div>
<EditBox <EditBox
placeholder={document.string.DocumentNamePlaceholder} placeholder={document.string.DocumentNamePlaceholder}
bind:value={object.name} bind:value={object.title}
kind={'large-style'} kind={'large-style'}
autoFocus autoFocus
focusIndex={1} focusIndex={1}

View File

@ -56,12 +56,12 @@
</script> </script>
<CollaboratorEditor <CollaboratorEditor
collaborativeDoc={object.content} collaborativeDoc={object.description}
objectClass={object._class} objectClass={object._class}
objectId={object._id} objectId={object._id}
objectSpace={object.space} objectSpace={object.space}
objectAttr="content" objectAttr="description"
field="content" field="description"
{user} {user}
{userComponent} {userComponent}
{focusIndex} {focusIndex}

View File

@ -28,6 +28,6 @@
<DocumentIcon {value} size={'medium'} defaultIcon={document.icon.Document} /> <DocumentIcon {value} size={'medium'} defaultIcon={document.icon.Document} />
</div> </div>
<span class="overflow-label"> <span class="overflow-label">
{value.name} {value.title}
</span> </span>
</div> </div>

View File

@ -52,13 +52,13 @@
</div> </div>
{/if} {/if}
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}> <span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
{value.name} {value.title}
</span> </span>
</div> </div>
</DocNavLink> </DocNavLink>
{:else if type === 'text'} {:else if type === 'text'}
<span class="overflow-label" use:tooltip={{ label: document.string.Document }}> <span class="overflow-label" use:tooltip={{ label: document.string.Document }}>
{value.name} {value.title}
</span> </span>
{/if} {/if}
{/if} {/if}

View File

@ -27,5 +27,5 @@
<div class="icon"> <div class="icon">
<Icon icon={document.icon.DocumentApplication} size={'small'} /> <Icon icon={document.icon.DocumentApplication} size={'small'} />
</div> </div>
{value.name} {value.title}
</div> </div>

View File

@ -80,7 +80,7 @@
const client = getClient() const client = getClient()
let doc: WithLookup<Document> | undefined let doc: WithLookup<Document> | undefined
let name = '' let title = ''
let innerWidth: number let innerWidth: number
let headings: Heading[] = [] let headings: Heading[] = []
@ -141,10 +141,10 @@
$: _id !== undefined && $: _id !== undefined &&
query.query(document.class.Document, { _id }, async (result) => { query.query(document.class.Document, { _id }, async (result) => {
;[doc] = result ;[doc] = result
name = doc?.name ?? '' title = doc?.title ?? ''
}) })
$: canSave = name.trim().length > 0 $: canSave = title.trim().length > 0
async function saveTitle (ev: Event): Promise<void> { async function saveTitle (ev: Event): Promise<void> {
ev.preventDefault() ev.preventDefault()
@ -153,10 +153,10 @@
return return
} }
const nameTrimmed = name.trim() const nameTrimmed = title.trim()
if (nameTrimmed.length > 0 && nameTrimmed !== doc.name) { if (nameTrimmed.length > 0 && nameTrimmed !== doc.title) {
await client.update(doc, { name: nameTrimmed }) await client.update(doc, { title: nameTrimmed })
} }
} }
@ -322,7 +322,7 @@
<DocumentTitle <DocumentTitle
focusIndex={1} focusIndex={1}
fill fill
bind:value={name} bind:value={title}
{readonly} {readonly}
placeholder={document.string.DocumentNamePlaceholder} placeholder={document.string.DocumentNamePlaceholder}
on:blur={(evt) => saveTitle(evt)} on:blur={(evt) => saveTitle(evt)}

View File

@ -29,7 +29,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let space: Ref<Teamspace> = value.space let space: Ref<Teamspace> = value.space
let parent: Ref<Document> = value.attachedTo let parent: Ref<Document> = value.parent
let children: Ref<Document>[] = [] let children: Ref<Document>[] = []
$: void updateChildren(value) $: void updateChildren(value)
@ -58,15 +58,15 @@
async function findChildren (doc: Document): Promise<Array<Ref<Document>>> { async function findChildren (doc: Document): Promise<Array<Ref<Document>>> {
const documents = await client.findAll( const documents = await client.findAll(
document.class.Document, document.class.Document,
{ space: doc.space, attachedTo: { $ne: document.ids.NoParent } }, { space: doc.space, parent: { $ne: document.ids.NoParent } },
{ projection: { _id: 1, attachedTo: 1 } } { projection: { _id: 1, parent: 1 } }
) )
const byParent = new Map<Ref<Document>, Array<Ref<Document>>>() const byParent = new Map<Ref<Document>, Array<Ref<Document>>>()
for (const document of documents) { for (const document of documents) {
const group = byParent.get(document.attachedTo) ?? [] const group = byParent.get(document.parent) ?? []
group.push(document._id) group.push(document._id)
byParent.set(document.attachedTo, group) byParent.set(document.parent, group)
} }
const result: Ref<Document>[] = [] const result: Ref<Document>[] = []
@ -83,7 +83,7 @@
return result return result
} }
$: canSave = space !== value.space || parent !== value.attachedTo $: canSave = space !== value.space || parent !== value.parent
</script> </script>
<Card <Card

View File

@ -21,7 +21,7 @@
{#if value} {#if value}
<div class="flex-col"> <div class="flex-col">
<span class="overflow-label mt-10px"> <span class="overflow-label mt-10px">
{value.name} {value.title}
</span> </span>
</div> </div>
{/if} {/if}

View File

@ -109,7 +109,7 @@
: { : {
fill: doc.color !== undefined ? getPlatformColorDef(doc.color, $themeStore.dark).icon : 'currentColor' fill: doc.color !== undefined ? getPlatformColorDef(doc.color, $themeStore.dark).icon : 'currentColor'
}} }}
title={doc.name} title={doc.title}
selected={selected === doc._id && draggedItem === undefined} selected={selected === doc._id && draggedItem === undefined}
isFold isFold
{level} {level}

View File

@ -99,9 +99,9 @@
descendants.clear() descendants.clear()
for (const doc of result) { for (const doc of result) {
const current = descendants.get(doc.attachedTo) ?? [] const current = descendants.get(doc.parent) ?? []
current.push(doc) current.push(doc)
descendants.set(doc.attachedTo, current) descendants.set(doc.parent, current)
documentById.set(doc._id, doc) documentById.set(doc._id, doc)
} }
@ -243,7 +243,7 @@
void moveDocumentBefore(doc, target) void moveDocumentBefore(doc, target)
} else if (pos === 'after') { } else if (pos === 'after') {
void moveDocumentAfter(doc, target) void moveDocumentAfter(doc, target)
} else if (doc.attachedTo !== object) { } else if (doc.parent !== object) {
void moveDocument(doc, target.space, target._id) void moveDocument(doc, target.space, target._id)
} }
} }
@ -315,7 +315,7 @@
: { : {
fill: item.color !== undefined ? getPlatformColorDef(item.color, $themeStore.dark).icon : 'currentColor' fill: item.color !== undefined ? getPlatformColorDef(item.color, $themeStore.dark).icon : 'currentColor'
}} }}
title={item.name} title={item.title}
selected selected
isFold isFold
empty empty

View File

@ -30,7 +30,7 @@
<div class="container flex-col flex-gap-2 flex-no-shrink"> <div class="container flex-col flex-gap-2 flex-no-shrink">
<div class="flex-between h-8"> <div class="flex-between h-8">
<div class="fs-bold overflow-label"> <div class="fs-bold overflow-label">
{value.name} {value.title}
</div> </div>
<div class="time"> <div class="time">
<TimeSince value={value.createdOn} /> <TimeSince value={value.createdOn} />

View File

@ -57,7 +57,7 @@ import {
const toObjectSearchResult = (e: WithLookup<Document>): ObjectSearchResult => ({ const toObjectSearchResult = (e: WithLookup<Document>): ObjectSearchResult => ({
doc: e, doc: e,
title: e.name, title: e.title,
icon: document.icon.Document, icon: document.icon.Document,
component: DocumentItem component: DocumentItem
}) })

View File

@ -14,8 +14,8 @@
// //
import { import {
type AttachedData,
type Client, type Client,
type Data,
type QuerySelector, type QuerySelector,
type Ref, type Ref,
SortingOrder, SortingOrder,
@ -26,7 +26,7 @@ import { type Document, type Teamspace, documentId, getFirstRank } from '@hcengi
import { getMetadata, translate } from '@hcengineering/platform' import { getMetadata, translate } from '@hcengineering/platform'
import presentation, { getClient } from '@hcengineering/presentation' import presentation, { getClient } from '@hcengineering/presentation'
import { makeRank } from '@hcengineering/rank' import { makeRank } from '@hcengineering/rank'
import { getCurrentResolvedLocation, getPanelURI, type Location, type ResolvedLocation } from '@hcengineering/ui' import { type Location, type ResolvedLocation, getCurrentResolvedLocation, getPanelURI } from '@hcengineering/ui'
import { accessDeniedStore } from '@hcengineering/view-resources' import { accessDeniedStore } from '@hcengineering/view-resources'
import { workbenchId } from '@hcengineering/workbench' import { workbenchId } from '@hcengineering/workbench'
import slugify from 'slugify' import slugify from 'slugify'
@ -39,46 +39,47 @@ export async function moveDocument (doc: Document, space: Ref<Teamspace>, parent
const prevRank = await getFirstRank(client, space, parent) const prevRank = await getFirstRank(client, space, parent)
const rank = makeRank(prevRank, undefined) const rank = makeRank(prevRank, undefined)
await client.update(doc, { space, attachedTo: parent, rank }) await client.update(doc, { space, parent, rank })
} }
export async function moveDocumentBefore (doc: Document, before: Document): Promise<void> { export async function moveDocumentBefore (doc: Document, before: Document): Promise<void> {
const client = getClient() const client = getClient()
const { space, attachedTo } = before const { space, parent } = before
const query = { rank: { $lt: before.rank } as unknown as QuerySelector<Document['rank']> } const query = { rank: { $lt: before.rank } as unknown as QuerySelector<Document['rank']> }
const lastRank = await getFirstRank(client, space, attachedTo, SortingOrder.Descending, query) const lastRank = await getFirstRank(client, space, parent, SortingOrder.Descending, query)
const rank = makeRank(lastRank, before.rank) const rank = makeRank(lastRank, before.rank)
await client.update(doc, { space, attachedTo, rank }) await client.update(doc, { space, parent, rank })
} }
export async function moveDocumentAfter (doc: Document, after: Document): Promise<void> { export async function moveDocumentAfter (doc: Document, after: Document): Promise<void> {
const client = getClient() const client = getClient()
const { space, attachedTo } = after const { space, parent } = after
const query = { rank: { $gt: after.rank } as unknown as QuerySelector<Document['rank']> } const query = { rank: { $gt: after.rank } as unknown as QuerySelector<Document['rank']> }
const nextRank = await getFirstRank(client, space, attachedTo, SortingOrder.Ascending, query) const nextRank = await getFirstRank(client, space, parent, SortingOrder.Ascending, query)
const rank = makeRank(after.rank, nextRank) const rank = makeRank(after.rank, nextRank)
await client.update(doc, { space, attachedTo, rank }) await client.update(doc, { space, parent, rank })
} }
export async function createEmptyDocument ( export async function createEmptyDocument (
client: TxOperations, client: TxOperations,
id: Ref<Document>, id: Ref<Document>,
space: Ref<Teamspace>, space: Ref<Teamspace>,
parent: Ref<Document>, parentId?: Ref<Document>,
data: Partial<Pick<AttachedData<Document>, 'name' | 'icon' | 'color'>> = {} data: Partial<Pick<Data<Document>, 'title' | 'icon' | 'color'>> = {}
): Promise<void> { ): Promise<void> {
const name = await translate(document.string.Untitled, {}) const title = await translate(document.string.Untitled, {})
const parent = parentId ?? document.ids.NoParent
const lastRank = await getFirstRank(client, space, parent) const lastRank = await getFirstRank(client, space, parent)
const rank = makeRank(lastRank, undefined) const rank = makeRank(lastRank, undefined)
const object: AttachedData<Document> = { const object: Data<Document> = {
name, title,
content: makeCollaborativeDoc(id, 'content'), description: makeCollaborativeDoc(id, 'description'),
attachments: 0, attachments: 0,
children: 0, children: 0,
embeddings: 0, embeddings: 0,
@ -86,18 +87,11 @@ export async function createEmptyDocument (
comments: 0, comments: 0,
references: 0, references: 0,
rank, rank,
parent: parent ?? document.ids.NoParent,
...data ...data
} }
await client.addCollection( await client.createDoc(document.class.Document, space, object, id)
document.class.Document,
space,
parent ?? document.ids.NoParent,
document.class.Document,
'children',
object,
id
)
} }
export async function resolveLocation (loc: Location): Promise<ResolvedLocation | undefined> { export async function resolveLocation (loc: Location): Promise<ResolvedLocation | undefined> {
@ -171,7 +165,7 @@ export function getDocumentLink (doc: Document): Location {
} }
export function getDocumentLinkId (doc: Document): string { export function getDocumentLinkId (doc: Document): string {
const slug = slugify(doc.name, { lower: true }) const slug = slugify(doc.title, { lower: true })
return `${slug}-${doc._id}` return `${slug}-${doc._id}`
} }
@ -188,5 +182,5 @@ export function parseDocumentId (shortLink?: string): Ref<Document> | undefined
export async function documentTitleProvider (client: Client, ref: Ref<Document>, doc?: Document): Promise<string> { export async function documentTitleProvider (client: Client, ref: Ref<Document>, doc?: Document): Promise<string> {
const object = doc ?? (await client.findOne(document.class.Document, { _id: ref })) const object = doc ?? (await client.findOne(document.class.Document, { _id: ref }))
return object?.name ?? '' return object?.title ?? ''
} }

View File

@ -14,7 +14,7 @@
// //
import { Attachment } from '@hcengineering/attachment' import { Attachment } from '@hcengineering/attachment'
import { Account, AttachedDoc, Class, CollaborativeDoc, Rank, Ref, TypedSpace } from '@hcengineering/core' import { Account, Card, Class, CollaborativeDoc, Rank, Ref, TypedSpace } from '@hcengineering/core'
import { Preference } from '@hcengineering/preference' import { Preference } from '@hcengineering/preference'
import { IconProps } from '@hcengineering/view' import { IconProps } from '@hcengineering/view'
@ -22,11 +22,12 @@ import { IconProps } from '@hcengineering/view'
export interface Teamspace extends TypedSpace, IconProps {} export interface Teamspace extends TypedSpace, IconProps {}
/** @public */ /** @public */
export interface Document extends AttachedDoc<Document, 'children', Teamspace>, IconProps { export interface Document extends Card, IconProps {
attachedTo: Ref<Document> parent: Ref<Document>
name: string description: CollaborativeDoc
content: CollaborativeDoc
space: Ref<Teamspace>
lockedBy?: Ref<Account> | null lockedBy?: Ref<Account> | null
@ -42,10 +43,10 @@ export interface Document extends AttachedDoc<Document, 'children', Teamspace>,
} }
/** @public */ /** @public */
export interface DocumentSnapshot extends AttachedDoc<Document, 'snapshots', Teamspace> { export interface DocumentSnapshot extends Card {
attachedTo: Ref<Document> parent: Ref<Document>
name: string title: string
content: CollaborativeDoc description: CollaborativeDoc
} }
/** @public */ /** @public */

View File

@ -21,13 +21,13 @@ import { type Document, type Teamspace } from './types'
export async function getFirstRank ( export async function getFirstRank (
client: TxOperations, client: TxOperations,
space: Ref<Teamspace>, space: Ref<Teamspace>,
attachedTo: Ref<Document>, parent: Ref<Document>,
sort: SortingOrder = SortingOrder.Descending, sort: SortingOrder = SortingOrder.Descending,
extra: DocumentQuery<Document> = {} extra: DocumentQuery<Document> = {}
): Promise<Rank | undefined> { ): Promise<Rank | undefined> {
const doc = await client.findOne( const doc = await client.findOne(
document.class.Document, document.class.Document,
{ space, attachedTo, ...extra }, { space, parent, ...extra },
{ sort: { rank: sort }, projection: { rank: 1 } } { sort: { rank: sort }, projection: { rank: 1 } }
) )

View File

@ -365,7 +365,15 @@
remoteProvider.awareness?.setLocalStateField('lastUpdate', Date.now()) 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 () => { onMount(async () => {
const _field = parseField(collaborativeDoc) ?? field
await ph await ph
editor = new Editor({ editor = new Editor({
@ -398,7 +406,7 @@
Placeholder.configure({ placeholder: placeHolderStr }), Placeholder.configure({ placeholder: placeHolderStr }),
Collaboration.configure({ Collaboration.configure({
document: ydoc, document: ydoc,
field field: _field
}), }),
CollaborationCursor.configure({ CollaborationCursor.configure({
provider: remoteProvider, provider: remoteProvider,

View File

@ -11,7 +11,7 @@ import serverCore, { TriggerControl } from '@hcengineering/server-core'
import slugify from 'slugify' import slugify from 'slugify'
function getDocumentId (doc: Document): string { function getDocumentId (doc: Document): string {
const slug = slugify(doc.name, { lower: true }) const slug = slugify(doc.title, { lower: true })
return `${slug}-${doc._id}` return `${slug}-${doc._id}`
} }
@ -23,7 +23,7 @@ export async function documentHTMLPresenter (doc: Doc, control: TriggerControl):
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? '' const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
const path = `${workbenchId}/${control.workspace.workspaceUrl}/${documentId}/${getDocumentId(document)}` const path = `${workbenchId}/${control.workspace.workspaceUrl}/${documentId}/${getDocumentId(document)}`
const link = concatLink(front, path) const link = concatLink(front, path)
return `<a href="${link}">${document.name}</a>` return `<a href="${link}">${document.title}</a>`
} }
export async function documentLinkIdProvider (doc: Document): Promise<string> { export async function documentLinkIdProvider (doc: Document): Promise<string> {
@ -35,7 +35,7 @@ export async function documentLinkIdProvider (doc: Document): Promise<string> {
*/ */
export async function documentTextPresenter (doc: Doc): Promise<string> { export async function documentTextPresenter (doc: Doc): Promise<string> {
const document = doc as Document const document = doc as Document
return document.name return document.title
} }
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type