mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-01 21:31:04 +00:00
EZQMS-1234: ability to relocate and reorder controlled documents within the space (#7668)
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions
Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>
This commit is contained in:
parent
ff5b57dd47
commit
0c6feb646e
@ -58,6 +58,7 @@
|
|||||||
"@hcengineering/chunter": "^0.6.20",
|
"@hcengineering/chunter": "^0.6.20",
|
||||||
"@hcengineering/text-editor": "^0.6.0",
|
"@hcengineering/text-editor": "^0.6.0",
|
||||||
"@hcengineering/collaboration": "^0.6.0",
|
"@hcengineering/collaboration": "^0.6.0",
|
||||||
"@hcengineering/time": "^0.6.0"
|
"@hcengineering/time": "^0.6.0",
|
||||||
|
"@hcengineering/rank": "^0.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
import attachment, { type Attachment } from '@hcengineering/attachment'
|
import attachment, { type Attachment } from '@hcengineering/attachment'
|
||||||
import {
|
import {
|
||||||
YXmlElement,
|
|
||||||
YXmlText,
|
|
||||||
YAbstractType,
|
|
||||||
yXmlElementClone,
|
|
||||||
loadCollabYdoc,
|
loadCollabYdoc,
|
||||||
saveCollabYdoc
|
saveCollabYdoc,
|
||||||
|
YAbstractType,
|
||||||
|
YXmlElement,
|
||||||
|
yXmlElementClone,
|
||||||
|
YXmlText
|
||||||
} from '@hcengineering/collaboration'
|
} from '@hcengineering/collaboration'
|
||||||
import {
|
import {
|
||||||
type ChangeControl,
|
type ChangeControl,
|
||||||
@ -17,8 +17,10 @@ import {
|
|||||||
createChangeControl,
|
createChangeControl,
|
||||||
createDocumentTemplate,
|
createDocumentTemplate,
|
||||||
type DocumentCategory,
|
type DocumentCategory,
|
||||||
|
type DocumentMeta,
|
||||||
documentsId,
|
documentsId,
|
||||||
DocumentState
|
DocumentState,
|
||||||
|
type ProjectMeta
|
||||||
} from '@hcengineering/controlled-documents'
|
} from '@hcengineering/controlled-documents'
|
||||||
import {
|
import {
|
||||||
type Class,
|
type Class,
|
||||||
@ -48,6 +50,7 @@ import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment'
|
|||||||
import core from '@hcengineering/model-core'
|
import core from '@hcengineering/model-core'
|
||||||
import tags from '@hcengineering/tags'
|
import tags from '@hcengineering/tags'
|
||||||
|
|
||||||
|
import { makeRank } from '@hcengineering/rank'
|
||||||
import documents, { DOMAIN_DOCUMENTS } from './index'
|
import documents, { DOMAIN_DOCUMENTS } from './index'
|
||||||
|
|
||||||
async function createTemplatesSpace (tx: TxOperations): Promise<void> {
|
async function createTemplatesSpace (tx: TxOperations): Promise<void> {
|
||||||
@ -364,6 +367,42 @@ async function migrateDocSections (client: MigrationClient): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function migrateProjectMetaRank (client: MigrationClient): Promise<void> {
|
||||||
|
const projectMeta = await client.find<ProjectMeta>(DOMAIN_DOCUMENTS, {
|
||||||
|
_class: documents.class.ProjectMeta,
|
||||||
|
rank: { $exists: false }
|
||||||
|
})
|
||||||
|
|
||||||
|
const docMeta = await client.find<DocumentMeta>(DOMAIN_DOCUMENTS, {
|
||||||
|
_class: documents.class.ProjectDocument,
|
||||||
|
_id: { $in: projectMeta.map((p) => p.meta) }
|
||||||
|
})
|
||||||
|
|
||||||
|
const docMetaById = new Map<Ref<DocumentMeta>, 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<ProjectMeta>, update: MigrateUpdate<ProjectMeta> }[] = []
|
||||||
|
|
||||||
|
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 = {
|
export const documentsOperation: MigrateOperation = {
|
||||||
async migrate (client: MigrationClient): Promise<void> {
|
async migrate (client: MigrationClient): Promise<void> {
|
||||||
await tryMigrate(client, documentsId, [
|
await tryMigrate(client, documentsId, [
|
||||||
@ -374,6 +413,10 @@ export const documentsOperation: MigrateOperation = {
|
|||||||
{
|
{
|
||||||
state: 'migrateDocSections',
|
state: 'migrateDocSections',
|
||||||
func: migrateDocSections
|
func: migrateDocSections
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: 'migrateProjectMetaRank',
|
||||||
|
func: migrateProjectMetaRank
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
@ -58,7 +58,8 @@ import {
|
|||||||
type Role,
|
type Role,
|
||||||
type TypedSpace,
|
type TypedSpace,
|
||||||
type Account,
|
type Account,
|
||||||
type RolesAssignment
|
type RolesAssignment,
|
||||||
|
type Rank
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
ArrOf,
|
ArrOf,
|
||||||
@ -181,6 +182,10 @@ export class TProjectMeta extends TDoc implements ProjectMeta {
|
|||||||
|
|
||||||
@Prop(Collection(documents.class.ProjectDocument), documents.string.Documents)
|
@Prop(Collection(documents.class.ProjectDocument), documents.string.Documents)
|
||||||
documents!: CollectionSize<ProjectDocument>
|
documents!: CollectionSize<ProjectDocument>
|
||||||
|
|
||||||
|
@Index(IndexKind.Indexed)
|
||||||
|
@Hidden()
|
||||||
|
rank!: Rank
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(documents.class.ProjectDocument, core.class.AttachedDoc, DOMAIN_DOCUMENTS)
|
@Model(documents.class.ProjectDocument, core.class.AttachedDoc, DOMAIN_DOCUMENTS)
|
||||||
|
@ -101,6 +101,7 @@
|
|||||||
{draggable}
|
{draggable}
|
||||||
on:dragstart
|
on:dragstart
|
||||||
on:dragover
|
on:dragover
|
||||||
|
on:dragend
|
||||||
on:drop
|
on:drop
|
||||||
>
|
>
|
||||||
{#if isFold && !empty}
|
{#if isFold && !empty}
|
||||||
|
@ -70,6 +70,7 @@
|
|||||||
"effector": "~22.8.7",
|
"effector": "~22.8.7",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^4.2.19",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"fast-equals": "^5.0.1"
|
"fast-equals": "^5.0.1",
|
||||||
|
"@hcengineering/rank": "^0.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
DocumentState
|
DocumentState
|
||||||
} from '@hcengineering/controlled-documents'
|
} from '@hcengineering/controlled-documents'
|
||||||
import { TreeItem } from '@hcengineering/view-resources'
|
import { TreeItem } from '@hcengineering/view-resources'
|
||||||
import { compareDocs } from '../../utils'
|
|
||||||
|
|
||||||
export let projectMeta: ProjectMeta[] = []
|
export let projectMeta: ProjectMeta[] = []
|
||||||
export let childrenByParent: Record<Ref<DocumentMeta>, Array<ProjectMeta>>
|
export let childrenByParent: Record<Ref<DocumentMeta>, Array<ProjectMeta>>
|
||||||
@ -36,13 +35,39 @@
|
|||||||
export let getMoreActions: ((obj: Doc, originalEvent?: MouseEvent) => Promise<Action[]>) | undefined = undefined
|
export let getMoreActions: ((obj: Doc, originalEvent?: MouseEvent) => Promise<Action[]>) | undefined = undefined
|
||||||
export let collapsedPrefix: string = ''
|
export let collapsedPrefix: string = ''
|
||||||
|
|
||||||
|
export let onDragStart: ((e: DragEvent, object: Ref<DocumentMeta>) => void) | undefined = undefined
|
||||||
|
export let onDragOver: ((e: DragEvent, object: Ref<DocumentMeta>) => void) | undefined = undefined
|
||||||
|
export let onDragEnd: ((e: DragEvent, object: Ref<DocumentMeta>) => void) | undefined = undefined
|
||||||
|
export let onDrop: ((e: DragEvent, object: Ref<DocumentMeta>) => void) | undefined = undefined
|
||||||
|
|
||||||
|
export let draggedItem: Ref<DocumentMeta> | undefined = undefined
|
||||||
|
export let draggedOver: Ref<DocumentMeta> | undefined = undefined
|
||||||
|
|
||||||
|
import DropArea from './DropArea.svelte'
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const currentUser = getCurrentAccount() as PersonAccount
|
const currentUser = getCurrentAccount() as PersonAccount
|
||||||
const currentPerson = currentUser.person
|
const currentPerson = currentUser.person
|
||||||
|
|
||||||
const docsQuery = createQuery()
|
|
||||||
let docs: WithLookup<ProjectDocument>[] = []
|
let docs: WithLookup<ProjectDocument>[] = []
|
||||||
|
|
||||||
|
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(
|
$: docsQuery.query(
|
||||||
documents.class.ProjectDocument,
|
documents.class.ProjectDocument,
|
||||||
{
|
{
|
||||||
@ -79,12 +104,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
docs.sort((a, b) => {
|
sortDocs(projectMeta)
|
||||||
if (a.$lookup?.document !== undefined && b.$lookup?.document !== undefined) {
|
|
||||||
return compareDocs(a.$lookup.document, b.$lookup.document)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lookup: {
|
lookup: {
|
||||||
@ -110,37 +130,62 @@
|
|||||||
|
|
||||||
{#if doc}
|
{#if doc}
|
||||||
{@const children = childrenByParent[doc.attachedTo] ?? []}
|
{@const children = childrenByParent[doc.attachedTo] ?? []}
|
||||||
<TreeItem
|
{@const isDraggedOver = draggedOver === doc.attachedTo}
|
||||||
_id={doc._id}
|
<div class="flex-col relative">
|
||||||
icon={documents.icon.Document}
|
{#if isDraggedOver}
|
||||||
iconProps={{
|
<DropArea />
|
||||||
fill: 'currentColor'
|
{/if}
|
||||||
}}
|
<TreeItem
|
||||||
title={getDocumentName(doc)}
|
_id={doc._id}
|
||||||
selected={selected === doc._id || selected === prjdoc._id}
|
icon={documents.icon.Document}
|
||||||
isFold
|
iconProps={{
|
||||||
empty={children.length === 0 || children === undefined}
|
fill: 'currentColor'
|
||||||
actions={getMoreActions !== undefined ? () => getDocMoreActions(prjdoc) : undefined}
|
}}
|
||||||
{level}
|
title={getDocumentName(doc)}
|
||||||
{collapsedPrefix}
|
selected={selected === doc._id || selected === prjdoc._id}
|
||||||
shouldTooltip
|
isFold
|
||||||
on:click={() => {
|
empty={children.length === 0 || children === undefined}
|
||||||
dispatch('selected', prjdoc)
|
actions={getMoreActions !== undefined ? () => getDocMoreActions(prjdoc) : undefined}
|
||||||
}}
|
{level}
|
||||||
>
|
{collapsedPrefix}
|
||||||
<svelte:fragment slot="dropbox">
|
shouldTooltip
|
||||||
{#if children.length}
|
on:click={() => {
|
||||||
<svelte:self
|
dispatch('selected', prjdoc)
|
||||||
projectMeta={children}
|
}}
|
||||||
{childrenByParent}
|
draggable={onDragStart !== undefined}
|
||||||
{selected}
|
on:dragstart={(evt) => {
|
||||||
{collapsedPrefix}
|
onDragStart?.(evt, doc.attachedTo)
|
||||||
{getMoreActions}
|
}}
|
||||||
level={level + 1}
|
on:dragover={(evt) => {
|
||||||
on:selected
|
onDragOver?.(evt, doc.attachedTo)
|
||||||
/>
|
}}
|
||||||
{/if}
|
on:dragend={(evt) => {
|
||||||
</svelte:fragment>
|
onDragEnd?.(evt, doc.attachedTo)
|
||||||
</TreeItem>
|
}}
|
||||||
|
on:drop={(evt) => {
|
||||||
|
onDrop?.(evt, doc.attachedTo)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="dropbox">
|
||||||
|
{#if children.length}
|
||||||
|
<svelte:self
|
||||||
|
projectMeta={children}
|
||||||
|
{childrenByParent}
|
||||||
|
{selected}
|
||||||
|
{collapsedPrefix}
|
||||||
|
{getMoreActions}
|
||||||
|
level={level + 1}
|
||||||
|
{onDragStart}
|
||||||
|
{onDragOver}
|
||||||
|
{onDragEnd}
|
||||||
|
{onDrop}
|
||||||
|
{draggedItem}
|
||||||
|
{draggedOver}
|
||||||
|
on:selected
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
</TreeItem>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -16,7 +16,15 @@
|
|||||||
import { WithLookup, type Doc, type Ref, type Space } from '@hcengineering/core'
|
import { WithLookup, type Doc, type Ref, type Space } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { getResource } from '@hcengineering/platform'
|
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 { getActions as getContributedActions, TreeNode, TreeItem } from '@hcengineering/view-resources'
|
||||||
import { ActionGroup } from '@hcengineering/view'
|
import { ActionGroup } from '@hcengineering/view'
|
||||||
import {
|
import {
|
||||||
@ -40,11 +48,17 @@
|
|||||||
getProjectDocsHierarchy,
|
getProjectDocsHierarchy,
|
||||||
isEditableProject,
|
isEditableProject,
|
||||||
createDocument,
|
createDocument,
|
||||||
canCreateChildDocument
|
canCreateChildDocument,
|
||||||
|
moveDocument,
|
||||||
|
moveDocumentBefore,
|
||||||
|
moveDocumentAfter
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
|
|
||||||
import documents from '../../plugin'
|
import documents from '../../plugin'
|
||||||
|
|
||||||
|
import DropArea from './DropArea.svelte'
|
||||||
|
import DropMarker from './DropMarker.svelte'
|
||||||
|
|
||||||
export let space: DocumentSpace
|
export let space: DocumentSpace
|
||||||
export let currentSpace: Ref<Space> | undefined
|
export let currentSpace: Ref<Space> | undefined
|
||||||
export let currentFragment: string | undefined
|
export let currentFragment: string | undefined
|
||||||
@ -67,6 +81,7 @@
|
|||||||
let project: Ref<Project> = documents.ids.NoProject
|
let project: Ref<Project> = documents.ids.NoProject
|
||||||
$: void selectProject(space)
|
$: void selectProject(space)
|
||||||
|
|
||||||
|
let docsByMeta = new Map<Ref<DocumentMeta>, WithLookup<ProjectMeta>>()
|
||||||
let rootDocs: Array<WithLookup<ProjectMeta>> = []
|
let rootDocs: Array<WithLookup<ProjectMeta>> = []
|
||||||
let childrenByParent: Record<Ref<DocumentMeta>, Array<WithLookup<ProjectMeta>>> = {}
|
let childrenByParent: Record<Ref<DocumentMeta>, Array<WithLookup<ProjectMeta>>> = {}
|
||||||
|
|
||||||
@ -78,6 +93,7 @@
|
|||||||
project
|
project
|
||||||
},
|
},
|
||||||
(result) => {
|
(result) => {
|
||||||
|
docsByMeta = new Map(result.map((r) => [r.meta, r]))
|
||||||
;({ rootDocs, childrenByParent } = getProjectDocsHierarchy(result))
|
;({ rootDocs, childrenByParent } = getProjectDocsHierarchy(result))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -116,6 +132,23 @@
|
|||||||
selectedControlledDoc = undefined
|
selectedControlledDoc = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAllDescendants (obj: Ref<DocumentMeta>): Ref<DocumentMeta>[] {
|
||||||
|
const result: Ref<DocumentMeta>[] = []
|
||||||
|
const queue: Ref<DocumentMeta>[] = [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<void> {
|
async function selectProject (space: DocumentSpace): Promise<void> {
|
||||||
project = getCurrentProject(space._id) ?? (await getLatestProjectId(space._id, true)) ?? documents.ids.NoProject
|
project = getCurrentProject(space._id) ?? (await getLatestProjectId(space._id, true)) ?? documents.ids.NoProject
|
||||||
}
|
}
|
||||||
@ -181,75 +214,202 @@
|
|||||||
|
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let parent: HTMLElement
|
||||||
|
let draggedItem: Ref<DocumentMeta> | undefined = undefined
|
||||||
|
let draggedOver: Ref<DocumentMeta> | undefined = undefined
|
||||||
|
let draggedOverPos: 'before' | 'after' | undefined = undefined
|
||||||
|
let draggedOverTop: number = 0
|
||||||
|
let cannotDropTo: Ref<DocumentMeta>[] = []
|
||||||
|
|
||||||
|
function canDrop (object: Ref<DocumentMeta>, target: Ref<DocumentMeta>): boolean {
|
||||||
|
if (object === target) return false
|
||||||
|
if (cannotDropTo.includes(target)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragStart (event: DragEvent, object: Ref<DocumentMeta>): 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<DocumentMeta>): 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<DocumentMeta>): 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
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TreeNode
|
<div bind:this={parent} class="flex-col relative">
|
||||||
_id={space?._id}
|
{#if draggedOver === documents.ids.NoParent}
|
||||||
folderIcon
|
<DropArea />
|
||||||
iconProps={{
|
|
||||||
fill: getPlatformColorForTextDef(space.name, $themeStore.dark).icon
|
|
||||||
}}
|
|
||||||
title={space.name}
|
|
||||||
highlighted={space._id === currentSpace && currentFragment !== undefined && !deselect}
|
|
||||||
visible={(space._id === currentSpace && currentFragment !== undefined && !deselect) || forciblyСollapsed}
|
|
||||||
showMenu={pressed}
|
|
||||||
{forciblyСollapsed}
|
|
||||||
actions={() => getSpaceActions(space)}
|
|
||||||
type={'nested'}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="extra">
|
|
||||||
{#if spaceType?.projects === true}
|
|
||||||
<ProjectSelector
|
|
||||||
value={project}
|
|
||||||
space={space?._id}
|
|
||||||
maxWidth={'6rem'}
|
|
||||||
kind={'ghost'}
|
|
||||||
size={'x-small'}
|
|
||||||
showDropdownIcon
|
|
||||||
bind:pressed
|
|
||||||
on:change={(evt) => {
|
|
||||||
project = evt.detail
|
|
||||||
setCurrentProject(space._id, project)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</svelte:fragment>
|
|
||||||
|
|
||||||
{#if rootDocs.length > 0}
|
|
||||||
<DocHierarchyLevel
|
|
||||||
projectMeta={rootDocs}
|
|
||||||
{childrenByParent}
|
|
||||||
{selected}
|
|
||||||
getMoreActions={getDocumentActions}
|
|
||||||
on:selected={(e) => {
|
|
||||||
handleDocumentSelected(e.detail)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<div class="pseudo-element flex-row-center content-dark-color text-md nowrap">
|
|
||||||
<Label label={documents.string.NoDocuments} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<svelte:fragment slot="visible">
|
{#if draggedOver && draggedOverPos}
|
||||||
{#if (selected || forciblyСollapsed) && selectedControlledDoc}
|
<DropMarker top={draggedOverTop} />
|
||||||
{@const doc = selectedControlledDoc}
|
{/if}
|
||||||
<TreeItem
|
|
||||||
_id={doc._id}
|
<TreeNode
|
||||||
icon={documents.icon.Document}
|
_id={space?._id}
|
||||||
iconProps={{
|
folderIcon
|
||||||
fill: 'currentColor'
|
iconProps={{
|
||||||
|
fill: getPlatformColorForTextDef(space.name, $themeStore.dark).icon
|
||||||
|
}}
|
||||||
|
title={space.name}
|
||||||
|
highlighted={space._id === currentSpace && currentFragment !== undefined && !deselect}
|
||||||
|
visible={(space._id === currentSpace && currentFragment !== undefined && !deselect) || forciblyСollapsed}
|
||||||
|
showMenu={pressed}
|
||||||
|
{forciblyСollapsed}
|
||||||
|
actions={() => 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()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="extra">
|
||||||
|
{#if spaceType?.projects === true}
|
||||||
|
<ProjectSelector
|
||||||
|
value={project}
|
||||||
|
space={space?._id}
|
||||||
|
maxWidth={'6rem'}
|
||||||
|
kind={'ghost'}
|
||||||
|
size={'x-small'}
|
||||||
|
showDropdownIcon
|
||||||
|
bind:pressed
|
||||||
|
on:change={(evt) => {
|
||||||
|
project = evt.detail
|
||||||
|
setCurrentProject(space._id, project)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
{#if rootDocs.length > 0}
|
||||||
|
<DocHierarchyLevel
|
||||||
|
projectMeta={rootDocs}
|
||||||
|
{childrenByParent}
|
||||||
|
{selected}
|
||||||
|
getMoreActions={getDocumentActions}
|
||||||
|
on:selected={(e) => {
|
||||||
|
handleDocumentSelected(e.detail)
|
||||||
}}
|
}}
|
||||||
title={getDocumentName(doc)}
|
{onDragStart}
|
||||||
actions={() => getDocumentActions(doc)}
|
{onDragEnd}
|
||||||
selected
|
{onDragOver}
|
||||||
isFold
|
{onDrop}
|
||||||
empty
|
{draggedItem}
|
||||||
forciblyСollapsed
|
{draggedOver}
|
||||||
/>
|
/>
|
||||||
|
{:else}
|
||||||
|
<div class="pseudo-element flex-row-center content-dark-color text-md nowrap">
|
||||||
|
<Label label={documents.string.NoDocuments} />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
|
||||||
</TreeNode>
|
<svelte:fragment slot="visible">
|
||||||
|
{#if (selected || forciblyСollapsed) && selectedControlledDoc}
|
||||||
|
{@const doc = selectedControlledDoc}
|
||||||
|
<TreeItem
|
||||||
|
_id={doc._id}
|
||||||
|
icon={documents.icon.Document}
|
||||||
|
iconProps={{
|
||||||
|
fill: 'currentColor'
|
||||||
|
}}
|
||||||
|
title={getDocumentName(doc)}
|
||||||
|
actions={() => getDocumentActions(doc)}
|
||||||
|
selected
|
||||||
|
isFold
|
||||||
|
empty
|
||||||
|
forciblyСollapsed
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
</TreeNode>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.pseudo-element {
|
.pseudo-element {
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="drop-area" />
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.drop-area {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0.75rem;
|
||||||
|
right: 0.75rem;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
export let top: number
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="drop-marker" style="top: {top}px;" />
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.drop-marker {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
height: 0.125rem;
|
||||||
|
background-color: var(--primary-button-focused);
|
||||||
|
|
||||||
|
left: 0.75rem;
|
||||||
|
right: 0.75rem;
|
||||||
|
top: 10rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -11,51 +11,53 @@
|
|||||||
//
|
//
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 chunter from '@hcengineering/chunter'
|
||||||
|
import { type Employee, type Person, type PersonAccount } from '@hcengineering/contact'
|
||||||
import documents, {
|
import documents, {
|
||||||
type ControlledDocument,
|
type ControlledDocument,
|
||||||
type Document,
|
type Document,
|
||||||
type DocumentRequest,
|
|
||||||
type DocumentTemplate,
|
|
||||||
type DocumentSpace,
|
|
||||||
type DocumentCategory,
|
type DocumentCategory,
|
||||||
type DocumentMeta,
|
|
||||||
type DocumentComment,
|
type DocumentComment,
|
||||||
|
type DocumentMeta,
|
||||||
|
type DocumentRequest,
|
||||||
|
type DocumentSpace,
|
||||||
|
type DocumentTemplate,
|
||||||
type OrgSpace,
|
type OrgSpace,
|
||||||
type Project,
|
type Project,
|
||||||
type ProjectDocument,
|
type ProjectDocument,
|
||||||
type ProjectMeta,
|
type ProjectMeta,
|
||||||
ControlledDocumentState,
|
ControlledDocumentState,
|
||||||
DocumentState,
|
DocumentState,
|
||||||
getDocumentName
|
getDocumentName,
|
||||||
|
getFirstRank
|
||||||
} from '@hcengineering/controlled-documents'
|
} 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 { getProjectDocumentLink } from './navigation'
|
||||||
|
import documentsResources from './plugin'
|
||||||
import { wizardOpened } from './stores/wizards/create-document'
|
import { wizardOpened } from './stores/wizards/create-document'
|
||||||
|
|
||||||
export type TranslatedDocumentStates = Readonly<Record<DocumentState, string>>
|
export type TranslatedDocumentStates = Readonly<Record<DocumentState, string>>
|
||||||
@ -654,3 +656,41 @@ export function formatSignatureDate (date: number): string {
|
|||||||
second: 'numeric'
|
second: 'numeric'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function moveDocument (doc: ProjectMeta, space: Ref<Space>, target?: ProjectMeta): Promise<void> {
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
let parent = documents.ids.NoParent
|
||||||
|
let path: Array<Ref<DocumentMeta>> = []
|
||||||
|
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<void> {
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
const { space, parent, path } = before
|
||||||
|
const query = { rank: { $lt: before.rank } as unknown as QuerySelector<ProjectMeta['rank']> }
|
||||||
|
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<void> {
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
const { space, parent, path } = after
|
||||||
|
const query = { rank: { $gt: after.rank } as unknown as QuerySelector<ProjectMeta['rank']> }
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
@ -49,7 +49,8 @@
|
|||||||
"@hcengineering/training": "^0.1.0",
|
"@hcengineering/training": "^0.1.0",
|
||||||
"lexorank": "~1.0.4",
|
"lexorank": "~1.0.4",
|
||||||
"@hcengineering/activity": "^0.6.0",
|
"@hcengineering/activity": "^0.6.0",
|
||||||
"@hcengineering/chunter": "^0.6.20"
|
"@hcengineering/chunter": "^0.6.20",
|
||||||
|
"@hcengineering/rank": "^0.6.4"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"@hcengineering:registry": "https://npm.pkg.github.com"
|
"@hcengineering:registry": "https://npm.pkg.github.com"
|
||||||
|
@ -27,9 +27,10 @@ import {
|
|||||||
type ProjectDocument,
|
type ProjectDocument,
|
||||||
DocumentState
|
DocumentState
|
||||||
} from './types'
|
} from './types'
|
||||||
|
import { makeRank } from '@hcengineering/rank'
|
||||||
|
|
||||||
import documents from './plugin'
|
import documents from './plugin'
|
||||||
import { TEMPLATE_PREFIX } from './utils'
|
import { getFirstRank, TEMPLATE_PREFIX } from './utils'
|
||||||
|
|
||||||
async function getParentPath (client: TxOperations, parent: Ref<ProjectDocument>): Promise<Array<Ref<DocumentMeta>>> {
|
async function getParentPath (client: TxOperations, parent: Ref<ProjectDocument>): Promise<Array<Ref<DocumentMeta>>> {
|
||||||
const parentDocObj = await client.findOne(documents.class.ProjectDocument, {
|
const parentDocObj = await client.findOne(documents.class.ProjectDocument, {
|
||||||
@ -175,12 +176,16 @@ export async function createControlledDocMetadata (
|
|||||||
path = await getParentPath(client, parent)
|
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, {
|
const projectMetaId = await ops.createDoc(documents.class.ProjectMeta, space, {
|
||||||
project: projectId,
|
project: projectId,
|
||||||
meta: documentMetaId,
|
meta: documentMetaId,
|
||||||
path,
|
path,
|
||||||
parent: path[0] ?? documents.ids.NoParent,
|
parent: parentMeta,
|
||||||
documents: 0
|
documents: 0,
|
||||||
|
rank: makeRank(lastRank, undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
const projectDocumentId = await client.addCollection(
|
const projectDocumentId = await client.addCollection(
|
||||||
@ -323,12 +328,16 @@ export async function createDocumentTemplateMetadata (
|
|||||||
metaId
|
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, {
|
const projectMetaId = await ops.createDoc(documents.class.ProjectMeta, space, {
|
||||||
project: projectId,
|
project: projectId,
|
||||||
meta: documentMetaId,
|
meta: documentMetaId,
|
||||||
path,
|
path,
|
||||||
parent: path[0] ?? documents.ids.NoParent,
|
parent: path[0] ?? documents.ids.NoParent,
|
||||||
documents: 0
|
documents: 0,
|
||||||
|
rank: makeRank(lastRank, undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
const projectDocumentId = await client.addCollection(
|
const projectDocumentId = await client.addCollection(
|
||||||
|
@ -16,7 +16,8 @@ import {
|
|||||||
type TypedSpace,
|
type TypedSpace,
|
||||||
type Timestamp,
|
type Timestamp,
|
||||||
SpaceType,
|
SpaceType,
|
||||||
SpaceTypeDescriptor
|
SpaceTypeDescriptor,
|
||||||
|
Rank
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { type TagReference } from '@hcengineering/tags'
|
import { type TagReference } from '@hcengineering/tags'
|
||||||
import { Request } from '@hcengineering/request'
|
import { Request } from '@hcengineering/request'
|
||||||
@ -91,6 +92,8 @@ export interface ProjectMeta extends Doc {
|
|||||||
// head: Ref<HierarchyDocument>
|
// head: Ref<HierarchyDocument>
|
||||||
|
|
||||||
documents: CollectionSize<ProjectDocument>
|
documents: CollectionSize<ProjectDocument>
|
||||||
|
|
||||||
|
rank: Rank
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +12,17 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank'
|
||||||
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
|
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
|
||||||
|
|
||||||
@ -22,6 +32,7 @@ import {
|
|||||||
ChangeControl,
|
ChangeControl,
|
||||||
ControlledDocument,
|
ControlledDocument,
|
||||||
Document,
|
Document,
|
||||||
|
DocumentMeta,
|
||||||
DocumentSpace,
|
DocumentSpace,
|
||||||
DocumentState,
|
DocumentState,
|
||||||
Project,
|
Project,
|
||||||
@ -143,7 +154,8 @@ export async function copyProjectDocuments (
|
|||||||
meta: meta.meta,
|
meta: meta.meta,
|
||||||
path: meta.path,
|
path: meta.path,
|
||||||
parent: meta.parent,
|
parent: meta.parent,
|
||||||
documents: meta.documents
|
documents: meta.documents,
|
||||||
|
rank: meta.rank
|
||||||
})
|
})
|
||||||
|
|
||||||
// copy project docs attached to meta
|
// copy project docs attached to meta
|
||||||
@ -165,6 +177,26 @@ export async function copyProjectDocuments (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function getFirstRank (
|
||||||
|
client: TxOperations,
|
||||||
|
space: Ref<Space>,
|
||||||
|
project: Ref<Project>,
|
||||||
|
parent: Ref<DocumentMeta>,
|
||||||
|
sort: SortingOrder = SortingOrder.Descending,
|
||||||
|
extra: DocumentQuery<ProjectMeta> = {}
|
||||||
|
): Promise<Rank | undefined> {
|
||||||
|
const doc = await client.findOne(
|
||||||
|
documents.class.ProjectMeta,
|
||||||
|
{ space, project, parent, ...extra },
|
||||||
|
{ sort: { rank: sort }, projection: { rank: 1 } }
|
||||||
|
)
|
||||||
|
|
||||||
|
return doc?.rank
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
@ -108,6 +108,7 @@
|
|||||||
on:click
|
on:click
|
||||||
on:dragstart
|
on:dragstart
|
||||||
on:dragover
|
on:dragover
|
||||||
|
on:dragend
|
||||||
on:drop
|
on:drop
|
||||||
on:toggle={(ev) => {
|
on:toggle={(ev) => {
|
||||||
if (ev.detail !== undefined) collapsed = !ev.detail
|
if (ev.detail !== undefined) collapsed = !ev.detail
|
||||||
@ -176,6 +177,7 @@
|
|||||||
on:click
|
on:click
|
||||||
on:dragstart
|
on:dragstart
|
||||||
on:dragover
|
on:dragover
|
||||||
|
on:dragend
|
||||||
on:drop
|
on:drop
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
export let showNotify: boolean = false
|
export let showNotify: boolean = false
|
||||||
export let forciblyСollapsed: boolean = false
|
export let forciblyСollapsed: boolean = false
|
||||||
export let collapsedPrefix: string = ''
|
export let collapsedPrefix: string = ''
|
||||||
|
export let draggable: boolean = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TreeElement
|
<TreeElement
|
||||||
@ -62,7 +63,12 @@
|
|||||||
{showMenu}
|
{showMenu}
|
||||||
{noDivider}
|
{noDivider}
|
||||||
{forciblyСollapsed}
|
{forciblyСollapsed}
|
||||||
|
{draggable}
|
||||||
on:click
|
on:click
|
||||||
|
on:dragstart
|
||||||
|
on:dragover
|
||||||
|
on:dragend
|
||||||
|
on:drop
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="extra"><slot name="extra" /></svelte:fragment>
|
<svelte:fragment slot="extra"><slot name="extra" /></svelte:fragment>
|
||||||
<svelte:fragment slot="dropbox"><slot name="dropbox" /></svelte:fragment>
|
<svelte:fragment slot="dropbox"><slot name="dropbox" /></svelte:fragment>
|
||||||
|
Loading…
Reference in New Issue
Block a user