From 0c6feb646ee8f6fc817d29d50e69782c76538c1d Mon Sep 17 00:00:00 2001 From: Victor Ilyushchenko Date: Wed, 15 Jan 2025 12:29:30 +0300 Subject: [PATCH] EZQMS-1234: ability to relocate and reorder controlled documents within the space (#7668) Signed-off-by: Victor Ilyushchenko --- models/controlled-documents/package.json | 3 +- models/controlled-documents/src/migration.ts | 55 +++- models/controlled-documents/src/types.ts | 7 +- packages/ui/src/components/NavGroup.svelte | 1 + .../package.json | 3 +- .../hierarchy/DocHierarchyLevel.svelte | 125 +++++--- .../hierarchy/DocumentSpacePresenter.svelte | 288 ++++++++++++++---- .../src/components/hierarchy/DropArea.svelte | 29 ++ .../components/hierarchy/DropMarker.svelte | 33 ++ .../src/utils.ts | 100 ++++-- plugins/controlled-documents/package.json | 3 +- plugins/controlled-documents/src/docutils.ts | 17 +- plugins/controlled-documents/src/types.ts | 5 +- plugins/controlled-documents/src/utils.ts | 36 ++- .../components/navigator/TreeElement.svelte | 2 + .../src/components/navigator/TreeItem.svelte | 6 + 16 files changed, 562 insertions(+), 151 deletions(-) create mode 100644 plugins/controlled-documents-resources/src/components/hierarchy/DropArea.svelte create mode 100644 plugins/controlled-documents-resources/src/components/hierarchy/DropMarker.svelte diff --git a/models/controlled-documents/package.json b/models/controlled-documents/package.json index 548cec53fc..dfbac2c566 100644 --- a/models/controlled-documents/package.json +++ b/models/controlled-documents/package.json @@ -58,6 +58,7 @@ "@hcengineering/chunter": "^0.6.20", "@hcengineering/text-editor": "^0.6.0", "@hcengineering/collaboration": "^0.6.0", - "@hcengineering/time": "^0.6.0" + "@hcengineering/time": "^0.6.0", + "@hcengineering/rank": "^0.6.4" } } diff --git a/models/controlled-documents/src/migration.ts b/models/controlled-documents/src/migration.ts index c199e79fb0..9f8cdfbb3e 100644 --- a/models/controlled-documents/src/migration.ts +++ b/models/controlled-documents/src/migration.ts @@ -4,12 +4,12 @@ import attachment, { type Attachment } from '@hcengineering/attachment' import { - YXmlElement, - YXmlText, - YAbstractType, - yXmlElementClone, loadCollabYdoc, - saveCollabYdoc + saveCollabYdoc, + YAbstractType, + YXmlElement, + yXmlElementClone, + YXmlText } from '@hcengineering/collaboration' import { type ChangeControl, @@ -17,8 +17,10 @@ import { createChangeControl, createDocumentTemplate, type DocumentCategory, + type DocumentMeta, documentsId, - DocumentState + DocumentState, + type ProjectMeta } from '@hcengineering/controlled-documents' import { type Class, @@ -48,6 +50,7 @@ import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment' import core from '@hcengineering/model-core' import tags from '@hcengineering/tags' +import { makeRank } from '@hcengineering/rank' import documents, { DOMAIN_DOCUMENTS } from './index' async function createTemplatesSpace (tx: TxOperations): Promise { @@ -364,6 +367,42 @@ async function migrateDocSections (client: MigrationClient): Promise { } } +async function migrateProjectMetaRank (client: MigrationClient): Promise { + const projectMeta = await client.find(DOMAIN_DOCUMENTS, { + _class: documents.class.ProjectMeta, + rank: { $exists: false } + }) + + const docMeta = await client.find(DOMAIN_DOCUMENTS, { + _class: documents.class.ProjectDocument, + _id: { $in: projectMeta.map((p) => p.meta) } + }) + + const docMetaById = new Map, DocumentMeta>() + for (const doc of docMeta) { + docMetaById.set(doc._id, doc) + } + + projectMeta.sort((a, b) => { + const docA = docMetaById.get(a.meta) + const docB = docMetaById.get(b.meta) + return (docA?.title ?? '').localeCompare(docB?.title ?? '', undefined, { numeric: true }) + }) + + let rank = makeRank(undefined, undefined) + const operations: { filter: MigrationDocumentQuery, update: MigrateUpdate }[] = [] + + for (const doc of projectMeta) { + operations.push({ + filter: { _id: doc._id }, + update: { $set: { rank } } + }) + rank = makeRank(rank, undefined) + } + + await client.bulk(DOMAIN_DOCUMENTS, operations) +} + export const documentsOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await tryMigrate(client, documentsId, [ @@ -374,6 +413,10 @@ export const documentsOperation: MigrateOperation = { { state: 'migrateDocSections', func: migrateDocSections + }, + { + state: 'migrateProjectMetaRank', + func: migrateProjectMetaRank } ]) }, diff --git a/models/controlled-documents/src/types.ts b/models/controlled-documents/src/types.ts index 16fa68a429..3d2b187ba1 100644 --- a/models/controlled-documents/src/types.ts +++ b/models/controlled-documents/src/types.ts @@ -58,7 +58,8 @@ import { type Role, type TypedSpace, type Account, - type RolesAssignment + type RolesAssignment, + type Rank } from '@hcengineering/core' import { ArrOf, @@ -181,6 +182,10 @@ export class TProjectMeta extends TDoc implements ProjectMeta { @Prop(Collection(documents.class.ProjectDocument), documents.string.Documents) documents!: CollectionSize + + @Index(IndexKind.Indexed) + @Hidden() + rank!: Rank } @Model(documents.class.ProjectDocument, core.class.AttachedDoc, DOMAIN_DOCUMENTS) diff --git a/packages/ui/src/components/NavGroup.svelte b/packages/ui/src/components/NavGroup.svelte index 171846feca..3abca34a91 100644 --- a/packages/ui/src/components/NavGroup.svelte +++ b/packages/ui/src/components/NavGroup.svelte @@ -101,6 +101,7 @@ {draggable} on:dragstart on:dragover + on:dragend on:drop > {#if isFold && !empty} diff --git a/plugins/controlled-documents-resources/package.json b/plugins/controlled-documents-resources/package.json index 6a2e26ded1..2443c51d7f 100644 --- a/plugins/controlled-documents-resources/package.json +++ b/plugins/controlled-documents-resources/package.json @@ -70,6 +70,7 @@ "effector": "~22.8.7", "svelte": "^4.2.19", "slugify": "^1.6.6", - "fast-equals": "^5.0.1" + "fast-equals": "^5.0.1", + "@hcengineering/rank": "^0.6.4" } } diff --git a/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte b/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte index d29173eefb..aa88f64bff 100644 --- a/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte +++ b/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte @@ -27,7 +27,6 @@ DocumentState } from '@hcengineering/controlled-documents' import { TreeItem } from '@hcengineering/view-resources' - import { compareDocs } from '../../utils' export let projectMeta: ProjectMeta[] = [] export let childrenByParent: Record, Array> @@ -36,13 +35,39 @@ export let getMoreActions: ((obj: Doc, originalEvent?: MouseEvent) => Promise) | undefined = undefined export let collapsedPrefix: string = '' + export let onDragStart: ((e: DragEvent, object: Ref) => void) | undefined = undefined + export let onDragOver: ((e: DragEvent, object: Ref) => void) | undefined = undefined + export let onDragEnd: ((e: DragEvent, object: Ref) => void) | undefined = undefined + export let onDrop: ((e: DragEvent, object: Ref) => void) | undefined = undefined + + export let draggedItem: Ref | undefined = undefined + export let draggedOver: Ref | undefined = undefined + + import DropArea from './DropArea.svelte' + const dispatch = createEventDispatcher() const currentUser = getCurrentAccount() as PersonAccount const currentPerson = currentUser.person - const docsQuery = createQuery() let docs: WithLookup[] = [] + function sortDocs (meta: ProjectMeta[]): void { + const metaById = new Map(meta.map((p) => [p._id, p])) + docs = docs.slice().sort((a, b) => { + const metaA = metaById.get(a.attachedTo) + const metaB = metaById.get(b.attachedTo) + + if (metaA !== undefined && metaB !== undefined) { + return metaA.rank.localeCompare(metaB.rank) + } + return 0 + }) + } + + $: sortDocs(projectMeta) + + const docsQuery = createQuery() + $: docsQuery.query( documents.class.ProjectDocument, { @@ -79,12 +104,7 @@ } } - docs.sort((a, b) => { - if (a.$lookup?.document !== undefined && b.$lookup?.document !== undefined) { - return compareDocs(a.$lookup.document, b.$lookup.document) - } - return 0 - }) + sortDocs(projectMeta) }, { lookup: { @@ -110,37 +130,62 @@ {#if doc} {@const children = childrenByParent[doc.attachedTo] ?? []} - getDocMoreActions(prjdoc) : undefined} - {level} - {collapsedPrefix} - shouldTooltip - on:click={() => { - dispatch('selected', prjdoc) - }} - > - - {#if children.length} - - {/if} - - + {@const isDraggedOver = draggedOver === doc.attachedTo} +
+ {#if isDraggedOver} + + {/if} + getDocMoreActions(prjdoc) : undefined} + {level} + {collapsedPrefix} + shouldTooltip + on:click={() => { + dispatch('selected', prjdoc) + }} + draggable={onDragStart !== undefined} + on:dragstart={(evt) => { + onDragStart?.(evt, doc.attachedTo) + }} + on:dragover={(evt) => { + onDragOver?.(evt, doc.attachedTo) + }} + on:dragend={(evt) => { + onDragEnd?.(evt, doc.attachedTo) + }} + on:drop={(evt) => { + onDrop?.(evt, doc.attachedTo) + }} + > + + {#if children.length} + + {/if} + + +
{/if} {/each} diff --git a/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte b/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte index 5ecdcd1746..fe189491da 100644 --- a/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte +++ b/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte @@ -16,7 +16,15 @@ import { WithLookup, type Doc, type Ref, type Space } from '@hcengineering/core' import { createQuery, getClient } from '@hcengineering/presentation' import { getResource } from '@hcengineering/platform' - import { type Action, getPlatformColorForTextDef, themeStore, navigate, IconEdit, Label } from '@hcengineering/ui' + import { + type Action, + getPlatformColorForTextDef, + themeStore, + navigate, + IconEdit, + Label, + closeTooltip + } from '@hcengineering/ui' import { getActions as getContributedActions, TreeNode, TreeItem } from '@hcengineering/view-resources' import { ActionGroup } from '@hcengineering/view' import { @@ -40,11 +48,17 @@ getProjectDocsHierarchy, isEditableProject, createDocument, - canCreateChildDocument + canCreateChildDocument, + moveDocument, + moveDocumentBefore, + moveDocumentAfter } from '../../utils' import documents from '../../plugin' + import DropArea from './DropArea.svelte' + import DropMarker from './DropMarker.svelte' + export let space: DocumentSpace export let currentSpace: Ref | undefined export let currentFragment: string | undefined @@ -67,6 +81,7 @@ let project: Ref = documents.ids.NoProject $: void selectProject(space) + let docsByMeta = new Map, WithLookup>() let rootDocs: Array> = [] let childrenByParent: Record, Array>> = {} @@ -78,6 +93,7 @@ project }, (result) => { + docsByMeta = new Map(result.map((r) => [r.meta, r])) ;({ rootDocs, childrenByParent } = getProjectDocsHierarchy(result)) }, { @@ -116,6 +132,23 @@ selectedControlledDoc = undefined } + function getAllDescendants (obj: Ref): Ref[] { + const result: Ref[] = [] + const queue: Ref[] = [obj] + + while (queue.length > 0) { + const next = queue.pop() + if (next === undefined) break + + const children = childrenByParent[next] ?? [] + const childrenRefs = children.map((p) => p.meta) + result.push(...childrenRefs) + queue.push(...childrenRefs) + } + + return result + } + async function selectProject (space: DocumentSpace): Promise { project = getCurrentProject(space._id) ?? (await getLatestProjectId(space._id, true)) ?? documents.ids.NoProject } @@ -181,75 +214,202 @@ return actions } + + let parent: HTMLElement + let draggedItem: Ref | undefined = undefined + let draggedOver: Ref | undefined = undefined + let draggedOverPos: 'before' | 'after' | undefined = undefined + let draggedOverTop: number = 0 + let cannotDropTo: Ref[] = [] + + function canDrop (object: Ref, target: Ref): boolean { + if (object === target) return false + if (cannotDropTo.includes(target)) return false + + return true + } + + function onDragStart (event: DragEvent, object: Ref): void { + // no prevent default to leverage default rendering + // event.preventDefault() + if (event.dataTransfer === null || event.target === null) { + return + } + + cannotDropTo = [object, ...getAllDescendants(object)] + + event.dataTransfer.effectAllowed = 'move' + event.dataTransfer.dropEffect = 'move' + draggedItem = object + + closeTooltip() + } + + function getDropPosition (event: DragEvent): { pos: 'before' | 'after' | undefined, top: number } { + const parentRect = parent.getBoundingClientRect() + const targetRect = (event.target as HTMLElement).getBoundingClientRect() + const dropPosition = event.clientY - targetRect.top + + const before = dropPosition >= 0 && dropPosition < targetRect.height / 6 + const after = dropPosition <= targetRect.height && dropPosition > (5 * targetRect.height) / 6 + + const pos = before ? 'before' : after ? 'after' : undefined + const top = pos === 'before' ? targetRect.top - parentRect.top - 1 : targetRect.bottom - parentRect.top - 1 + + return { pos, top } + } + + function onDragOver (event: DragEvent, object: Ref): void { + event.preventDefault() + // this is an ugly solution to control drop effect + // we drag and drop elements that are in the depth of components hierarchy + // so we cannot access them directly + if (!(event.target as HTMLElement).draggable) return + if (event.dataTransfer === null || event.target === null || draggedItem === object) { + return + } + + if (draggedItem !== undefined && canDrop(draggedItem, object)) { + event.dataTransfer.dropEffect = 'move' + draggedOver = object + + const { pos, top } = getDropPosition(event) + draggedOverPos = pos + draggedOverTop = top + } else { + event.dataTransfer.dropEffect = 'none' + } + } + + function onDragEnd (event: DragEvent): void { + event.preventDefault() + draggedItem = undefined + draggedOver = undefined + draggedOverPos = undefined + } + + function onDrop (event: DragEvent, object: Ref): void { + event.preventDefault() + if (event.dataTransfer === null) { + return + } + if (draggedItem !== undefined && canDrop(draggedItem, object)) { + const doc = docsByMeta.get(draggedItem) + const target = docsByMeta.get(object) + + if (doc !== undefined && target !== undefined && doc._id !== target._id) { + if (object === documents.ids.NoParent) { + void moveDocument(doc, doc.space) + } else if (target !== undefined) { + const { pos } = getDropPosition(event) + if (pos === 'before') { + void moveDocumentBefore(doc, target) + } else if (pos === 'after') { + void moveDocumentAfter(doc, target) + } else if (doc.parent !== object) { + void moveDocument(doc, target.space, target) + } + } + } + } + draggedItem = undefined + draggedOver = undefined + } - getSpaceActions(space)} - type={'nested'} -> - - {#if spaceType?.projects === true} - { - project = evt.detail - setCurrentProject(space._id, project) - }} - /> - {/if} - - - {#if rootDocs.length > 0} - { - handleDocumentSelected(e.detail) - }} - /> - {:else} -
-
+
+ {#if draggedOver === documents.ids.NoParent} + {/if} - - {#if (selected || forciblyСollapsed) && selectedControlledDoc} - {@const doc = selectedControlledDoc} - + {/if} + + getSpaceActions(space)} + type={'nested'} + draggable + on:drop={(evt) => { + onDrop(evt, documents.ids.NoParent) + }} + on:dragover={(evt) => { + onDragOver(evt, documents.ids.NoParent) + }} + on:dragstart={(evt) => { + evt.preventDefault() + }} + > + + {#if spaceType?.projects === true} + { + project = evt.detail + setCurrentProject(space._id, project) + }} + /> + {/if} + + + {#if rootDocs.length > 0} + { + handleDocumentSelected(e.detail) }} - title={getDocumentName(doc)} - actions={() => getDocumentActions(doc)} - selected - isFold - empty - forciblyСollapsed + {onDragStart} + {onDragEnd} + {onDragOver} + {onDrop} + {draggedItem} + {draggedOver} /> + {:else} +
+
{/if} -
- + + + {#if (selected || forciblyСollapsed) && selectedControlledDoc} + {@const doc = selectedControlledDoc} + getDocumentActions(doc)} + selected + isFold + empty + forciblyСollapsed + /> + {/if} + + +
diff --git a/plugins/controlled-documents-resources/src/components/hierarchy/DropMarker.svelte b/plugins/controlled-documents-resources/src/components/hierarchy/DropMarker.svelte new file mode 100644 index 0000000000..ff849a72e5 --- /dev/null +++ b/plugins/controlled-documents-resources/src/components/hierarchy/DropMarker.svelte @@ -0,0 +1,33 @@ + + + +
+ + diff --git a/plugins/controlled-documents-resources/src/utils.ts b/plugins/controlled-documents-resources/src/utils.ts index 2cff4e07ed..f87f4cc09c 100644 --- a/plugins/controlled-documents-resources/src/utils.ts +++ b/plugins/controlled-documents-resources/src/utils.ts @@ -11,51 +11,53 @@ // // See the License for the specific language governing permissions and // limitations under the License. -import core, { - type Class, - type Doc, - type DocumentQuery, - type Hierarchy, - type Ref, - type Tx, - type TxOperations, - type Space, - type Markup, - type Client, - type WithLookup, - SortingOrder, - getCurrentAccount, - checkPermission -} from '@hcengineering/core' -import { type IntlString, translate } from '@hcengineering/platform' -import { getClient } from '@hcengineering/presentation' -import { type Person, type Employee, type PersonAccount } from '@hcengineering/contact' -import request, { RequestStatus } from '@hcengineering/request' -import { isEmptyMarkup } from '@hcengineering/text' -import { showPopup, getUserTimezone, type Location } from '@hcengineering/ui' -import { type KeyFilter } from '@hcengineering/view' import chunter from '@hcengineering/chunter' +import { type Employee, type Person, type PersonAccount } from '@hcengineering/contact' import documents, { type ControlledDocument, type Document, - type DocumentRequest, - type DocumentTemplate, - type DocumentSpace, type DocumentCategory, - type DocumentMeta, type DocumentComment, + type DocumentMeta, + type DocumentRequest, + type DocumentSpace, + type DocumentTemplate, type OrgSpace, type Project, type ProjectDocument, type ProjectMeta, ControlledDocumentState, DocumentState, - getDocumentName + getDocumentName, + getFirstRank } from '@hcengineering/controlled-documents' -import { type Request } from '@hcengineering/request' +import core, { + type Class, + type Client, + type Doc, + type DocumentQuery, + type Hierarchy, + type Markup, + type QuerySelector, + type Ref, + type Space, + type Tx, + type TxOperations, + type WithLookup, + SortingOrder, + checkPermission, + getCurrentAccount +} from '@hcengineering/core' +import { type IntlString, translate } from '@hcengineering/platform' +import { getClient } from '@hcengineering/presentation' +import request, { type Request, RequestStatus } from '@hcengineering/request' +import { isEmptyMarkup } from '@hcengineering/text' +import { type Location, getUserTimezone, showPopup } from '@hcengineering/ui' +import { type KeyFilter } from '@hcengineering/view' -import documentsResources from './plugin' +import { makeRank } from '@hcengineering/rank' import { getProjectDocumentLink } from './navigation' +import documentsResources from './plugin' import { wizardOpened } from './stores/wizards/create-document' export type TranslatedDocumentStates = Readonly> @@ -654,3 +656,41 @@ export function formatSignatureDate (date: number): string { second: 'numeric' }) } + +export async function moveDocument (doc: ProjectMeta, space: Ref, target?: ProjectMeta): Promise { + const client = getClient() + + let parent = documents.ids.NoParent + let path: Array> = [] + if (target !== undefined) { + parent = target.meta + path = [target.meta, ...target.path] + } + + const prevRank = await getFirstRank(client, space, doc.project, parent) + const rank = makeRank(prevRank, undefined) + + await client.update(doc, { parent, path, rank }) +} + +export async function moveDocumentBefore (doc: ProjectMeta, before: ProjectMeta): Promise { + const client = getClient() + + const { space, parent, path } = before + const query = { rank: { $lt: before.rank } as unknown as QuerySelector } + const lastRank = await getFirstRank(client, space, doc.project, parent, SortingOrder.Descending, query) + const rank = makeRank(lastRank, before.rank) + + await client.update(doc, { parent, path, rank }) +} + +export async function moveDocumentAfter (doc: ProjectMeta, after: ProjectMeta): Promise { + const client = getClient() + + const { space, parent, path } = after + const query = { rank: { $gt: after.rank } as unknown as QuerySelector } + const nextRank = await getFirstRank(client, space, doc.project, parent, SortingOrder.Ascending, query) + const rank = makeRank(after.rank, nextRank) + + await client.update(doc, { parent, path, rank }) +} diff --git a/plugins/controlled-documents/package.json b/plugins/controlled-documents/package.json index 758aa43e17..cd0c8a67f6 100644 --- a/plugins/controlled-documents/package.json +++ b/plugins/controlled-documents/package.json @@ -49,7 +49,8 @@ "@hcengineering/training": "^0.1.0", "lexorank": "~1.0.4", "@hcengineering/activity": "^0.6.0", - "@hcengineering/chunter": "^0.6.20" + "@hcengineering/chunter": "^0.6.20", + "@hcengineering/rank": "^0.6.4" }, "publishConfig": { "@hcengineering:registry": "https://npm.pkg.github.com" diff --git a/plugins/controlled-documents/src/docutils.ts b/plugins/controlled-documents/src/docutils.ts index 0de1ae6c3f..d66948dd36 100644 --- a/plugins/controlled-documents/src/docutils.ts +++ b/plugins/controlled-documents/src/docutils.ts @@ -27,9 +27,10 @@ import { type ProjectDocument, DocumentState } from './types' +import { makeRank } from '@hcengineering/rank' import documents from './plugin' -import { TEMPLATE_PREFIX } from './utils' +import { getFirstRank, TEMPLATE_PREFIX } from './utils' async function getParentPath (client: TxOperations, parent: Ref): Promise>> { const parentDocObj = await client.findOne(documents.class.ProjectDocument, { @@ -175,12 +176,16 @@ export async function createControlledDocMetadata ( path = await getParentPath(client, parent) } + const parentMeta = path[0] ?? documents.ids.NoParent + const lastRank = await getFirstRank(client, space, projectId, parentMeta) + const projectMetaId = await ops.createDoc(documents.class.ProjectMeta, space, { project: projectId, meta: documentMetaId, path, - parent: path[0] ?? documents.ids.NoParent, - documents: 0 + parent: parentMeta, + documents: 0, + rank: makeRank(lastRank, undefined) }) const projectDocumentId = await client.addCollection( @@ -323,12 +328,16 @@ export async function createDocumentTemplateMetadata ( metaId ) + const parentMeta = path[0] ?? documents.ids.NoParent + const lastRank = await getFirstRank(client, space, projectId, parentMeta) + const projectMetaId = await ops.createDoc(documents.class.ProjectMeta, space, { project: projectId, meta: documentMetaId, path, parent: path[0] ?? documents.ids.NoParent, - documents: 0 + documents: 0, + rank: makeRank(lastRank, undefined) }) const projectDocumentId = await client.addCollection( diff --git a/plugins/controlled-documents/src/types.ts b/plugins/controlled-documents/src/types.ts index 4ec4a756ac..d49a22396d 100644 --- a/plugins/controlled-documents/src/types.ts +++ b/plugins/controlled-documents/src/types.ts @@ -16,7 +16,8 @@ import { type TypedSpace, type Timestamp, SpaceType, - SpaceTypeDescriptor + SpaceTypeDescriptor, + Rank } from '@hcengineering/core' import { type TagReference } from '@hcengineering/tags' import { Request } from '@hcengineering/request' @@ -91,6 +92,8 @@ export interface ProjectMeta extends Doc { // head: Ref documents: CollectionSize + + rank: Rank } /** diff --git a/plugins/controlled-documents/src/utils.ts b/plugins/controlled-documents/src/utils.ts index b5bb9be263..7ae9ad4d13 100644 --- a/plugins/controlled-documents/src/utils.ts +++ b/plugins/controlled-documents/src/utils.ts @@ -12,7 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import { ApplyOperations, Data, DocumentUpdate, Ref, TxOperations } from '@hcengineering/core' +import { + ApplyOperations, + Data, + DocumentQuery, + DocumentUpdate, + Rank, + Ref, + SortingOrder, + Space, + TxOperations +} from '@hcengineering/core' import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank' import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket' @@ -22,6 +32,7 @@ import { ChangeControl, ControlledDocument, Document, + DocumentMeta, DocumentSpace, DocumentState, Project, @@ -143,7 +154,8 @@ export async function copyProjectDocuments ( meta: meta.meta, path: meta.path, parent: meta.parent, - documents: meta.documents + documents: meta.documents, + rank: meta.rank }) // copy project docs attached to meta @@ -165,6 +177,26 @@ export async function copyProjectDocuments ( } } +/** + * @public + */ +export async function getFirstRank ( + client: TxOperations, + space: Ref, + project: Ref, + parent: Ref, + sort: SortingOrder = SortingOrder.Descending, + extra: DocumentQuery = {} +): Promise { + const doc = await client.findOne( + documents.class.ProjectMeta, + { space, project, parent, ...extra }, + { sort: { rank: sort }, projection: { rank: 1 } } + ) + + return doc?.rank +} + /** * @public */ diff --git a/plugins/view-resources/src/components/navigator/TreeElement.svelte b/plugins/view-resources/src/components/navigator/TreeElement.svelte index 391e5a7cf8..210491bc24 100644 --- a/plugins/view-resources/src/components/navigator/TreeElement.svelte +++ b/plugins/view-resources/src/components/navigator/TreeElement.svelte @@ -108,6 +108,7 @@ on:click on:dragstart on:dragover + on:dragend on:drop on:toggle={(ev) => { if (ev.detail !== undefined) collapsed = !ev.detail @@ -176,6 +177,7 @@ on:click on:dragstart on:dragover + on:dragend on:drop > diff --git a/plugins/view-resources/src/components/navigator/TreeItem.svelte b/plugins/view-resources/src/components/navigator/TreeItem.svelte index 0c984dfddb..bc7e1efd17 100644 --- a/plugins/view-resources/src/components/navigator/TreeItem.svelte +++ b/plugins/view-resources/src/components/navigator/TreeItem.svelte @@ -39,6 +39,7 @@ export let showNotify: boolean = false export let forciblyСollapsed: boolean = false export let collapsedPrefix: string = '' + export let draggable: boolean = false