mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-02 05:09:26 +00:00
Fixes for document hierarchy presentation in QMS (#7938)
* Fixes for document hierarchy presentation in QMS Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com> * fixed test Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com> * styling fix Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com> * cooler names Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com> --------- Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>
This commit is contained in:
parent
50eac538a6
commit
0be8b860fc
@ -14,184 +14,70 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { type Ref, SortingOrder, getCurrentAccount } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { type PersonAccount } from '@hcengineering/contact'
|
||||
import documents, {
|
||||
type ControlledDocument,
|
||||
type DocumentMeta,
|
||||
import {
|
||||
DocumentBundle,
|
||||
ProjectDocumentTree,
|
||||
type HierarchyDocument,
|
||||
type Project,
|
||||
type ProjectMeta
|
||||
type Project
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
|
||||
import { compareDocs, notEmpty } from '../../../../utils'
|
||||
import { createDocumentHierarchyQuery } from '../../../../utils'
|
||||
import DocumentFlatTreeElement from './DocumentFlatTreeElement.svelte'
|
||||
|
||||
export let document: HierarchyDocument | undefined
|
||||
export let project: Ref<Project>
|
||||
|
||||
const currentUser = getCurrentAccount() as PersonAccount
|
||||
const currentPerson = currentUser.person
|
||||
let tree = new ProjectDocumentTree()
|
||||
|
||||
let meta: ProjectMeta | undefined
|
||||
const projectMetaQuery = createQuery()
|
||||
const query = createDocumentHierarchyQuery()
|
||||
$: if (document !== undefined && project !== undefined) {
|
||||
projectMetaQuery.query(
|
||||
documents.class.ProjectMeta,
|
||||
{
|
||||
project,
|
||||
meta: document.attachedTo
|
||||
},
|
||||
(result) => {
|
||||
;[meta] = result
|
||||
}
|
||||
)
|
||||
query.query(document.space, project, (data) => {
|
||||
tree = data
|
||||
})
|
||||
}
|
||||
|
||||
let parentMetas: ProjectMeta[] | undefined
|
||||
const parentMetasQuery = createQuery()
|
||||
$: if (meta !== undefined) {
|
||||
parentMetasQuery.query(
|
||||
documents.class.ProjectMeta,
|
||||
{
|
||||
meta: { $in: meta.path },
|
||||
project: meta.project
|
||||
},
|
||||
(result) => {
|
||||
parentMetas = result
|
||||
}
|
||||
)
|
||||
} else {
|
||||
parentMetasQuery.unsubscribe()
|
||||
}
|
||||
$: docmetaid = tree.metaOf(document?._id)
|
||||
|
||||
let directChildrenMetas: ProjectMeta[] | undefined
|
||||
const childrenMetasQuery = createQuery()
|
||||
$: if (meta !== undefined) {
|
||||
childrenMetasQuery.query(
|
||||
documents.class.ProjectMeta,
|
||||
{
|
||||
parent: meta?.meta,
|
||||
project: meta.project
|
||||
},
|
||||
(result) => {
|
||||
if (meta === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
directChildrenMetas = result
|
||||
}
|
||||
)
|
||||
} else {
|
||||
childrenMetasQuery.unsubscribe()
|
||||
}
|
||||
|
||||
let docs: Record<Ref<DocumentMeta>, ControlledDocument> = {}
|
||||
const docsQuery = createQuery()
|
||||
$: if (parentMetas !== undefined && directChildrenMetas !== undefined) {
|
||||
docsQuery.query(
|
||||
documents.class.ProjectDocument,
|
||||
{
|
||||
attachedTo: { $in: [...parentMetas.map((p) => p._id), ...directChildrenMetas.map((p) => p._id)] }
|
||||
},
|
||||
(result) => {
|
||||
docs = {}
|
||||
let lastTemplate: string | undefined = '###'
|
||||
let lastSeqNumber = -1
|
||||
|
||||
for (const prjdoc of result) {
|
||||
const doc = prjdoc.$lookup?.document as ControlledDocument | undefined
|
||||
if (doc === undefined) continue
|
||||
|
||||
// TODO add proper fix, when document with no template copied, saved value is null
|
||||
const template = doc.template ?? undefined
|
||||
|
||||
if (template === lastTemplate && doc.seqNumber === lastSeqNumber) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
doc.owner === currentPerson ||
|
||||
doc.coAuthors.findIndex((emp) => emp === currentPerson) >= 0 ||
|
||||
doc.approvers.findIndex((emp) => emp === currentPerson) >= 0 ||
|
||||
doc.reviewers.findIndex((emp) => emp === currentPerson) >= 0
|
||||
) {
|
||||
docs[doc.attachedTo] = doc
|
||||
|
||||
lastTemplate = template
|
||||
lastSeqNumber = doc.seqNumber
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
document: documents.class.ControlledDocument
|
||||
},
|
||||
sort: {
|
||||
'$lookup.document.template': SortingOrder.Ascending,
|
||||
'$lookup.document.seqNumber': SortingOrder.Ascending,
|
||||
'$lookup.document.major': SortingOrder.Descending,
|
||||
'$lookup.document.minor': SortingOrder.Descending,
|
||||
'$lookup.document.patch': SortingOrder.Descending
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
docsQuery.unsubscribe()
|
||||
}
|
||||
|
||||
let directChildrenDocs: ControlledDocument[] = []
|
||||
$: if (directChildrenMetas !== undefined) {
|
||||
directChildrenDocs = Object.values(docs).filter(
|
||||
(d) => directChildrenMetas !== undefined && directChildrenMetas.findIndex((m) => m.meta === d.attachedTo) >= 0
|
||||
)
|
||||
directChildrenDocs.sort(compareDocs)
|
||||
}
|
||||
|
||||
let parentDocs: ControlledDocument[] = []
|
||||
$: if (meta !== undefined) {
|
||||
parentDocs = [...meta.path]
|
||||
.reverse()
|
||||
.map((mId) => docs[mId])
|
||||
.filter(notEmpty)
|
||||
}
|
||||
|
||||
let levels: Array<[HierarchyDocument[], boolean]> = []
|
||||
let levels: Array<[DocumentBundle[], boolean]> = []
|
||||
$: {
|
||||
levels = []
|
||||
const parents = tree
|
||||
.parentChainOf(docmetaid)
|
||||
.reverse()
|
||||
.map((ref) => tree.bundleOf(ref))
|
||||
.filter((r) => r !== undefined)
|
||||
const me = tree.bundleOf(docmetaid)
|
||||
const children = tree
|
||||
.childrenOf(docmetaid)
|
||||
.map((ref) => tree.bundleOf(ref))
|
||||
.filter((r) => r !== undefined)
|
||||
|
||||
if (parentDocs?.length > 0) {
|
||||
levels.push([parentDocs, false])
|
||||
}
|
||||
|
||||
if (document !== undefined) {
|
||||
levels.push([[document], true])
|
||||
}
|
||||
|
||||
if (directChildrenDocs?.length > 0) {
|
||||
levels.push([directChildrenDocs, false])
|
||||
if (parents.length > 0) levels.push([parents as DocumentBundle[], false])
|
||||
if (me) {
|
||||
levels.push([[me], true])
|
||||
}
|
||||
if (children.length > 0) levels.push([children as DocumentBundle[], false])
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if levels.length > 0}
|
||||
{@const [firstDocs, firstHltd] = levels[0]}
|
||||
<div class="root">
|
||||
{#each firstDocs as doc}
|
||||
<DocumentFlatTreeElement {doc} {project} highlighted={firstHltd} />
|
||||
{#each firstDocs as bundle}
|
||||
<DocumentFlatTreeElement {bundle} {project} highlighted={firstHltd} />
|
||||
{/each}
|
||||
{#if levels.length > 1}
|
||||
{@const [secondDocs, secondHltd] = levels[1]}
|
||||
<div class="container">
|
||||
{#each secondDocs as doc}
|
||||
<DocumentFlatTreeElement {doc} {project} highlighted={secondHltd} />
|
||||
{#each secondDocs as bundle}
|
||||
<DocumentFlatTreeElement {bundle} {project} highlighted={secondHltd} />
|
||||
{/each}
|
||||
{#if levels.length > 2}
|
||||
{@const [thirdDocs, thirdHltd] = levels[2]}
|
||||
<div class="container">
|
||||
{#each thirdDocs as doc}
|
||||
<DocumentFlatTreeElement {doc} {project} highlighted={thirdHltd} />
|
||||
{#each thirdDocs as bundle}
|
||||
<DocumentFlatTreeElement {bundle} {project} highlighted={thirdHltd} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@ -215,5 +101,8 @@
|
||||
padding: 0 1rem;
|
||||
border-left: 2px solid var(--theme-navpanel-border);
|
||||
gap: 0.25rem;
|
||||
|
||||
padding-left: 0.25rem;
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -14,17 +14,25 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import documents, { getDocumentName, type Document, type Project } from '@hcengineering/controlled-documents'
|
||||
import documents, {
|
||||
DocumentBundle,
|
||||
getDocumentName,
|
||||
isFolder,
|
||||
type Project
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { Icon, navigate } from '@hcengineering/ui'
|
||||
|
||||
import { getProjectDocumentLink } from '../../../../navigation'
|
||||
|
||||
export let doc: Document
|
||||
export let bundle: DocumentBundle
|
||||
export let project: Ref<Project>
|
||||
export let highlighted: boolean = false
|
||||
|
||||
const icon = documents.icon.Document
|
||||
$: meta = bundle?.DocumentMeta[0]
|
||||
$: prjdoc = bundle?.ProjectDocument[0]
|
||||
$: document = bundle?.ControlledDocument[0]
|
||||
$: icon = isFolder(prjdoc) ? documents.icon.Folder : documents.icon.Document
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
@ -32,7 +40,8 @@
|
||||
<div
|
||||
class="antiNav-element root"
|
||||
on:click={() => {
|
||||
const loc = getProjectDocumentLink(doc, project)
|
||||
if (!document) return
|
||||
const loc = getProjectDocumentLink(document, project)
|
||||
navigate(loc)
|
||||
}}
|
||||
>
|
||||
@ -48,7 +57,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<span class="an-element__label" class:font-medium={highlighted}>
|
||||
{getDocumentName(doc)}
|
||||
{document ? getDocumentName(document) : meta.title}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -13,23 +13,20 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { type Ref, type Doc, SortingOrder, getCurrentAccount, WithLookup, toIdMap } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import type { PersonAccount } from '@hcengineering/contact'
|
||||
import { type Action } from '@hcengineering/ui'
|
||||
import documents, {
|
||||
type ControlledDocument,
|
||||
type DocumentMeta,
|
||||
type ProjectMeta,
|
||||
type ProjectDocument,
|
||||
getDocumentName,
|
||||
DocumentState
|
||||
DocumentState,
|
||||
ProjectDocumentTree,
|
||||
getDocumentName
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type Action } from '@hcengineering/ui'
|
||||
import { TreeItem } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let tree = new ProjectDocumentTree()
|
||||
export let documentIds: Ref<DocumentMeta>[] = []
|
||||
|
||||
export let projectMeta: ProjectMeta[] = []
|
||||
export let childrenByParent: Record<Ref<DocumentMeta>, Array<ProjectMeta>>
|
||||
export let selected: Ref<Doc> | undefined
|
||||
export let level: number = 0
|
||||
export let getMoreActions: ((obj: Doc, originalEvent?: MouseEvent) => Promise<Action[]>) | undefined = undefined
|
||||
@ -45,113 +42,27 @@
|
||||
|
||||
import DropArea from './DropArea.svelte'
|
||||
|
||||
const removeStates = [DocumentState.Obsolete, DocumentState.Deleted]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const currentUser = getCurrentAccount() as PersonAccount
|
||||
const currentPerson = currentUser.person
|
||||
|
||||
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(
|
||||
documents.class.ProjectDocument,
|
||||
{
|
||||
'$lookup.document.state': { $ne: DocumentState.Deleted },
|
||||
attachedTo: { $in: projectMeta.map((p) => p._id) }
|
||||
},
|
||||
(result) => {
|
||||
docs = []
|
||||
let lastTemplate: string | undefined = '###'
|
||||
let lastSeqNumber = -1
|
||||
|
||||
for (const prjdoc of result) {
|
||||
if (prjdoc.document === documents.ids.Folder) {
|
||||
docs.push(prjdoc)
|
||||
continue
|
||||
}
|
||||
|
||||
const doc = prjdoc.$lookup?.document as ControlledDocument | undefined
|
||||
if (doc === undefined) continue
|
||||
if (doc.state === DocumentState.Deleted) continue
|
||||
|
||||
// TODO add proper fix, when document with no template copied, saved value is null
|
||||
const template = doc.template ?? undefined
|
||||
if (template === lastTemplate && doc.seqNumber === lastSeqNumber) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
[DocumentState.Effective, DocumentState.Archived].includes(doc.state) ||
|
||||
doc.owner === currentPerson ||
|
||||
doc.coAuthors.findIndex((emp) => emp === currentPerson) >= 0 ||
|
||||
doc.approvers.findIndex((emp) => emp === currentPerson) >= 0 ||
|
||||
doc.reviewers.findIndex((emp) => emp === currentPerson) >= 0
|
||||
) {
|
||||
docs.push(prjdoc)
|
||||
|
||||
lastTemplate = template
|
||||
lastSeqNumber = doc.seqNumber
|
||||
}
|
||||
}
|
||||
|
||||
sortDocs(projectMeta)
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
document: documents.class.ControlledDocument
|
||||
},
|
||||
sort: {
|
||||
'$lookup.document.template': SortingOrder.Ascending,
|
||||
'$lookup.document.seqNumber': SortingOrder.Ascending,
|
||||
'$lookup.document.major': SortingOrder.Descending,
|
||||
'$lookup.document.minor': SortingOrder.Descending,
|
||||
'$lookup.document.patch': SortingOrder.Descending
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let docsMeta: DocumentMeta[] = []
|
||||
|
||||
const metaQuery = createQuery()
|
||||
$: metaQuery.query(documents.class.DocumentMeta, { _id: { $in: projectMeta.map((p) => p.meta) } }, (result) => {
|
||||
docsMeta = result
|
||||
})
|
||||
|
||||
async function getDocMoreActions (obj: Doc): Promise<Action[]> {
|
||||
return getMoreActions !== undefined ? await getMoreActions(obj) : []
|
||||
}
|
||||
|
||||
$: projectMetaById = toIdMap(projectMeta)
|
||||
$: docsMetaById = toIdMap(docsMeta)
|
||||
</script>
|
||||
|
||||
{#each docs as prjdoc}
|
||||
{@const pjmeta = projectMetaById.get(prjdoc.attachedTo)}
|
||||
{@const doc = prjdoc.$lookup?.document}
|
||||
{@const metaid = pjmeta?.meta}
|
||||
{@const meta = metaid ? docsMetaById.get(metaid) : undefined}
|
||||
{#each documentIds as metaid}
|
||||
{@const bundle = tree.bundleOf(metaid)}
|
||||
{@const prjdoc = bundle?.ProjectDocument[0]}
|
||||
{@const doc = bundle?.ControlledDocument[0]}
|
||||
{@const meta = bundle?.DocumentMeta[0]}
|
||||
{@const title = doc ? getDocumentName(doc) : meta?.title ?? ''}
|
||||
{@const docid = doc?._id ?? prjdoc._id}
|
||||
{@const isFolder = prjdoc.document === documents.ids.Folder}
|
||||
{@const isObsolete = doc ? doc.state === DocumentState.Obsolete : false}
|
||||
{@const children = metaid ? childrenByParent[metaid] ?? [] : []}
|
||||
{@const docid = doc?._id ?? prjdoc?._id}
|
||||
{@const isFolder = prjdoc?.document === documents.ids.Folder}
|
||||
{@const children = tree.childrenOf(metaid)}
|
||||
{@const isRemoved = doc && removeStates.includes(doc.state)}
|
||||
|
||||
{#if metaid && (!isObsolete || children.length > 0)}
|
||||
{#if prjdoc && metaid}
|
||||
{@const isDraggedOver = draggedOver === metaid}
|
||||
<div class="flex-col relative">
|
||||
{#if isDraggedOver}
|
||||
@ -161,7 +72,7 @@
|
||||
_id={docid}
|
||||
icon={isFolder ? documents.icon.Folder : documents.icon.Document}
|
||||
iconProps={{
|
||||
fill: isObsolete ? 'var(--dangerous-bg-color)' : 'currentColor'
|
||||
fill: isRemoved ? 'var(--dangerous-bg-color)' : 'currentColor'
|
||||
}}
|
||||
{title}
|
||||
selected={selected === docid || selected === prjdoc._id}
|
||||
@ -191,8 +102,8 @@
|
||||
<svelte:fragment slot="dropbox">
|
||||
{#if children.length}
|
||||
<svelte:self
|
||||
projectMeta={children}
|
||||
{childrenByParent}
|
||||
documentIds={children}
|
||||
{tree}
|
||||
{selected}
|
||||
{collapsedPrefix}
|
||||
{getMoreActions}
|
||||
|
@ -13,20 +13,14 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { WithLookup, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { getPlatformColorForTextDef, themeStore, getTreeCollapsed } from '@hcengineering/ui'
|
||||
import documents, {
|
||||
type DocumentMeta,
|
||||
type DocumentSpace,
|
||||
type Project,
|
||||
type ProjectMeta
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import documents, { ProjectDocumentTree, type DocumentSpace, type Project } from '@hcengineering/controlled-documents'
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { getPlatformColorForTextDef, themeStore } from '@hcengineering/ui'
|
||||
import { TreeNode } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { createDocumentHierarchyQuery } from '../../utils'
|
||||
import DocHierarchyLevel from './DocHierarchyLevel.svelte'
|
||||
import { getProjectDocsHierarchy } from '../../utils'
|
||||
|
||||
export let space: DocumentSpace
|
||||
export let project: Ref<Project> | undefined
|
||||
@ -34,24 +28,17 @@
|
||||
export let collapsedPrefix: string = ''
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
// let collapsed: boolean = getPrefixedTreeCollapsed(space._id, collapsedPrefix)
|
||||
// $: setPrefixedTreeCollapsed(space._id, collapsedPrefix, collapsed)
|
||||
// $: folderIcon = collapsed ? FolderCollapsed : FolderExpanded
|
||||
|
||||
let rootDocs: Array<WithLookup<ProjectMeta>> = []
|
||||
let childrenByParent: Record<Ref<DocumentMeta>, Array<WithLookup<ProjectMeta>>> = {}
|
||||
let tree = new ProjectDocumentTree()
|
||||
|
||||
const docsQuery = createQuery()
|
||||
$: docsQuery.query(
|
||||
documents.class.ProjectMeta,
|
||||
{
|
||||
space: space._id,
|
||||
project
|
||||
},
|
||||
(result) => {
|
||||
;({ rootDocs, childrenByParent } = getProjectDocsHierarchy(result))
|
||||
}
|
||||
)
|
||||
const query = createDocumentHierarchyQuery()
|
||||
$: if (document !== undefined && project !== undefined) {
|
||||
query.query(space._id, project, (data) => {
|
||||
tree = data
|
||||
})
|
||||
}
|
||||
|
||||
$: root = tree.childrenOf(documents.ids.NoParent)
|
||||
</script>
|
||||
|
||||
<TreeNode
|
||||
@ -63,12 +50,12 @@
|
||||
title={space.name}
|
||||
highlighted={selected !== undefined}
|
||||
selected={selected === undefined}
|
||||
empty={rootDocs.length === 0}
|
||||
empty={root.length === 0}
|
||||
{collapsedPrefix}
|
||||
type={'nested-selectable'}
|
||||
on:click={() => {
|
||||
dispatch('selected', space)
|
||||
}}
|
||||
>
|
||||
<DocHierarchyLevel projectMeta={rootDocs} {childrenByParent} {selected} {collapsedPrefix} on:selected />
|
||||
<DocHierarchyLevel documentIds={root} {tree} {selected} {collapsedPrefix} on:selected />
|
||||
</TreeNode>
|
||||
|
@ -13,20 +13,6 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
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,
|
||||
closeTooltip
|
||||
} from '@hcengineering/ui'
|
||||
import { getActions as getContributedActions, TreeNode, TreeItem } from '@hcengineering/view-resources'
|
||||
import { ActionGroup } from '@hcengineering/view'
|
||||
import {
|
||||
type ControlledDocument,
|
||||
type DocumentMeta,
|
||||
@ -34,27 +20,41 @@
|
||||
type DocumentSpaceType,
|
||||
type Project,
|
||||
type ProjectDocument,
|
||||
type ProjectMeta,
|
||||
ProjectDocumentTree,
|
||||
getDocumentName
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import { type Doc, type Ref, type Space, WithLookup } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
type Action,
|
||||
IconEdit,
|
||||
Label,
|
||||
closeTooltip,
|
||||
getPlatformColorForTextDef,
|
||||
navigate,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import { ActionGroup } from '@hcengineering/view'
|
||||
import { TreeItem, TreeNode, getActions as getContributedActions } from '@hcengineering/view-resources'
|
||||
|
||||
import ProjectSelector from '../project/ProjectSelector.svelte'
|
||||
import DocHierarchyLevel from './DocHierarchyLevel.svelte'
|
||||
import { getDocumentIdFromFragment, getProjectDocumentLink } from '../../navigation'
|
||||
import {
|
||||
canCreateChildDocument,
|
||||
canCreateChildFolder,
|
||||
createDocument,
|
||||
createDocumentHierarchyQuery,
|
||||
createFolder,
|
||||
getCurrentProject,
|
||||
getLatestProjectId,
|
||||
setCurrentProject,
|
||||
getProjectDocsHierarchy,
|
||||
isEditableProject,
|
||||
createDocument,
|
||||
canCreateChildDocument,
|
||||
moveDocument,
|
||||
moveDocumentBefore,
|
||||
moveDocumentAfter,
|
||||
canCreateChildFolder,
|
||||
createFolder
|
||||
moveDocumentBefore,
|
||||
setCurrentProject
|
||||
} from '../../utils'
|
||||
import ProjectSelector from '../project/ProjectSelector.svelte'
|
||||
import DocHierarchyLevel from './DocHierarchyLevel.svelte'
|
||||
|
||||
import documents from '../../plugin'
|
||||
|
||||
@ -83,27 +83,14 @@
|
||||
let project: Ref<Project> = documents.ids.NoProject
|
||||
$: void selectProject(space)
|
||||
|
||||
let docsByMeta = new Map<Ref<DocumentMeta>, WithLookup<ProjectMeta>>()
|
||||
let rootDocs: Array<WithLookup<ProjectMeta>> = []
|
||||
let childrenByParent: Record<Ref<DocumentMeta>, Array<WithLookup<ProjectMeta>>> = {}
|
||||
let tree = new ProjectDocumentTree()
|
||||
|
||||
const projectMetaQ = createQuery()
|
||||
$: projectMetaQ.query(
|
||||
documents.class.ProjectMeta,
|
||||
{
|
||||
space: space._id,
|
||||
project
|
||||
},
|
||||
(result) => {
|
||||
docsByMeta = new Map(result.map((r) => [r.meta, r]))
|
||||
;({ rootDocs, childrenByParent } = getProjectDocsHierarchy(result))
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
meta: documents.class.DocumentMeta
|
||||
}
|
||||
}
|
||||
)
|
||||
const query = createDocumentHierarchyQuery()
|
||||
$: if (document !== undefined && project !== undefined) {
|
||||
query.query(space._id, project, (data) => {
|
||||
tree = data
|
||||
})
|
||||
}
|
||||
|
||||
let selectedControlledDoc: ControlledDocument | undefined = undefined
|
||||
|
||||
@ -134,23 +121,6 @@
|
||||
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> {
|
||||
project = getCurrentProject(space._id) ?? (await getLatestProjectId(space._id, true)) ?? documents.ids.NoProject
|
||||
}
|
||||
@ -253,7 +223,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
cannotDropTo = [object, ...getAllDescendants(object)]
|
||||
cannotDropTo = [object, ...tree.descendantsOf(object)]
|
||||
|
||||
event.dataTransfer.effectAllowed = 'move'
|
||||
event.dataTransfer.dropEffect = 'move'
|
||||
@ -311,8 +281,8 @@
|
||||
return
|
||||
}
|
||||
if (draggedItem !== undefined && canDrop(draggedItem, object)) {
|
||||
const doc = docsByMeta.get(draggedItem)
|
||||
const target = docsByMeta.get(object)
|
||||
const doc = tree.bundleOf(draggedItem)?.ProjectMeta[0]
|
||||
const target = tree.bundleOf(object)?.ProjectMeta[0]
|
||||
|
||||
if (doc !== undefined && target !== undefined && doc._id !== target._id) {
|
||||
if (object === documents.ids.NoParent) {
|
||||
@ -385,10 +355,11 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
{#if rootDocs.length > 0}
|
||||
{@const root = tree.childrenOf(documents.ids.NoParent)}
|
||||
{#if root.length > 0}
|
||||
<DocHierarchyLevel
|
||||
projectMeta={rootDocs}
|
||||
{childrenByParent}
|
||||
{tree}
|
||||
documentIds={root}
|
||||
{selected}
|
||||
getMoreActions={getDocumentActions}
|
||||
on:selected={(e) => {
|
||||
|
@ -27,9 +27,12 @@ import documents, {
|
||||
type ProjectDocument,
|
||||
type ProjectMeta,
|
||||
ControlledDocumentState,
|
||||
type DocumentBundle,
|
||||
DocumentState,
|
||||
emptyBundle,
|
||||
getDocumentName,
|
||||
getFirstRank
|
||||
getFirstRank,
|
||||
ProjectDocumentTree
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import core, {
|
||||
type Class,
|
||||
@ -49,7 +52,7 @@ import core, {
|
||||
getCurrentAccount
|
||||
} from '@hcengineering/core'
|
||||
import { type IntlString, translate } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, 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'
|
||||
@ -867,3 +870,53 @@ export async function moveDocumentAfter (doc: ProjectMeta, after: ProjectMeta):
|
||||
|
||||
await client.update(doc, { parent, path, rank })
|
||||
}
|
||||
|
||||
export class DocumentHiearchyQuery {
|
||||
queries = {
|
||||
prjMeta: createQuery(),
|
||||
prjDoc: createQuery()
|
||||
}
|
||||
|
||||
bundle: DocumentBundle = { ...emptyBundle() }
|
||||
|
||||
handleUpdate (data: Partial<DocumentBundle>, callback: (tree: ProjectDocumentTree) => void): void {
|
||||
this.bundle = { ...this.bundle, ...data }
|
||||
callback(new ProjectDocumentTree(this.bundle))
|
||||
}
|
||||
|
||||
query (
|
||||
space: Ref<DocumentSpace>,
|
||||
project: Ref<Project<DocumentSpace>>,
|
||||
callback: (tree: ProjectDocumentTree) => void
|
||||
): void {
|
||||
project = project ?? documents.ids.NoProject
|
||||
|
||||
this.queries.prjMeta.query(
|
||||
documents.class.ProjectMeta,
|
||||
{ space, project },
|
||||
(ProjectMeta) => {
|
||||
const DocumentMeta = ProjectMeta.map((e) => e.$lookup?.meta).filter((e) => e !== undefined) as DocumentMeta[]
|
||||
const patch: Partial<DocumentBundle> = { ProjectMeta, DocumentMeta }
|
||||
this.handleUpdate(patch, callback)
|
||||
},
|
||||
{ lookup: { meta: documents.class.DocumentMeta } }
|
||||
)
|
||||
|
||||
this.queries.prjDoc.query(
|
||||
documents.class.ProjectDocument,
|
||||
{ space, project },
|
||||
(ProjectDocument) => {
|
||||
const ControlledDocument = ProjectDocument.map((e) => e.$lookup?.document as ControlledDocument).filter(
|
||||
(e) => e !== undefined
|
||||
)
|
||||
const patch: Partial<DocumentBundle> = { ProjectDocument, ControlledDocument }
|
||||
this.handleUpdate(patch, callback)
|
||||
},
|
||||
{ lookup: { document: documents.class.ControlledDocument } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function createDocumentHierarchyQuery (): DocumentHiearchyQuery {
|
||||
return new DocumentHiearchyQuery()
|
||||
}
|
||||
|
@ -20,10 +20,12 @@ import {
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
DocumentUpdate,
|
||||
getCurrentAccount,
|
||||
Rank,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Space,
|
||||
toIdMap,
|
||||
TxOperations
|
||||
} from '@hcengineering/core'
|
||||
import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank'
|
||||
@ -33,6 +35,8 @@ import documents from './plugin'
|
||||
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import { makeRank } from '@hcengineering/rank'
|
||||
import tags, { TagReference } from '@hcengineering/tags'
|
||||
import {
|
||||
ChangeControl,
|
||||
@ -47,7 +51,6 @@ import {
|
||||
ProjectDocument,
|
||||
ProjectMeta
|
||||
} from './types'
|
||||
import { makeRank } from '@hcengineering/rank'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -138,30 +141,152 @@ export async function deleteProjectDrafts (client: ApplyOperations, source: Ref<
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectDocumentTree {
|
||||
rootDocs: ProjectMeta[]
|
||||
childrenByParent: Map<Ref<DocumentMeta>, ProjectMeta[]>
|
||||
export function isCollaborator (doc: ControlledDocument, person: Ref<Employee>): boolean {
|
||||
return (
|
||||
doc.owner === person ||
|
||||
doc.coAuthors.includes(person) ||
|
||||
doc.approvers.includes(person) ||
|
||||
doc.reviewers.includes(person)
|
||||
)
|
||||
}
|
||||
|
||||
constructor (pjMeta: ProjectMeta[]) {
|
||||
this.rootDocs = []
|
||||
this.childrenByParent = new Map<Ref<DocumentMeta>, Array<ProjectMeta>>()
|
||||
export function isFolder (doc: ProjectDocument | undefined): boolean {
|
||||
return doc !== undefined && doc.document === documents.ids.Folder
|
||||
}
|
||||
|
||||
for (const meta of pjMeta) {
|
||||
const parentId = meta.path[0] ?? documents.ids.NoParent
|
||||
function extractPresentableStateFromDocumentBundle (bundle: DocumentBundle, prjmeta: ProjectMeta): DocumentBundle {
|
||||
bundle = { ...bundle }
|
||||
|
||||
if (!this.childrenByParent.has(parentId)) {
|
||||
this.childrenByParent.set(parentId, [])
|
||||
const person = getCurrentAccount().person as Ref<Employee>
|
||||
const documentById = toIdMap(bundle.ControlledDocument)
|
||||
|
||||
const getSortSequence = (prjdoc: ProjectDocument): number[] => {
|
||||
const doc = documentById.get(prjdoc.document as Ref<ControlledDocument>)
|
||||
return doc !== undefined ? [doc.seqNumber, doc.major, doc.minor, doc.createdOn ?? 0] : [0, 0, 0, 0]
|
||||
}
|
||||
|
||||
const prjdoc = bundle.ProjectDocument.filter((prjdoc) => {
|
||||
if (prjdoc.attachedTo !== prjmeta._id) return false
|
||||
if (isFolder(prjdoc)) return true
|
||||
const doc = documentById.get(prjdoc.document as Ref<ControlledDocument>)
|
||||
const isPublicState = doc?.state === DocumentState.Effective || doc?.state === DocumentState.Archived
|
||||
return doc !== undefined && (isPublicState || isCollaborator(doc, person))
|
||||
}).sort((a, b) => {
|
||||
const s0 = getSortSequence(a)
|
||||
const s1 = getSortSequence(b)
|
||||
return s0.reduce((r, v, i) => (r !== 0 ? r : s1[i] - v), 0)
|
||||
})[0]
|
||||
|
||||
const doc = prjdoc !== undefined ? documentById.get(prjdoc.document as Ref<ControlledDocument>) : undefined
|
||||
|
||||
bundle.ProjectMeta = [prjmeta]
|
||||
bundle.ProjectDocument = prjdoc !== undefined ? [prjdoc] : []
|
||||
bundle.ControlledDocument = doc !== undefined ? [doc] : []
|
||||
|
||||
return bundle
|
||||
}
|
||||
|
||||
export class ProjectDocumentTree {
|
||||
parents: Map<Ref<DocumentMeta>, Ref<DocumentMeta>>
|
||||
nodesChildren: Map<Ref<DocumentMeta>, DocumentBundle[]>
|
||||
nodes: Map<Ref<DocumentMeta>, DocumentBundle>
|
||||
links: Map<Ref<Doc>, Ref<DocumentMeta>>
|
||||
|
||||
constructor (bundle?: DocumentBundle) {
|
||||
bundle = { ...emptyBundle(), ...bundle }
|
||||
const { bundles, links } = compileBundles(bundle)
|
||||
this.links = links
|
||||
this.nodes = new Map()
|
||||
this.nodesChildren = new Map()
|
||||
this.parents = new Map()
|
||||
|
||||
bundles.sort((a, b) => {
|
||||
const rankA = a.ProjectMeta[0]?.rank ?? ''
|
||||
const rankB = b.ProjectMeta[0]?.rank ?? ''
|
||||
return rankA.localeCompare(rankB)
|
||||
})
|
||||
|
||||
for (const bundle of bundles) {
|
||||
const prjmeta = bundle.ProjectMeta[0]
|
||||
if (prjmeta === undefined) continue
|
||||
|
||||
const presentable = extractPresentableStateFromDocumentBundle(bundle, prjmeta)
|
||||
this.nodes.set(prjmeta.meta, presentable)
|
||||
|
||||
const parent = prjmeta.path[0] ?? documents.ids.NoParent
|
||||
this.parents.set(prjmeta.meta, parent)
|
||||
|
||||
if (!this.nodesChildren.has(parent)) {
|
||||
this.nodesChildren.set(parent, [])
|
||||
}
|
||||
this.nodesChildren.get(parent)?.push(bundle)
|
||||
}
|
||||
|
||||
this.childrenByParent.get(parentId)?.push(meta)
|
||||
const nodesForRemoval = new Set<Ref<DocumentMeta>>()
|
||||
for (const [id, node] of this.nodes) {
|
||||
const state = node.ControlledDocument[0]?.state
|
||||
const isRemoved = state === DocumentState.Obsolete || state === DocumentState.Deleted
|
||||
if (isRemoved) nodesForRemoval.add(id)
|
||||
}
|
||||
|
||||
if (parentId === documents.ids.NoParent) {
|
||||
this.rootDocs.push(meta)
|
||||
}
|
||||
for (const id of this.nodes.keys()) {
|
||||
if (!nodesForRemoval.has(id)) continue
|
||||
|
||||
const blocked = this.descendantsOf(id).some((node) => !nodesForRemoval.has(node))
|
||||
if (blocked) nodesForRemoval.delete(id)
|
||||
}
|
||||
|
||||
for (const id of nodesForRemoval) {
|
||||
this.nodes.delete(id)
|
||||
this.parents.delete(id)
|
||||
this.nodesChildren.delete(id)
|
||||
}
|
||||
|
||||
for (const [id, children] of this.nodesChildren) {
|
||||
this.nodesChildren.set(
|
||||
id,
|
||||
children.filter((c) => !nodesForRemoval.has(c.ProjectMeta[0].meta))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
getDescendants (parent: Ref<DocumentMeta>): Ref<DocumentMeta>[] {
|
||||
metaOf (ref: Ref<Doc> | undefined): Ref<DocumentMeta> | undefined {
|
||||
if (ref === undefined) return
|
||||
return this.links.get(ref)
|
||||
}
|
||||
|
||||
parentChainOf (ref: Ref<DocumentMeta> | undefined): Ref<DocumentMeta>[] {
|
||||
if (ref === undefined) return []
|
||||
// Found a bug that can cause path field to contain invalid state,
|
||||
// until we fix it with migration and a separate fix it's better to use parent.
|
||||
//
|
||||
// return this.bundleOf(ref)?.ProjectMeta[0]?.path ?? []
|
||||
const parents: Ref<DocumentMeta>[] = []
|
||||
while (this.parentOf(ref) !== documents.ids.NoParent) {
|
||||
ref = this.parentOf(ref)
|
||||
parents.push(ref)
|
||||
}
|
||||
return parents
|
||||
}
|
||||
|
||||
parentOf (ref: Ref<DocumentMeta> | undefined): Ref<DocumentMeta> {
|
||||
if (ref === undefined) {
|
||||
return documents.ids.NoParent
|
||||
}
|
||||
return this.parents.get(ref) ?? documents.ids.NoParent
|
||||
}
|
||||
|
||||
bundleOf (ref: Ref<DocumentMeta> | undefined): DocumentBundle | undefined {
|
||||
if (ref === undefined) return
|
||||
return this.nodes.get(ref)
|
||||
}
|
||||
|
||||
childrenOf (ref: Ref<DocumentMeta> | undefined): Ref<DocumentMeta>[] {
|
||||
if (ref === undefined) return []
|
||||
return this.nodesChildren.get(ref)?.map((p) => p.ProjectMeta[0].meta) ?? []
|
||||
}
|
||||
|
||||
descendantsOf (parent: Ref<DocumentMeta>): Ref<DocumentMeta>[] {
|
||||
const result: Ref<DocumentMeta>[] = []
|
||||
const queue: Ref<DocumentMeta>[] = [parent]
|
||||
|
||||
@ -169,8 +294,8 @@ class ProjectDocumentTree {
|
||||
const next = queue.pop()
|
||||
if (next === undefined) break
|
||||
|
||||
const children = this.childrenByParent.get(next) ?? []
|
||||
const childrenRefs = children.map((p) => p.meta)
|
||||
const children = this.nodesChildren.get(next) ?? []
|
||||
const childrenRefs = children.map((p) => p.ProjectMeta[0].meta)
|
||||
result.push(...childrenRefs)
|
||||
queue.push(...childrenRefs)
|
||||
}
|
||||
@ -184,8 +309,8 @@ export async function findProjectDocsHierarchy (
|
||||
space: Ref<DocumentSpace>,
|
||||
project?: Ref<Project<DocumentSpace>>
|
||||
): Promise<ProjectDocumentTree> {
|
||||
const pjMeta = await client.findAll(documents.class.ProjectMeta, { space, project })
|
||||
return new ProjectDocumentTree(pjMeta)
|
||||
const ProjectMeta = await client.findAll(documents.class.ProjectMeta, { space, project })
|
||||
return new ProjectDocumentTree({ ...emptyBundle(), ProjectMeta })
|
||||
}
|
||||
|
||||
export interface DocumentBundle {
|
||||
@ -201,7 +326,7 @@ export interface DocumentBundle {
|
||||
Attachment: Attachment[]
|
||||
}
|
||||
|
||||
function emptyBundle (): DocumentBundle {
|
||||
export function emptyBundle (): DocumentBundle {
|
||||
return {
|
||||
DocumentMeta: [],
|
||||
ProjectMeta: [],
|
||||
@ -216,6 +341,46 @@ function emptyBundle (): DocumentBundle {
|
||||
}
|
||||
}
|
||||
|
||||
export function compileBundles (all: DocumentBundle): {
|
||||
bundles: DocumentBundle[]
|
||||
links: Map<Ref<Doc>, Ref<DocumentMeta>>
|
||||
} {
|
||||
const bundles = new Map<Ref<DocumentMeta>, DocumentBundle>(all.DocumentMeta.map((m) => [m._id, { ...emptyBundle() }]))
|
||||
const links = new Map<Ref<Doc>, Ref<DocumentMeta>>()
|
||||
|
||||
const link = (ref: Ref<Doc>, lookup: Ref<Doc>): void => {
|
||||
const meta = links.get(lookup)
|
||||
if (meta !== undefined) links.set(ref, meta)
|
||||
}
|
||||
|
||||
const relink = (ref: Ref<Doc>, prop: keyof DocumentBundle, obj: DocumentBundle[typeof prop][0]): void => {
|
||||
const meta = links.get(ref)
|
||||
if (meta !== undefined) bundles.get(meta)?.[prop].push(obj as any)
|
||||
}
|
||||
|
||||
for (const m of all.DocumentMeta) links.set(m._id, m._id) // DocumentMeta -> DocumentMeta
|
||||
for (const m of all.ProjectMeta) links.set(m._id, m.meta) // ProjectMeta -> DocumentMeta
|
||||
for (const m of all.ProjectDocument) {
|
||||
link(m._id, m.attachedTo) // ProjectDocument -> ProjectMeta
|
||||
link(m.document, m.attachedTo) // ControlledDocument -> ProjectMeta
|
||||
}
|
||||
for (const m of all.ControlledDocument) link(m.changeControl, m.attachedTo) // ChangeControl -> ControlledDocument
|
||||
for (const m of all.DocumentRequest) link(m._id, m.attachedTo) // DocumentRequest -> ControlledDocument
|
||||
for (const m of all.DocumentSnapshot) link(m._id, m.attachedTo) // DocumentSnapshot -> ControlledDocument
|
||||
for (const m of all.ChatMessage) link(m._id, m.attachedTo) // ChatMessage -> (ControlledDocument | ChatMessage)
|
||||
for (const m of all.TagReference) link(m._id, m.attachedTo) // TagReference -> ControlledDocument
|
||||
for (const m of all.Attachment) link(m._id, m.attachedTo) // Attachment -> (ControlledDocument | ChatMessage)
|
||||
|
||||
let key: keyof DocumentBundle
|
||||
for (key in all) {
|
||||
all[key].forEach((value) => {
|
||||
relink(value._id, key, value)
|
||||
})
|
||||
}
|
||||
|
||||
return { bundles: Array.from(bundles.values()), links }
|
||||
}
|
||||
|
||||
export async function findAllDocumentBundles (
|
||||
client: TxOperations,
|
||||
ids: Ref<DocumentMeta>[]
|
||||
@ -293,40 +458,7 @@ export async function findAllDocumentBundles (
|
||||
...all.ControlledDocument.map((p) => p._id)
|
||||
])
|
||||
|
||||
const bundles = new Map<Ref<DocumentMeta>, DocumentBundle>(all.DocumentMeta.map((m) => [m._id, { ...emptyBundle() }]))
|
||||
const links = new Map<Ref<Doc>, Ref<DocumentMeta>>()
|
||||
|
||||
const link = (ref: Ref<Doc>, lookup: Ref<Doc>): void => {
|
||||
const meta = links.get(lookup)
|
||||
if (meta !== undefined) links.set(ref, meta)
|
||||
}
|
||||
|
||||
const relink = (ref: Ref<Doc>, prop: keyof DocumentBundle, obj: DocumentBundle[typeof prop][0]): void => {
|
||||
const meta = links.get(ref)
|
||||
if (meta !== undefined) bundles.get(meta)?.[prop].push(obj as any)
|
||||
}
|
||||
|
||||
for (const m of all.DocumentMeta) links.set(m._id, m._id) // DocumentMeta -> DocumentMeta
|
||||
for (const m of all.ProjectMeta) links.set(m._id, m.meta) // ProjectMeta -> DocumentMeta
|
||||
for (const m of all.ProjectDocument) {
|
||||
link(m._id, m.attachedTo) // ProjectDocument -> ProjectMeta
|
||||
link(m.document, m.attachedTo) // ControlledDocument -> ProjectMeta
|
||||
}
|
||||
for (const m of all.ControlledDocument) link(m.changeControl, m.attachedTo) // ChangeControl -> ControlledDocument
|
||||
for (const m of all.DocumentRequest) link(m._id, m.attachedTo) // DocumentRequest -> ControlledDocument
|
||||
for (const m of all.DocumentSnapshot) link(m._id, m.attachedTo) // DocumentSnapshot -> ControlledDocument
|
||||
for (const m of all.ChatMessage) link(m._id, m.attachedTo) // ChatMessage -> (ControlledDocument | ChatMessage)
|
||||
for (const m of all.TagReference) link(m._id, m.attachedTo) // TagReference -> ControlledDocument
|
||||
for (const m of all.Attachment) link(m._id, m.attachedTo) // Attachment -> (ControlledDocument | ChatMessage)
|
||||
|
||||
let key: keyof DocumentBundle
|
||||
for (key in all) {
|
||||
all[key].forEach((value) => {
|
||||
relink(value._id, key, value)
|
||||
})
|
||||
}
|
||||
|
||||
return Array.from(bundles.values())
|
||||
return compileBundles(all).bundles
|
||||
}
|
||||
|
||||
export async function findOneDocumentBundle (
|
||||
@ -369,7 +501,7 @@ async function _buildDocumentTransferContext (
|
||||
|
||||
const docIds = new Set<Ref<DocumentMeta>>(request.sourceDocumentIds)
|
||||
for (const id of request.sourceDocumentIds) {
|
||||
sourceTree.getDescendants(id).forEach((d) => docIds.add(d))
|
||||
sourceTree.descendantsOf(id).forEach((d) => docIds.add(d))
|
||||
}
|
||||
|
||||
const bundles = await findAllDocumentBundles(client, Array.from(docIds))
|
||||
|
@ -1094,11 +1094,11 @@ test.describe('QMS. Documents tests', () => {
|
||||
await documentContentPage.checkDocument(documentDetails)
|
||||
await documentContentPage.checkDocumentStatus(DocumentStatus.IN_REVIEW)
|
||||
|
||||
await expect(documentContentPage.contentLocator.locator('h1:first-child')).toHaveText(overview.heading)
|
||||
await expect(documentContentPage.contentLocator.locator('h1:first-child + p')).toHaveText(overview.content)
|
||||
await expect(documentContentPage.contentLocator.locator('h1:nth-of-type(1)')).toHaveText(overview.heading)
|
||||
await expect(documentContentPage.contentLocator.locator('h1:nth-of-type(1) + p')).toHaveText(overview.content)
|
||||
|
||||
await expect(documentContentPage.contentLocator.locator('h1:not(:first-child)')).toHaveText(main.heading)
|
||||
await expect(documentContentPage.contentLocator.locator('h1:not(:first-child) + p')).toHaveText(main.content)
|
||||
await expect(documentContentPage.contentLocator.locator('h1:nth-of-type(2)')).toHaveText(main.heading)
|
||||
await expect(documentContentPage.contentLocator.locator('h1:nth-of-type(2) + p')).toHaveText(main.content)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user