From 40931c81ca95802f904360e044ecd525dcc1df7c Mon Sep 17 00:00:00 2001 From: Victor Ilyushchenko Date: Tue, 28 Jan 2025 10:02:13 +0300 Subject: [PATCH] EZQMS-1393: implemented folders in controlled documents (#7803) * EZQMS-1393: implemented folders in controlled documents Signed-off-by: Victor Ilyushchenko * formatting Signed-off-by: Victor Ilyushchenko --------- Signed-off-by: Victor Ilyushchenko --- models/controlled-documents/src/index.ts | 124 +++++++++++-- models/controlled-documents/src/plugin.ts | 4 + .../assets/icons.svg | 4 + .../controlled-documents-assets/lang/cs.json | 6 +- .../controlled-documents-assets/lang/de.json | 6 +- .../controlled-documents-assets/lang/en.json | 6 +- .../controlled-documents-assets/lang/fr.json | 6 +- .../controlled-documents-assets/lang/it.json | 6 +- .../controlled-documents-assets/lang/ru.json | 6 +- .../controlled-documents-assets/lang/zh.json | 6 +- .../controlled-documents-assets/src/index.ts | 1 + .../package.json | 3 +- .../components/create-doc/CreateFolder.svelte | 83 +++++++++ .../hierarchy/DocHierarchyLevel.svelte | 45 +++-- .../hierarchy/DocumentSpacePresenter.svelte | 19 +- .../src/index.ts | 142 ++++++++++----- .../src/plugin.ts | 6 + .../src/utils.ts | 168 ++++++++++++++++++ plugins/controlled-documents/src/docutils.ts | 52 ++++++ plugins/controlled-documents/src/plugin.ts | 19 +- plugins/controlled-documents/src/utils.ts | 4 +- 21 files changed, 633 insertions(+), 83 deletions(-) create mode 100644 plugins/controlled-documents-resources/src/components/create-doc/CreateFolder.svelte diff --git a/models/controlled-documents/src/index.ts b/models/controlled-documents/src/index.ts index 2f4ebf8dbc..0f6085136d 100644 --- a/models/controlled-documents/src/index.ts +++ b/models/controlled-documents/src/index.ts @@ -66,6 +66,8 @@ import { TDocumentComment } from './types' import { defineSpaceType } from './spaceType' +import { type Class, type Doc, type Ref } from '@hcengineering/core' +import { type Action } from '@hcengineering/view' export { documentsId } from '@hcengineering/controlled-documents/src/index' export * from './types' @@ -459,6 +461,51 @@ export function createModel (builder: Builder): void { documentsPlugin.action.CreateChildTemplate ) + createAction( + builder, + { + action: documents.actionImpl.CreateChildFolder, + label: documentsPlugin.string.CreateChildFolder, + icon: documents.icon.Folder, + category: view.category.General, + input: 'focus', // should only work for one document, not bulk + target: documents.class.ProjectDocument, + visibilityTester: documents.function.CanCreateChildFolder, + context: { mode: ['context'], group: 'create' } + }, + documentsPlugin.action.CreateChildFolder + ) + + createAction( + builder, + { + action: documents.actionImpl.RenameFolder, + label: view.string.Rename, + icon: view.icon.Edit, + category: view.category.General, + input: 'focus', // should only work for one document, not bulk + target: documents.class.ProjectDocument, + visibilityTester: documents.function.CanRenameFolder, + context: { mode: ['context'], group: 'edit' } + }, + documentsPlugin.action.RenameFolder + ) + + createAction( + builder, + { + action: documents.actionImpl.DeleteFolder, + label: view.string.Delete, + icon: view.icon.Delete, + category: view.category.General, + input: 'focus', // should only work for one document, not bulk + target: documents.class.ProjectDocument, + visibilityTester: documents.function.CanDeleteFolder, + context: { mode: ['context'], group: 'remove' } + }, + documentsPlugin.action.DeleteFolder + ) + createAction( builder, { @@ -489,6 +536,21 @@ export function createModel (builder: Builder): void { documentsPlugin.action.CreateTemplate ) + createAction( + builder, + { + action: documents.actionImpl.CreateFolder, + label: documentsPlugin.string.CreateFolder, + icon: documents.icon.Folder, + category: view.category.General, + input: 'none', + target: documents.class.DocumentSpace, + visibilityTester: documents.function.CanCreateFolder, + context: { mode: ['context'], group: 'create' } + }, + documentsPlugin.action.CreateFolder + ) + createAction( builder, { @@ -758,22 +820,60 @@ export function createModel (builder: Builder): void { createAction( builder, { - action: print.actionImpl.Print, - actionProps: { - signed: true - }, - label: print.string.PrintToPDF, - icon: print.icon.Print, + action: view.actionImpl.Open, + label: view.string.Open, + icon: view.icon.Open, + keyBinding: ['Enter'], + input: 'focus', category: view.category.General, - input: 'focus', // NOTE: should only work for one doc for now, not bulk - target: documents.class.Document, - context: { mode: ['context', 'browser'], group: 'tools' }, - visibilityTester: print.function.CanPrint, - override: [print.action.Print] + target: documents.class.ProjectDocument, + context: { mode: ['browser', 'context'], group: 'edit' }, + visibilityTester: documents.function.CanOpenDocument, + override: [view.action.Open] }, - documents.action.Print + documents.action.OpenDocument ) + createAction( + builder, + { + action: view.actionImpl.OpenInNewTab, + label: view.string.OpenInNewTab, + icon: view.icon.Open, + input: 'focus', + category: view.category.General, + target: documents.class.ProjectDocument, + context: { mode: ['browser', 'context'], group: 'edit' }, + visibilityTester: documents.function.CanOpenDocument, + override: [view.action.OpenInNewTab] + }, + documents.action.OpenDocumentInNewTab + ) + + function createPrintAction (target: Ref>, id?: Ref>): void { + createAction( + builder, + { + action: print.actionImpl.Print, + actionProps: { + signed: true + }, + label: print.string.PrintToPDF, + icon: print.icon.Print, + category: view.category.General, + input: 'focus', // NOTE: should only work for one doc for now, not bulk + target, + context: { mode: ['context', 'browser'], group: 'tools' }, + visibilityTester: documents.function.CanPrintDocument, + override: [print.action.Print] + }, + id + ) + } + + createPrintAction(documents.class.ProjectDocument, documents.action.PrintProjectDocument) + createPrintAction(documents.class.Document, documents.action.Print) + defineSpaceType(builder) definePermissions(builder) defineNotifications(builder) diff --git a/models/controlled-documents/src/plugin.ts b/models/controlled-documents/src/plugin.ts index 892d439f7e..64e6f5692b 100644 --- a/models/controlled-documents/src/plugin.ts +++ b/models/controlled-documents/src/plugin.ts @@ -59,8 +59,12 @@ export default mergeIds(documentsId, documents, { actionImpl: { CreateChildDocument: '' as ViewAction, CreateChildTemplate: '' as ViewAction, + CreateChildFolder: '' as ViewAction, + RenameFolder: '' as ViewAction, + DeleteFolder: '' as ViewAction, CreateDocument: '' as ViewAction, CreateTemplate: '' as ViewAction, + CreateFolder: '' as ViewAction, TransferTemplate: '' as ViewAction, DeleteDocument: '' as ViewAction, ArchiveDocument: '' as ViewAction, diff --git a/plugins/controlled-documents-assets/assets/icons.svg b/plugins/controlled-documents-assets/assets/icons.svg index cc6dd2356b..83134837a7 100644 --- a/plugins/controlled-documents-assets/assets/icons.svg +++ b/plugins/controlled-documents-assets/assets/icons.svg @@ -62,4 +62,8 @@ + + + + diff --git a/plugins/controlled-documents-assets/lang/cs.json b/plugins/controlled-documents-assets/lang/cs.json index 8eba26f62e..e2619c2ef8 100644 --- a/plugins/controlled-documents-assets/lang/cs.json +++ b/plugins/controlled-documents-assets/lang/cs.json @@ -291,7 +291,11 @@ "Transfer": "Přenos", "TransferWarning": "Někteří členové týmu mohou po této akci ztratit možnost prohlížet nebo upravovat tento dokument.", "TransferDocuments": "Přenos řízených dokumentů", - "TransferDocumentsHint": "Dokumenty, které mají být přeneseny do vybraného prostoru:" + "TransferDocumentsHint": "Dokumenty, které mají být přeneseny do vybraného prostoru:", + + "CreateFolder": "Vytvořit novou složku", + "RenameFolder": "Přejmenovat složku", + "CreateChildFolder": "Vytvořit podsložku" }, "controlledDocStates": { "Empty": "", diff --git a/plugins/controlled-documents-assets/lang/de.json b/plugins/controlled-documents-assets/lang/de.json index 0298bad71a..22ef0595b0 100644 --- a/plugins/controlled-documents-assets/lang/de.json +++ b/plugins/controlled-documents-assets/lang/de.json @@ -298,7 +298,11 @@ "Transfer": "Übertragung", "TransferWarning": "Einige Teammitglieder können dieses Dokument nach dieser Aktion möglicherweise nicht mehr anzeigen oder bearbeiten.", "TransferDocuments": "Übertragung kontrollierter Dokumente", - "TransferDocumentsHint": "Dokumente, die in den ausgewählten Bereich übertragen werden sollen:" + "TransferDocumentsHint": "Dokumente, die in den ausgewählten Bereich übertragen werden sollen:", + + "CreateFolder": "Neuen Ordner erstellen", + "RenameFolder": "Ordner umbenennen", + "CreateChildFolder": "Unterordner erstellen" }, "controlledDocStates": { "Empty": "", diff --git a/plugins/controlled-documents-assets/lang/en.json b/plugins/controlled-documents-assets/lang/en.json index 9f981bf2ee..0ad18214f6 100644 --- a/plugins/controlled-documents-assets/lang/en.json +++ b/plugins/controlled-documents-assets/lang/en.json @@ -300,7 +300,11 @@ "Transfer": "Transfer", "TransferWarning": "Some team members may lose the ability to view or edit this document after this action.", "TransferDocuments": "Transfer controlled documents", - "TransferDocumentsHint": "Documents to be transferred to the selected space:" + "TransferDocumentsHint": "Documents to be transferred to the selected space:", + + "CreateFolder": "Create new folder", + "RenameFolder": "Rename folder", + "CreateChildFolder": "Create child folder" }, "controlledDocStates": { "Empty": "", diff --git a/plugins/controlled-documents-assets/lang/fr.json b/plugins/controlled-documents-assets/lang/fr.json index 0378c61382..9100cfe799 100644 --- a/plugins/controlled-documents-assets/lang/fr.json +++ b/plugins/controlled-documents-assets/lang/fr.json @@ -258,7 +258,11 @@ "Transfer": "Transfert", "TransferWarning": "Certains membres de l'équipe peuvent perdre la possibilité de visualiser ou de modifier ce document après cette action.", "TransferDocuments": "Transférer des documents contrôlés", - "TransferDocumentsHint": "Documents à transférer dans l'espace sélectionné:" + "TransferDocumentsHint": "Documents à transférer dans l'espace sélectionné:", + + "CreateFolder": "Créer un nouveau dossier", + "RenameFolder": "Renommer le dossier", + "CreateChildFolder": "Créer un sous-dossier" }, "controlledDocStates": { "Empty": "", diff --git a/plugins/controlled-documents-assets/lang/it.json b/plugins/controlled-documents-assets/lang/it.json index 561f79624a..61fe609756 100644 --- a/plugins/controlled-documents-assets/lang/it.json +++ b/plugins/controlled-documents-assets/lang/it.json @@ -256,7 +256,11 @@ "Transfer": "Trasferimento", "TransferWarning": "Alcuni membri del team potrebbero perdere la possibilità di visualizzare o modificare il documento dopo questa azione.", "TransferDocuments": "Trasferimento di documenti controllati", - "TransferDocumentsHint": "Documenti da trasferire nello spazio selezionato:" + "TransferDocumentsHint": "Documenti da trasferire nello spazio selezionato:", + + "CreateFolder": "Crea nuova cartella", + "RenameFolder": "Rinomina cartella", + "CreateChildFolder": "Crea sottocartella" }, "controlledDocStates": { "Empty": "", diff --git a/plugins/controlled-documents-assets/lang/ru.json b/plugins/controlled-documents-assets/lang/ru.json index 1b4864e0cb..3afb969e8d 100644 --- a/plugins/controlled-documents-assets/lang/ru.json +++ b/plugins/controlled-documents-assets/lang/ru.json @@ -300,7 +300,11 @@ "Transfer": "Трансфер", "TransferWarning": "После этого действия некоторые члены команды могут потерять возможность просматривать или редактировать этот документ.", "TransferDocuments": "Трансфер управляемых документов", - "TransferDocumentsHint": "Документы, которые будут перенесены в выбранное пространство:" + "TransferDocumentsHint": "Документы, которые будут перенесены в выбранное пространство:", + + "CreateFolder": "Создать новую папку", + "RenameFolder": "Переименовать папку", + "CreateChildFolder": "Создать подпапку" }, "controlledDocStates": { "Empty": "", diff --git a/plugins/controlled-documents-assets/lang/zh.json b/plugins/controlled-documents-assets/lang/zh.json index 930b82681f..83021ea4c1 100644 --- a/plugins/controlled-documents-assets/lang/zh.json +++ b/plugins/controlled-documents-assets/lang/zh.json @@ -297,7 +297,11 @@ "Transfer": "转让", "TransferWarning": "执行此操作后,某些团队成员可能会失去查看或编辑此文档的能力", "TransferDocuments": "移交受控文件", - "TransferDocumentsHint": "要转移到所选空间的文件:" + "TransferDocumentsHint": "要转移到所选空间的文件:", + + "CreateFolder": "创建新文件夹", + "RenameFolder": "重命名文件夹", + "CreateChildFolder": "创建子文件夹" }, "controlledDocStates": { "Empty": "", diff --git a/plugins/controlled-documents-assets/src/index.ts b/plugins/controlled-documents-assets/src/index.ts index c6e4ca615e..08fd8498fb 100644 --- a/plugins/controlled-documents-assets/src/index.ts +++ b/plugins/controlled-documents-assets/src/index.ts @@ -21,6 +21,7 @@ loadMetadata(documents.icon, { Approvals: `${icons}#approvals`, DocumentApplication: `${icons}#documentapplication`, NewDocument: `${icons}#newdocument`, + Folder: `${icons}#c-folder`, Document: `${icons}#document`, Library: `${icons}#library`, StateApproved: `${icons}#state-approved`, diff --git a/plugins/controlled-documents-resources/package.json b/plugins/controlled-documents-resources/package.json index 2443c51d7f..fc5e4b1b66 100644 --- a/plugins/controlled-documents-resources/package.json +++ b/plugins/controlled-documents-resources/package.json @@ -71,6 +71,7 @@ "svelte": "^4.2.19", "slugify": "^1.6.6", "fast-equals": "^5.0.1", - "@hcengineering/rank": "^0.6.4" + "@hcengineering/rank": "^0.6.4", + "@hcengineering/print": "^0.6.0" } } diff --git a/plugins/controlled-documents-resources/src/components/create-doc/CreateFolder.svelte b/plugins/controlled-documents-resources/src/components/create-doc/CreateFolder.svelte new file mode 100644 index 0000000000..7ac4eb7de2 --- /dev/null +++ b/plugins/controlled-documents-resources/src/components/create-doc/CreateFolder.svelte @@ -0,0 +1,83 @@ + + + + + + { + dispatch('close') + }} + on:changeContent +> +
+ +
+
diff --git a/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte b/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte index aa88f64bff..f878052779 100644 --- a/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte +++ b/plugins/controlled-documents-resources/src/components/hierarchy/DocHierarchyLevel.svelte @@ -14,7 +14,7 @@ --> {#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} + {@const title = doc ? getDocumentName(doc) : meta?.title ?? ''} + {@const docid = doc?._id ?? prjdoc._id} + {@const isFolder = prjdoc.document === documents.ids.Folder} - {#if doc} - {@const children = childrenByParent[doc.attachedTo] ?? []} - {@const isDraggedOver = draggedOver === doc.attachedTo} + {#if metaid} + {@const children = childrenByParent[metaid] ?? []} + {@const isDraggedOver = draggedOver === metaid}
{#if isDraggedOver} {/if} getDocMoreActions(prjdoc) : undefined} @@ -154,16 +175,16 @@ }} draggable={onDragStart !== undefined} on:dragstart={(evt) => { - onDragStart?.(evt, doc.attachedTo) + onDragStart?.(evt, metaid) }} on:dragover={(evt) => { - onDragOver?.(evt, doc.attachedTo) + onDragOver?.(evt, metaid) }} on:dragend={(evt) => { - onDragEnd?.(evt, doc.attachedTo) + onDragEnd?.(evt, metaid) }} on:drop={(evt) => { - onDrop?.(evt, doc.attachedTo) + onDrop?.(evt, metaid) }} > diff --git a/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte b/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte index fe189491da..14db33b85b 100644 --- a/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte +++ b/plugins/controlled-documents-resources/src/components/hierarchy/DocumentSpacePresenter.svelte @@ -51,7 +51,9 @@ canCreateChildDocument, moveDocument, moveDocumentBefore, - moveDocumentAfter + moveDocumentAfter, + canCreateChildFolder, + createFolder } from '../../utils' import documents from '../../plugin' @@ -178,6 +180,21 @@ }) } + if ( + spaceType?.projects === true && + (await isEditableProject(project)) && + (await canCreateChildFolder(space, true)) + ) { + actions.push({ + icon: documents.icon.Folder, + label: documents.string.CreateFolder, + group: 'create', + action: async () => { + await createFolder(space) + } + }) + } + return orderActions(actions) } diff --git a/plugins/controlled-documents-resources/src/index.ts b/plugins/controlled-documents-resources/src/index.ts index 0a7cb8032b..5f22385e3f 100644 --- a/plugins/controlled-documents-resources/src/index.ts +++ b/plugins/controlled-documents-resources/src/index.ts @@ -12,54 +12,54 @@ // See the License for the specific language governing permissions and // limitations under the License. // +import { type PersonAccount } from '@hcengineering/contact' import { + type Document, + type DocumentMeta, + type DocumentSpace, + DocumentState, + type Project, + type ProjectDocument +} from '@hcengineering/controlled-documents' +import { + checkPermission, type Class, type Client, + type Doc, type DocumentQuery, + getCurrentAccount, type Ref, type RelatedDocument, SortingOrder, - type WithLookup, - type Doc, - getCurrentAccount, - checkPermission + type WithLookup } from '@hcengineering/core' -import { - type Document, - type DocumentSpace, - DocumentState, - type DocumentMeta, - type ProjectDocument, - type Project -} from '@hcengineering/controlled-documents' import { type Resources } from '@hcengineering/platform' -import { type ObjectSearchResult, getClient, MessageBox } from '@hcengineering/presentation' +import { getClient, MessageBox, type ObjectSearchResult } from '@hcengineering/presentation' import { showPopup } from '@hcengineering/ui' -import { type PersonAccount } from '@hcengineering/contact' import CreateDocument from './components/CreateDocument.svelte' +import DeleteCategoryPopup from './components/category/popups/DeleteCategoryPopup.svelte' import QmsDocumentWizard from './components/create-doc/QmsDocumentWizard.svelte' import QmsTemplateWizard from './components/create-doc/QmsTemplateWizard.svelte' import DocumentStatusTag from './components/document/common/DocumentStatusTag.svelte' -import DocumentSpacePresenter from './components/hierarchy/DocumentSpacePresenter.svelte' -import DocumentPresenter from './components/document/presenters/DocumentPresenter.svelte' -import StatePresenter from './components/document/presenters/StatePresenter.svelte' -import StateFilterValuePresenter from './components/document/presenters/StateFilterValuePresenter.svelte' -import TitlePresenter from './components/document/presenters/TitlePresenter.svelte' -import OwnerPresenter from './components/document/presenters/OwnerPresenter.svelte' import AddCommentPopup from './components/document/popups/AddCommentPopup.svelte' -import DocumentCommentsPopup from './components/document/popups/DocumentCommentsPopup.svelte' import ChangeOwnerPopup from './components/document/popups/ChangeOwnerPopup.svelte' -import DeleteCategoryPopup from './components/category/popups/DeleteCategoryPopup.svelte' +import DocumentCommentsPopup from './components/document/popups/DocumentCommentsPopup.svelte' +import DocumentPresenter from './components/document/presenters/DocumentPresenter.svelte' +import OwnerPresenter from './components/document/presenters/OwnerPresenter.svelte' +import StateFilterValuePresenter from './components/document/presenters/StateFilterValuePresenter.svelte' +import StatePresenter from './components/document/presenters/StatePresenter.svelte' +import TitlePresenter from './components/document/presenters/TitlePresenter.svelte' +import DocumentSpacePresenter from './components/hierarchy/DocumentSpacePresenter.svelte' -import CategoryPresenter from './components/category/presenters/CategoryPresenter.svelte' +import DocumentItem from './components/DocumentItem.svelte' import Documents from './components/Documents.svelte' import DocumentsContainer from './components/DocumentsContainer.svelte' -import MyDocuments from './components/MyDocuments.svelte' import EditDoc from './components/EditDoc.svelte' import EditProjectDoc from './components/EditProjectDoc.svelte' -import DocumentItem from './components/DocumentItem.svelte' +import MyDocuments from './components/MyDocuments.svelte' import NewDocumentHeader from './components/NewDocumentHeader.svelte' +import CategoryPresenter from './components/category/presenters/CategoryPresenter.svelte' import DocumentIcon from './components/icons/DocumentIcon.svelte' import DocumentTemplates from './components/DocumentTemplates.svelte' @@ -67,43 +67,53 @@ import DocumentTemplates from './components/DocumentTemplates.svelte' import Categories from './components/Categories.svelte' import EditDocumentCategory from './components/EditDocumentCategory.svelte' +import DocumentMetaPresenter from './components/DocumentMetaPresenter.svelte' +import CreateDocumentSpaceType from './components/docspace/CreateDocumentSpaceType.svelte' +import CreateDocumentsSpace from './components/docspace/CreateDocumentsSpace.svelte' import DocumentTitle from './components/document/DocumentTitle.svelte' import EditDocContent from './components/document/EditDocContent.svelte' +import ControlledStateFilterValuePresenter from './components/document/presenters/ControlledStateFilterValuePresenter.svelte' import DocumentVersionPresenter from './components/document/presenters/DocumentVersionPresenter.svelte' -import DocumentReviewRequest from './components/requests/DocumentReviewRequest.svelte' -import DocumentReviewRequestPresenter from './components/requests/DocumentReviewRequestPresenter.svelte' import DocumentApprovalRequest from './components/requests/DocumentApprovalRequest.svelte' import DocumentApprovalRequestPresenter from './components/requests/DocumentApprovalRequestPresenter.svelte' -import ControlledStateFilterValuePresenter from './components/document/presenters/ControlledStateFilterValuePresenter.svelte' -import DocumentMetaPresenter from './components/DocumentMetaPresenter.svelte' -import CreateDocumentsSpace from './components/docspace/CreateDocumentsSpace.svelte' -import CreateDocumentSpaceType from './components/docspace/CreateDocumentSpaceType.svelte' +import DocumentReviewRequest from './components/requests/DocumentReviewRequest.svelte' +import DocumentReviewRequestPresenter from './components/requests/DocumentReviewRequestPresenter.svelte' -import Projects from './components/project/Projects.svelte' import ProjectPresenter from './components/project/ProjectPresenter.svelte' import ProjectRefPresenter from './components/project/ProjectRefPresenter.svelte' +import Projects from './components/project/Projects.svelte' +import { getPrintBaseURL } from '@hcengineering/print' +import CreateFolder from './components/create-doc/CreateFolder.svelte' +import TransferDocumentPopup from './components/document/popups/TransferDocumentPopup.svelte' +import { resolveLocation } from './navigation' import documents from './plugin' import './styles/_colors.scss' -import { resolveLocation } from './navigation' +import { comment, isCommentVisible } from './text' import { - getVisibleFilters, - sortDocumentStates, - getAllDocumentStates, canChangeDocumentOwner, - canDeleteDocumentCategory, - canCreateChildTemplate, canCreateChildDocument, - documentIdentifierProvider, - getControlledDocumentTitle, - getDocumentMetaLinkFragment, + canCreateChildFolder, + canCreateChildTemplate, + canDeleteDocumentCategory, + canDeleteFolder, + canRenameFolder, createChildDocument, + createChildFolder, createChildTemplate, createDocument, - createTemplate + createFolder, + createTemplate, + deleteFolder, + documentIdentifierProvider, + getAllDocumentStates, + getControlledDocumentTitle, + getDocumentMetaLinkFragment, + getVisibleFilters, + isFolder, + renameFolder, + sortDocumentStates } from './utils' -import { comment, isCommentVisible } from './text' -import TransferDocumentPopup from './components/document/popups/TransferDocumentPopup.svelte' export { DocumentStatusTag, DocumentTitle, DocumentVersionPresenter, StatePresenter } @@ -210,6 +220,37 @@ async function canArchiveDocument (obj?: Doc | Doc[]): Promise { ).then((res) => res.every((r) => r)) } +async function canOpenDocument (obj?: ProjectDocument | ProjectDocument[]): Promise { + if (obj == null) { + return false + } + + const h = getClient().getHierarchy() + + const objs = Array.isArray(obj) ? obj : [obj] + return !objs.some((d) => isFolder(h, d)) +} + +async function canPrintDocument (obj?: Document | Document[] | ProjectDocument | ProjectDocument[]): Promise { + if (obj == null) { + return false + } + + const h = getClient().getHierarchy() + + const objs = Array.isArray(obj) ? obj : [obj] + if (objs.some((d) => isFolder(h, d))) return false + + let printURL = '' + try { + printURL = getPrintBaseURL() + } catch (err) { + // do nothing + } + + return printURL?.length > 0 +} + async function canTransferDocument (obj?: Doc | Doc[]): Promise { if (obj == null) { return false @@ -339,7 +380,8 @@ export default async (): Promise => ({ Projects, ProjectPresenter, ProjectRefPresenter, - DocumentIcon + DocumentIcon, + CreateFolder }, completion: { DocumentMetaQuery: async ( @@ -356,8 +398,12 @@ export default async (): Promise => ({ CanChangeDocumentOwner: canChangeDocumentOwner, CanCreateTemplate: canCreateChildTemplate, CanCreateDocument: canCreateChildDocument, + CanCreateFolder: canCreateChildFolder, CanCreateChildTemplate: canCreateChildTemplate, CanCreateChildDocument: canCreateChildDocument, + CanCreateChildFolder: canCreateChildFolder, + CanRenameFolder: canRenameFolder, + CanDeleteFolder: canDeleteFolder, CanDeleteDocumentCategory: canDeleteDocumentCategory, GetVisibleFilters: getVisibleFilters, DocumentStateSort: sortDocumentStates, @@ -366,6 +412,8 @@ export default async (): Promise => ({ CanDeleteDocument: canDeleteDocument, CanArchiveDocument: canArchiveDocument, CanTransferDocument: canTransferDocument, + CanOpenDocument: canOpenDocument, + CanPrintDocument: canPrintDocument, DocumentIdentifierProvider: documentIdentifierProvider, ControlledDocumentTitleProvider: getControlledDocumentTitle, Comment: comment, @@ -374,8 +422,12 @@ export default async (): Promise => ({ actionImpl: { CreateChildDocument: createChildDocument, CreateChildTemplate: createChildTemplate, + CreateChildFolder: createChildFolder, + RenameFolder: renameFolder, + DeleteFolder: deleteFolder, CreateDocument: createDocument, CreateTemplate: createTemplate, + CreateFolder: createFolder, DeleteDocument: deleteDocuments, ArchiveDocument: archiveDocuments, TransferDocument: transferDocuments, diff --git a/plugins/controlled-documents-resources/src/plugin.ts b/plugins/controlled-documents-resources/src/plugin.ts index 0f63086ecf..164838e64e 100644 --- a/plugins/controlled-documents-resources/src/plugin.ts +++ b/plugins/controlled-documents-resources/src/plugin.ts @@ -226,8 +226,12 @@ export default mergeIds(documentsId, documents, { function: { CanCreateTemplate: '' as Resource>, CanCreateDocument: '' as Resource>, + CanCreateFolder: '' as Resource>, CanCreateChildTemplate: '' as Resource>, CanCreateChildDocument: '' as Resource>, + CanCreateChildFolder: '' as Resource>, + CanRenameFolder: '' as Resource>, + CanDeleteFolder: '' as Resource>, CheckIsDocumentCreationDisabled: '' as Resource<() => Promise>, CheckAreTemplatesDisabled: '' as Resource<() => Promise>, CheckAreDomainsDisabled: '' as Resource<() => Promise>, @@ -238,6 +242,8 @@ export default mergeIds(documentsId, documents, { GetDocumentMetaLinkFragment: '' as Resource<(doc: Doc, props: Record) => Promise>, CanDeleteDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise>, CanArchiveDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise>, + CanOpenDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise>, + CanPrintDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise>, CanTransferDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise>, ControlledDocumentTitleProvider: '' as Resource<(client: Client, ref: Ref, doc?: Doc) => Promise> } diff --git a/plugins/controlled-documents-resources/src/utils.ts b/plugins/controlled-documents-resources/src/utils.ts index f87f4cc09c..926e258897 100644 --- a/plugins/controlled-documents-resources/src/utils.ts +++ b/plugins/controlled-documents-resources/src/utils.ts @@ -119,6 +119,11 @@ export function isProjectDocument (hierarchy: Hierarchy, doc: Doc): doc is Proje return hierarchy.isDerived(doc._class, documents.class.ProjectDocument) } +export function isFolder (hierarchy: Hierarchy, doc: Doc): doc is ProjectDocument { + if (!isProjectDocument(hierarchy, doc)) return false + return doc.document === documents.ids.Folder +} + export async function getVisibleFilters (filters: KeyFilter[], space?: Ref): Promise { // Removes the "Space" filter if a specific space is provided return space === undefined ? filters : filters.filter((f) => f.key !== 'space') @@ -461,6 +466,112 @@ export async function canCreateChildDocument ( return true } +export async function canCreateChildFolder ( + doc?: Document | Document[] | DocumentSpace | DocumentSpace[] | ProjectDocument | ProjectDocument[], + includeProjects = false +): Promise { + if (doc === null || doc === undefined) { + return false + } + if (Array.isArray(doc)) { + return false + } + + const client = getClient() + const hierarchy = client.getHierarchy() + const spaceId: Ref = isSpace(hierarchy, doc) ? doc._id : doc.space + + const canCreateDocument = await checkPermission(client, documents.permission.CreateDocument, spaceId) + if (!canCreateDocument) { + return false + } + + if (isSpace(hierarchy, doc)) { + const spaceType = await client.findOne(documents.class.DocumentSpaceType, { _id: doc.type }) + return includeProjects || spaceType?.projects !== true + } + + if (isProjectDocument(hierarchy, doc)) { + return await isEditableProject(doc.project) + } + + return true +} + +export async function canRenameFolder ( + doc?: Document | Document[] | DocumentSpace | DocumentSpace[] | ProjectDocument | ProjectDocument[], + includeProjects = false +): Promise { + if (doc === null || doc === undefined) { + return false + } + if (Array.isArray(doc)) { + return false + } + + const client = getClient() + const hierarchy = client.getHierarchy() + const spaceId: Ref = isSpace(hierarchy, doc) ? doc._id : doc.space + + const canCreateDocument = await checkPermission(client, documents.permission.CreateDocument, spaceId) + if (!canCreateDocument) { + return false + } + + if (isSpace(hierarchy, doc)) { + const spaceType = await client.findOne(documents.class.DocumentSpaceType, { _id: doc.type }) + return includeProjects || spaceType?.projects !== true + } + + if (!isFolder(hierarchy, doc)) { + return false + } + + return await isEditableProject(doc.project) +} + +export async function canDeleteFolder (obj?: Doc | Doc[]): Promise { + if (obj == null) { + return false + } + + const client = getClient() + const hierarchy = client.getHierarchy() + + const objs = (Array.isArray(obj) ? obj : [obj]) as Document[] + + const isFolders = objs.every((doc) => isFolder(hierarchy, doc)) + if (!isFolders) { + return false + } + + const folders = objs as unknown as ProjectDocument[] + + const pjMeta = await client.findAll(documents.class.ProjectMeta, { _id: { $in: folders.map((f) => f.attachedTo) } }) + const directChildren = await client.findAll(documents.class.ProjectMeta, { + parent: { $in: pjMeta.map((p) => p.meta) } + }) + + if (directChildren.length > 0) { + return false + } + + const currentUser = getCurrentAccount() as PersonAccount + const isOwner = objs.every((doc) => doc.owner === currentUser.person) + + if (isOwner) { + return true + } + + const spaces = new Set(objs.map((doc) => doc.space)) + + return await Promise.all( + Array.from(spaces).map( + async (space) => await checkPermission(getClient(), documents.permission.ArchiveDocument, space) + ) + ).then((res) => res.every((r) => r)) +} + export async function canDeleteDocumentCategory (doc?: Doc | Doc[]): Promise { if (doc === null || doc === undefined) { return false @@ -627,6 +738,54 @@ export async function createChildTemplate (doc: ProjectDocument): Promise showPopup(documents.component.QmsTemplateWizard, {}) } +export async function createChildFolder (doc: ProjectDocument): Promise { + const props = { + space: doc.space, + project: doc.project, + parent: doc._id + } + + showPopup(documents.component.CreateFolder, props) +} + +export async function renameFolder (doc: ProjectDocument): Promise { + const client = getClient() + + const pjmeta = await client.findOne(documents.class.ProjectMeta, { _id: doc.attachedTo }) + if (pjmeta === undefined) return + + const meta = await client.findOne(documents.class.DocumentMeta, { _id: pjmeta.meta }) + if (meta === undefined) return + + const props = { + folder: meta, + name: meta.title + } + + showPopup(documents.component.CreateFolder, props) +} + +export async function deleteFolder (obj: ProjectDocument | ProjectDocument[]): Promise { + const client = getClient() + + if (!(await canDeleteFolder(obj))) { + return + } + + const objs = Array.isArray(obj) ? obj : [obj] + + const pjmeta = await client.findAll(documents.class.ProjectMeta, { _id: { $in: objs.map((p) => p.attachedTo) } }) + const meta = await client.findAll(documents.class.DocumentMeta, { _id: { $in: pjmeta.map((p) => p.meta) } }) + + const docsToRemove = [...objs, ...pjmeta, ...meta] + const ops = client.apply() + for (const doc of docsToRemove) { + await ops.remove(doc) + } + + await ops.commit() +} + export async function createDocument (space: DocumentSpace): Promise { const project = await getLatestProjectId(space._id) wizardOpened({ @@ -642,6 +801,15 @@ export async function createTemplate (space: OrgSpace): Promise { showPopup(documents.component.QmsTemplateWizard, {}) } +export async function createFolder (space: DocumentSpace): Promise { + const project = await getLatestProjectId(space._id) + const props = { + space: space._id, + project: project ?? documents.ids.NoProject + } + showPopup(documents.component.CreateFolder, props) +} + export function formatSignatureDate (date: number): string { const timeZone: string = getUserTimezone() diff --git a/plugins/controlled-documents/src/docutils.ts b/plugins/controlled-documents/src/docutils.ts index d66948dd36..2aee60d6f6 100644 --- a/plugins/controlled-documents/src/docutils.ts +++ b/plugins/controlled-documents/src/docutils.ts @@ -357,3 +357,55 @@ export async function createDocumentTemplateMetadata ( return { success: success.result, seqNumber, code, documentMetaId, projectDocumentId } } + +export async function createNewFolder ( + client: TxOperations, + space: Ref, + project: Ref | undefined, + parent: Ref | undefined, + title: string +): Promise<{ + success: boolean + documentMetaId: Ref + projectDocumentId: Ref + }> { + const projectId = project ?? documents.ids.NoProject + + const ops = client.apply() + + const documentMetaId = await ops.createDoc(documents.class.DocumentMeta, space, { documents: 0, title }) + + let path: Array> = [] + if (parent !== undefined) { + 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: parentMeta, + documents: 0, + rank: makeRank(lastRank, undefined) + }) + + const projectDocumentId = await client.addCollection( + documents.class.ProjectDocument, + space, + projectMetaId, + documents.class.ProjectMeta, + 'documents', + { + project: projectId, + initial: projectId, + document: documents.ids.Folder + } + ) + + const success = await ops.commit() + + return { success: success.result, documentMetaId, projectDocumentId } +} diff --git a/plugins/controlled-documents/src/plugin.ts b/plugins/controlled-documents/src/plugin.ts index f3a5af320e..552db27090 100644 --- a/plugins/controlled-documents/src/plugin.ts +++ b/plugins/controlled-documents/src/plugin.ts @@ -107,20 +107,28 @@ export const documentsPlugin = plugin(documentsId, { DocumentMetaPresenter: '' as AnyComponent, DocumentVersionPresenter: '' as AnyComponent, DeleteCategoryPopup: '' as AnyComponent, - DocumentIcon: '' as AnyComponent + DocumentIcon: '' as AnyComponent, + CreateFolder: '' as AnyComponent }, action: { ChangeDocumentOwner: '' as Ref>, CreateChildDocument: '' as Ref>, CreateChildTemplate: '' as Ref>, + CreateChildFolder: '' as Ref>, + RenameFolder: '' as Ref>, + DeleteFolder: '' as Ref>, CreateDocument: '' as Ref>, CreateTemplate: '' as Ref>, + CreateFolder: '' as Ref>, DeleteDocumentCategory: '' as Ref>, DeleteDocument: '' as Ref, ArchiveDocument: '' as Ref, EditDocSpace: '' as Ref, TransferDocument: '' as Ref, - Print: '' as Ref> + Print: '' as Ref>, + PrintProjectDocument: '' as Ref>, + OpenDocument: '' as Ref>, + OpenDocumentInNewTab: '' as Ref> }, function: { CanChangeDocumentOwner: '' as Resource<(doc?: Doc | Doc[]) => Promise>, @@ -131,6 +139,7 @@ export const documentsPlugin = plugin(documentsId, { CheckmarkCircle: '' as Asset, DocumentApplication: '' as Asset, NewDocument: '' as Asset, + Folder: '' as Asset, Document: '' as Asset, Library: '' as Asset, StateDraft: '' as Asset, @@ -212,6 +221,8 @@ export const documentsPlugin = plugin(documentsId, { ChangeOwnerWarning: '' as IntlString, CreateDocument: '' as IntlString, CreateTemplate: '' as IntlString, + CreateFolder: '' as IntlString, + RenameFolder: '' as IntlString, DeleteCategory: '' as IntlString, DeleteCategoryHint: '' as IntlString, DeleteCategoryWarning: '' as IntlString, @@ -228,6 +239,7 @@ export const documentsPlugin = plugin(documentsId, { Path: '' as IntlString, CreateChildDocument: '' as IntlString, CreateChildTemplate: '' as IntlString, + CreateChildFolder: '' as IntlString, All: '' as IntlString, ImpactAnalysis: '' as IntlString, ImpactedDocuments: '' as IntlString, @@ -270,7 +282,8 @@ export const documentsPlugin = plugin(documentsId, { }, ids: { NoParent: '' as Ref, - NoProject: '' as Ref + NoProject: '' as Ref, + Folder: '' as Ref }, sequence: { Templates: '' as Ref, diff --git a/plugins/controlled-documents/src/utils.ts b/plugins/controlled-documents/src/utils.ts index 7acbb4f229..a1475c579a 100644 --- a/plugins/controlled-documents/src/utils.ts +++ b/plugins/controlled-documents/src/utils.ts @@ -443,9 +443,9 @@ async function _transferDocuments ( if (bundle.DocumentMeta.length !== 1) return false if (bundle.ProjectMeta.length !== 1) return false if (bundle.DocumentMeta[0].space !== cx.request.sourceSpaceId) return false - if (bundle.ControlledDocument.length < 1) return false - const isTemplate = hierarchy.hasMixin(bundle.ControlledDocument[0], documents.mixin.DocumentTemplate) + const anydoc = bundle.ControlledDocument[0] + const isTemplate = anydoc !== undefined && hierarchy.hasMixin(anydoc, documents.mixin.DocumentTemplate) if (isTemplate && hierarchy.isDerived(cx.targetSpace._class, documents.class.ExternalSpace)) return false }