mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Cherry Pick of recent changes in qms (#8498)
Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import type { Asset, IntlString, Plugin, StatusCode } from '@hcengineering/platform'
|
||||
import type { Asset, IntlString, Metadata, Plugin, StatusCode } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { BenchmarkDoc } from './benchmark'
|
||||
import { AccountRole } from './classes'
|
||||
@@ -288,5 +288,8 @@ export default plugin(coreId, {
|
||||
},
|
||||
role: {
|
||||
Admin: '' as Ref<Role>
|
||||
},
|
||||
metadata: {
|
||||
DisablePermissions: '' as Metadata<boolean>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
import { getEmbeddedLabel, getMetadata, IntlString } from '@hcengineering/platform'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { DOMAIN_BENCHMARK } from './benchmark'
|
||||
import {
|
||||
@@ -555,6 +555,9 @@ export async function checkPermission (
|
||||
_space: Ref<TypedSpace>,
|
||||
space?: TypedSpace
|
||||
): Promise<boolean> {
|
||||
const arePermissionsDisabled = getMetadata(core.metadata.DisablePermissions) ?? false
|
||||
if (arePermissionsDisabled) return true
|
||||
|
||||
space = space ?? (await client.findOne(core.class.TypedSpace, { _id: _space }))
|
||||
const type = await client
|
||||
.getModel()
|
||||
|
||||
@@ -13,7 +13,15 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { SortingOrder, type PluginConfiguration, type Tx, type TxUpdateDoc } from '@hcengineering/core'
|
||||
import core, {
|
||||
type Configuration,
|
||||
type PluginConfiguration,
|
||||
type Ref,
|
||||
SortingOrder,
|
||||
toIdMap,
|
||||
type Tx,
|
||||
type TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { getResourcePlugin, type Plugin, type Resource } from '@hcengineering/platform'
|
||||
import { writable } from 'svelte/store'
|
||||
import { addTxListener, createQuery } from '.'
|
||||
@@ -41,10 +49,10 @@ export class ConfigurationManager {
|
||||
}
|
||||
}
|
||||
// Issue status live query
|
||||
export let configuration = new ConfigurationManager([], new Map())
|
||||
export const configurationStore = writable<ConfigurationManager>(configuration)
|
||||
export let pluginConfiguration = new ConfigurationManager([], new Map())
|
||||
export const pluginConfigurationStore = writable<ConfigurationManager>(pluginConfiguration)
|
||||
|
||||
const configQuery = createQuery(true)
|
||||
const pluginConfigQuery = createQuery(true)
|
||||
|
||||
addTxListener((txes: Tx[]) => {
|
||||
for (const tx of txes) {
|
||||
@@ -63,15 +71,22 @@ addTxListener((txes: Tx[]) => {
|
||||
* @public
|
||||
*/
|
||||
export function hasResource<T> (resource?: Resource<T>): boolean | undefined {
|
||||
return configuration.hasResource(resource)
|
||||
return pluginConfiguration.hasResource(resource)
|
||||
}
|
||||
|
||||
configQuery.query(
|
||||
pluginConfigQuery.query(
|
||||
core.class.PluginConfiguration,
|
||||
{},
|
||||
(res) => {
|
||||
configuration = new ConfigurationManager(res, new Map(res.map((it) => [it.pluginId, it])))
|
||||
configurationStore.set(configuration)
|
||||
pluginConfiguration = new ConfigurationManager(res, new Map(res.map((it) => [it.pluginId, it])))
|
||||
pluginConfigurationStore.set(pluginConfiguration)
|
||||
},
|
||||
{ sort: { pluginId: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
const configQuery = createQuery(true)
|
||||
export const configurationStore = writable<Map<Ref<Configuration>, Configuration>>(new Map())
|
||||
|
||||
configQuery.query(core.class.Configuration, {}, (res) => {
|
||||
configurationStore.set(toIdMap(res))
|
||||
})
|
||||
|
||||
@@ -252,6 +252,7 @@
|
||||
"Name": "Název",
|
||||
"DraftRevision": "Revize konceptu",
|
||||
"CreateNewDraft": "Vytvořit nový koncept",
|
||||
"RestoreDraft": "Obnovit koncept",
|
||||
"ChangeSeverity": "Změnit závažnost",
|
||||
"Reference": "Reference",
|
||||
"History": "Historie",
|
||||
@@ -302,7 +303,10 @@
|
||||
"MakeDocumentObsoleteDialog": "Označit {count, plural, one {dokument jako zastaralý} other {dokumenty jako zastaralé}}",
|
||||
"MakeDocumentObsoleteConfirm": "Opravdu chcete označit následující dokumenty jako zastaralé: {titles}?",
|
||||
|
||||
"LatestVersionHint": "nejnovější"
|
||||
"LatestVersionHint": "nejnovější",
|
||||
|
||||
"CannotDeleteFolder": "Složku nelze smazat",
|
||||
"CannotDeleteFolderHint": "Před odstraněním složky prosím přesuňte všechny podřízené dokumenty na jiné místo."
|
||||
},
|
||||
"controlledDocStates": {
|
||||
"Empty": "",
|
||||
|
||||
@@ -254,6 +254,7 @@
|
||||
"Name": "Name",
|
||||
"DraftRevision": "Entwurfsrevision",
|
||||
"CreateNewDraft": "Neue Version entwerfen",
|
||||
"RestoreDraft": "Entwurf wiederherstellen",
|
||||
"ChangeSeverity": "Änderungsschwere",
|
||||
"Reference": "Referenz",
|
||||
"History": "Historie",
|
||||
@@ -309,7 +310,10 @@
|
||||
"MakeDocumentObsoleteDialog": "{count, plural, one {Dokument als veraltet markieren} other {Dokumente als veraltet markieren}}",
|
||||
"MakeDocumentObsoleteConfirm": "Möchten Sie die folgenden Dokumente wirklich als veraltet markieren: {titles}?",
|
||||
|
||||
"LatestVersionHint": "neueste"
|
||||
"LatestVersionHint": "neueste",
|
||||
|
||||
"CannotDeleteFolder": "Der Ordner kann nicht gelöscht werden",
|
||||
"CannotDeleteFolderHint": "Bitte verschieben Sie alle untergeordneten Dokumente an einen anderen Ort, bevor Sie den Ordner löschen."
|
||||
},
|
||||
"controlledDocStates": {
|
||||
"Empty": "",
|
||||
|
||||
@@ -261,6 +261,7 @@
|
||||
"Name": "Name",
|
||||
"DraftRevision": "Draft revision",
|
||||
"CreateNewDraft": "Draft new version",
|
||||
"RestoreDraft": "Restore draft",
|
||||
"ChangeSeverity": "Change severity",
|
||||
"Reference": "Reference",
|
||||
"History": "History",
|
||||
@@ -311,7 +312,10 @@
|
||||
"RenameFolder": "Rename folder",
|
||||
"CreateChildFolder": "Create child folder",
|
||||
|
||||
"LatestVersionHint": "latest"
|
||||
"LatestVersionHint": "latest",
|
||||
|
||||
"CannotDeleteFolder": "The folder cannot be deleted",
|
||||
"CannotDeleteFolderHint": "Please move all child documents to another location before deleting the folder."
|
||||
},
|
||||
"controlledDocStates": {
|
||||
"Empty": "",
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"Name": "Nom",
|
||||
"DraftRevision": "Révision du brouillon",
|
||||
"CreateNewDraft": "Créer une nouvelle version brouillon",
|
||||
"RestoreDraft": "Restaurer le brouillon",
|
||||
"ChangeSeverity": "Changer la gravité",
|
||||
"Reference": "Référence",
|
||||
"History": "Historique",
|
||||
@@ -269,7 +270,10 @@
|
||||
"MakeDocumentObsoleteDialog": "Marquer {count, plural, one {le document comme obsolète} other {les documents comme obsolètes}}",
|
||||
"MakeDocumentObsoleteConfirm": "Voulez-vous vraiment marquer les documents suivants comme obsolètes : {titles} ?",
|
||||
|
||||
"LatestVersionHint": "dernier"
|
||||
"LatestVersionHint": "dernier",
|
||||
|
||||
"CannotDeleteFolder": "Le dossier ne peut pas être supprimé",
|
||||
"CannotDeleteFolderHint": "Veuillez déplacer tous les documents enfants vers un autre emplacement avant de supprimer le dossier."
|
||||
},
|
||||
"controlledDocStates": {
|
||||
"Empty": "",
|
||||
|
||||
@@ -217,6 +217,7 @@
|
||||
"Name": "Nome",
|
||||
"DraftRevision": "Revisione della bozza",
|
||||
"CreateNewDraft": "Crea nuova versione",
|
||||
"RestoreDraft": "Ripristina bozza",
|
||||
"ChangeSeverity": "Cambia gravità",
|
||||
"Reference": "Riferimento",
|
||||
"History": "Storia",
|
||||
@@ -267,7 +268,10 @@
|
||||
"MakeDocumentObsoleteDialog": "Segna {count, plural, one {il documento come obsoleto} other {i documenti come obsoleti}}",
|
||||
"MakeDocumentObsoleteConfirm": "Vuoi davvero segnare i seguenti documenti come obsoleti: {titles}?",
|
||||
|
||||
"LatestVersionHint": "ultimo"
|
||||
"LatestVersionHint": "ultimo",
|
||||
|
||||
"CannotDeleteFolder": "Impossibile eliminare la cartella",
|
||||
"CannotDeleteFolderHint": "Sposta tutti i documenti figli in un'altra posizione prima di eliminare la cartella."
|
||||
},
|
||||
"controlledDocStates": {
|
||||
"Empty": "",
|
||||
|
||||
@@ -256,6 +256,7 @@
|
||||
"Name": "Название",
|
||||
"DraftRevision": "Ревизия рабочей копии",
|
||||
"CreateNewDraft": "Создать новую рабочую копию",
|
||||
"RestoreDraft": "Восстановить черновик",
|
||||
"ChangeSeverity": "Серьезность изменений",
|
||||
"Reference": "Референс",
|
||||
"History": "История",
|
||||
@@ -311,7 +312,10 @@
|
||||
"MakeDocumentObsoleteDialog": "Пометить {count, plural, one {документ как устаревший} other {документы как устаревшие}}",
|
||||
"MakeDocumentObsoleteConfirm": "Вы действительно хотите пометить следующие документы как устаревшие: {titles}?",
|
||||
|
||||
"LatestVersionHint": "последняя"
|
||||
"LatestVersionHint": "последняя",
|
||||
|
||||
"CannotDeleteFolder": "Папка не может быть удалена",
|
||||
"CannotDeleteFolderHint": "Пожалуйста, переместите все дочерние документы в другое место перед удалением папки."
|
||||
},
|
||||
"controlledDocStates": {
|
||||
"Empty": "",
|
||||
|
||||
@@ -253,6 +253,7 @@
|
||||
"Name": "名称",
|
||||
"DraftRevision": "草稿修订",
|
||||
"CreateNewDraft": "创建新版本草稿",
|
||||
"RestoreDraft": "恢复草稿",
|
||||
"ChangeSeverity": "更改严重性",
|
||||
"Reference": "参考",
|
||||
"History": "历史",
|
||||
@@ -308,7 +309,10 @@
|
||||
"MakeDocumentObsoleteDialog": "标记 {count, plural, one {文档为过时} other {文档为过时}}",
|
||||
"MakeDocumentObsoleteConfirm": "您确定要将以下文档标记为过时吗:{titles}?",
|
||||
|
||||
"LatestVersionHint": "最新"
|
||||
"LatestVersionHint": "最新",
|
||||
|
||||
"CannotDeleteFolder": "无法删除文件夹",
|
||||
"CannotDeleteFolderHint": "请在删除文件夹之前将所有子文档移动到其他位置。"
|
||||
},
|
||||
"controlledDocStates": {
|
||||
"Empty": "",
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import documents, {
|
||||
ControlledDocument,
|
||||
ControlledDocumentState,
|
||||
DocumentRequest,
|
||||
DocumentState,
|
||||
Project
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
@@ -23,25 +30,19 @@
|
||||
Button,
|
||||
Chevron,
|
||||
DropdownLabelsIntl,
|
||||
IconMoreV,
|
||||
Tab,
|
||||
Tabs,
|
||||
eventToHTMLElement,
|
||||
IconMoreV,
|
||||
navigate,
|
||||
resolvedLocationStore,
|
||||
showPopup
|
||||
showPopup,
|
||||
Tab,
|
||||
Tabs
|
||||
} from '@hcengineering/ui'
|
||||
import { showMenu } from '@hcengineering/view-resources'
|
||||
import documents, {
|
||||
ControlledDocument,
|
||||
ControlledDocumentState,
|
||||
DocumentRequest,
|
||||
DocumentState,
|
||||
Project
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
import { createDocumentSnapshotAndEdit, createNewDraftForControlledDoc, getDocReference } from '../docutils'
|
||||
import { getProjectDocumentLink } from '../navigation'
|
||||
import documentRes from '../plugin'
|
||||
import {
|
||||
$activeRightPanelTab as activeRightPanelTab,
|
||||
@@ -49,20 +50,21 @@
|
||||
$availableRightPanelTabs as availableRightPanelTabs,
|
||||
$canCreateNewDraft as canCreateNewDraft,
|
||||
$canCreateNewSnapshot as canCreateNewSnapshot,
|
||||
$canRestoreDraft as canRestoreDraft,
|
||||
$canSendForApproval as canSendForApproval,
|
||||
$canSendForReview as canSendForReview,
|
||||
$controlledDocument as controlledDocument,
|
||||
controlledDocumentClosed,
|
||||
controlledDocumentOpened,
|
||||
$controlledDocumentTemplate as controlledDocumentTemplate,
|
||||
$documentLatestVersion as documentLatestVersion,
|
||||
$documentApprovalIsActive as documentApprovalIsActive,
|
||||
$documentLatestVersion as documentLatestVersion,
|
||||
$documentReviewIsActive as documentReviewIsActive,
|
||||
$documentState as documentState,
|
||||
$editorMode as editorMode,
|
||||
editorModeUpdated,
|
||||
$isDocumentOwner as isDocumentOwner,
|
||||
$isProjectEditable as isProjectEditable,
|
||||
controlledDocumentClosed,
|
||||
controlledDocumentOpened,
|
||||
editorModeUpdated,
|
||||
rightPanelTabChanged
|
||||
} from '../stores/editors/document'
|
||||
import { completeRequest, getDocumentVersionString, getLatestProjectId, rejectRequest, TeamPopupData } from '../utils'
|
||||
@@ -77,12 +79,11 @@
|
||||
import DocumentRightPanel from './document/right-panel/DocumentRightPanel.svelte'
|
||||
import { scrollIntoSection } from './document/store'
|
||||
import DocumentVersionsPopup from './DocumentVersionsPopup.svelte'
|
||||
import DocumentTemplateFooter from './print/DocumentTemplateFooter.svelte'
|
||||
import DocumentTemplateHeader from './print/DocumentTemplateHeader.svelte'
|
||||
import ProjectSelector from './project/ProjectSelector.svelte'
|
||||
import SignatureDialog from './SignatureDialog.svelte'
|
||||
import TeamPopup from './TeamPopup.svelte'
|
||||
import ProjectSelector from './project/ProjectSelector.svelte'
|
||||
import DocumentTemplateHeader from './print/DocumentTemplateHeader.svelte'
|
||||
import DocumentTemplateFooter from './print/DocumentTemplateFooter.svelte'
|
||||
import { getProjectDocumentLink } from '../navigation'
|
||||
|
||||
export let _id: Ref<ControlledDocument>
|
||||
export let _class: Ref<Class<ControlledDocument>>
|
||||
@@ -220,39 +221,43 @@
|
||||
}
|
||||
|
||||
async function onCreateNewDraft (): Promise<void> {
|
||||
if (creating) {
|
||||
return
|
||||
}
|
||||
if (creating) return
|
||||
|
||||
creating = true
|
||||
try {
|
||||
if ($controlledDocument != null && $canCreateNewDraft && $documentLatestVersion != null) {
|
||||
const latest = $documentLatestVersion
|
||||
const version = { major: latest.major, minor: latest.minor + 1 }
|
||||
const project = await getLatestProjectId($controlledDocument.space)
|
||||
|
||||
if (project !== undefined) {
|
||||
try {
|
||||
const { id, success } = await createNewDraftForControlledDoc(
|
||||
client,
|
||||
$controlledDocument,
|
||||
$controlledDocument.space,
|
||||
version,
|
||||
project
|
||||
)
|
||||
if (success) {
|
||||
const loc = getProjectDocumentLink(id, project)
|
||||
navigate(loc)
|
||||
}
|
||||
} catch (err) {
|
||||
await setPlatformStatus(unknownError(err))
|
||||
}
|
||||
} else {
|
||||
console.warn('No document project found for space', $controlledDocument.space)
|
||||
}
|
||||
} else {
|
||||
if (!$controlledDocument || !$canCreateNewDraft || !$documentLatestVersion) {
|
||||
console.warn('Unexpected document state', $documentState)
|
||||
return
|
||||
}
|
||||
|
||||
const latest = $documentLatestVersion
|
||||
const version = { major: latest.major, minor: latest.minor + 1 }
|
||||
const project = await getLatestProjectId($controlledDocument.space)
|
||||
|
||||
if (project === undefined) {
|
||||
console.warn('No document project found for space', $controlledDocument.space)
|
||||
return
|
||||
}
|
||||
|
||||
if ($canRestoreDraft) {
|
||||
await client.update(latest, { state: DocumentState.Draft })
|
||||
const loc = getProjectDocumentLink(latest, project)
|
||||
navigate(loc)
|
||||
} else {
|
||||
const { id, success } = await createNewDraftForControlledDoc(
|
||||
client,
|
||||
$controlledDocument,
|
||||
$controlledDocument.space,
|
||||
version,
|
||||
project
|
||||
)
|
||||
if (success) {
|
||||
const loc = getProjectDocumentLink(id, project)
|
||||
navigate(loc)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
await setPlatformStatus(unknownError(err))
|
||||
} finally {
|
||||
creating = false
|
||||
}
|
||||
@@ -371,7 +376,7 @@
|
||||
|
||||
{#if $canCreateNewDraft}
|
||||
<Button
|
||||
label={documentRes.string.CreateNewDraft}
|
||||
label={$canRestoreDraft ? documentRes.string.RestoreDraft : documentRes.string.CreateNewDraft}
|
||||
kind="regular"
|
||||
loading={creating}
|
||||
disabled={creating}
|
||||
|
||||
+6
@@ -137,11 +137,16 @@
|
||||
|
||||
$: space = $locationStep.space
|
||||
|
||||
let submitted = false
|
||||
|
||||
async function handleSubmit (): Promise<void> {
|
||||
if ($locationStep.space === undefined || $locationStep.project === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (submitted) return
|
||||
submitted = true
|
||||
|
||||
const newDocId = generateId<ControlledDocument>()
|
||||
const _space = $locationStep.space
|
||||
|
||||
@@ -198,6 +203,7 @@
|
||||
loading={isLoading}
|
||||
label={documents.string.NewDocument}
|
||||
submitLabel={documents.string.CreateDraft}
|
||||
canSubmit={!submitted}
|
||||
{canProceed}
|
||||
{steps}
|
||||
selectedStep={$currentStep}
|
||||
|
||||
+6
@@ -130,11 +130,16 @@
|
||||
currentStepUpdated(e.detail)
|
||||
}
|
||||
|
||||
let submitted = false
|
||||
|
||||
async function handleSubmit (): Promise<void> {
|
||||
if ($locationStep.space === undefined || $locationStep.project === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (submitted) return
|
||||
submitted = true
|
||||
|
||||
const { category } = docObject
|
||||
if (category === undefined || category === null) return
|
||||
|
||||
@@ -206,6 +211,7 @@
|
||||
submitLabel={documents.string.CreateDraft}
|
||||
{canProceed}
|
||||
{steps}
|
||||
canSubmit={!submitted}
|
||||
selectedStep={currentTemplateStep}
|
||||
on:stepChanged={handleStepChanged}
|
||||
on:submit={handleSubmit}
|
||||
|
||||
@@ -380,6 +380,10 @@
|
||||
padding-top: 20rem;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
@media print {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.watermark {
|
||||
|
||||
+6
-4
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import documents, { type Document, type DocumentTemplate } from '@hcengineering/controlled-documents'
|
||||
import documents, { DocumentState, type Document, type DocumentTemplate } from '@hcengineering/controlled-documents'
|
||||
import { PersonPresenter } from '@hcengineering/contact-resources'
|
||||
import { DateRangeMode } from '@hcengineering/core'
|
||||
import { DatePresenter, Label, Scroller, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
@@ -73,7 +73,9 @@
|
||||
)
|
||||
}
|
||||
|
||||
$: isEditableDraft = $isEditable && $controlledDocument != null && $documentAllVersions.length === 1
|
||||
$: isEditableDraft = $isEditable && $controlledDocument != null && $controlledDocument.state === DocumentState.Draft
|
||||
$: isInitialEditableDraft = isEditableDraft && $documentAllVersions.length === 1
|
||||
|
||||
$: isTemplate =
|
||||
$controlledDocument != null && hierarchy.hasMixin($controlledDocument, documents.mixin.DocumentTemplate)
|
||||
|
||||
@@ -94,7 +96,7 @@
|
||||
value={$controlledDocument}
|
||||
isRegular
|
||||
disableLink
|
||||
editable={isEditableDraft}
|
||||
editable={isInitialEditableDraft}
|
||||
on:edit={(e) => {
|
||||
handleCodeEdit(e.detail)
|
||||
}}
|
||||
@@ -119,7 +121,7 @@
|
||||
|
||||
{#if isTemplate}
|
||||
<DocumentInfo label={documentsRes.string.DocumentPrefix}>
|
||||
<DocumentPrefixPresenter value={asTemplate} editable={isEditableDraft} />
|
||||
<DocumentPrefixPresenter value={asTemplate} editable={isInitialEditableDraft} />
|
||||
</DocumentInfo>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -214,7 +214,10 @@ export default mergeIds(documentsId, documents, {
|
||||
CreateDocumentTemplateFailed: '' as IntlString,
|
||||
TryAgain: '' as IntlString,
|
||||
|
||||
LatestVersionHint: '' as IntlString
|
||||
LatestVersionHint: '' as IntlString,
|
||||
|
||||
CannotDeleteFolder: '' as IntlString,
|
||||
CannotDeleteFolderHint: '' as IntlString
|
||||
},
|
||||
controlledDocStates: {
|
||||
Empty: '' as IntlString,
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
|
||||
export * from './document/actions'
|
||||
export * from './document/canCreateNewDraft'
|
||||
export * from './document/canRestoreDraft'
|
||||
export * from './document/canCreateNewSnapshot'
|
||||
export * from './document/canSendForApproval'
|
||||
export * from './document/canSendForReview'
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright © 2025 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.
|
||||
//
|
||||
|
||||
import { DocumentState } from '@hcengineering/controlled-documents'
|
||||
import { combine } from 'effector'
|
||||
import { $canCreateNewDraft } from './canCreateNewDraft'
|
||||
import { $controlledDocument, $documentLatestVersion } from './editor'
|
||||
|
||||
export const $canRestoreDraft = combine(
|
||||
$controlledDocument,
|
||||
$documentLatestVersion,
|
||||
$canCreateNewDraft,
|
||||
(document, latest, canCreateNewDraft) => {
|
||||
if (latest === null || document == null) return false
|
||||
return canCreateNewDraft && latest._id === document._id && document.state === DocumentState.Deleted
|
||||
}
|
||||
)
|
||||
@@ -34,7 +34,8 @@ import documents, {
|
||||
compareDocumentVersions,
|
||||
emptyBundle,
|
||||
getDocumentName,
|
||||
getFirstRank
|
||||
getFirstRank,
|
||||
transferDocuments
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import core, {
|
||||
type Class,
|
||||
@@ -50,10 +51,11 @@ import core, {
|
||||
type TxOperations,
|
||||
type WithLookup,
|
||||
SortingOrder,
|
||||
checkPermission
|
||||
checkPermission,
|
||||
getCurrentAccount
|
||||
} from '@hcengineering/core'
|
||||
import { type IntlString, translate } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import request, { type Request, RequestStatus } from '@hcengineering/request'
|
||||
import { isEmptyMarkup } from '@hcengineering/text'
|
||||
import { type Location, getUserTimezone, showPopup } from '@hcengineering/ui'
|
||||
@@ -547,46 +549,22 @@ export async function canRenameFolder (
|
||||
return await isEditableProject(doc.project)
|
||||
}
|
||||
|
||||
export async function canDeleteFolder (obj?: Doc | Doc[]): Promise<boolean> {
|
||||
if (obj == null) {
|
||||
return false
|
||||
}
|
||||
export async function canDeleteFolder (doc: ProjectDocument): Promise<boolean> {
|
||||
if (doc?._class === undefined) 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) {
|
||||
if (!isFolder(hierarchy, doc)) {
|
||||
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 me = getCurrentEmployee()
|
||||
const isOwner = objs.every((doc) => doc.owner === me)
|
||||
|
||||
if (isOwner) {
|
||||
const currentUser = getCurrentAccount()
|
||||
if (currentUser.socialIds.some((id) => id === doc.createdBy)) {
|
||||
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))
|
||||
return await checkPermission(getClient(), documents.permission.ArchiveDocument, doc.space)
|
||||
}
|
||||
|
||||
export async function canDeleteDocumentCategory (doc?: Doc | Doc[]): Promise<boolean> {
|
||||
@@ -820,25 +798,70 @@ export async function renameFolder (doc: ProjectDocument): Promise<void> {
|
||||
showPopup(documents.component.CreateFolder, props)
|
||||
}
|
||||
|
||||
export async function deleteFolder (obj: ProjectDocument | ProjectDocument[]): Promise<void> {
|
||||
export async function deleteFolder (obj: ProjectDocument): Promise<void> {
|
||||
const success = await _deleteFolder(obj)
|
||||
if (!success) {
|
||||
showPopup(MessageBox, {
|
||||
label: documentsResources.string.CannotDeleteFolder,
|
||||
message: documentsResources.string.CannotDeleteFolderHint,
|
||||
canSubmit: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function _deleteFolder (obj: ProjectDocument): Promise<boolean> {
|
||||
const client = getClient()
|
||||
|
||||
if (!(await canDeleteFolder(obj))) {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
const objs = Array.isArray(obj) ? obj : [obj]
|
||||
const space = obj.space
|
||||
const project = obj.project
|
||||
|
||||
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 bundle: DocumentBundle = {
|
||||
...emptyBundle(),
|
||||
ProjectMeta: await client.findAll(documents.class.ProjectMeta, { space, project }),
|
||||
ProjectDocument: await client.findAll(documents.class.ProjectDocument, { space, project }),
|
||||
DocumentMeta: await client.findAll(documents.class.DocumentMeta, { space }),
|
||||
ControlledDocument: await client.findAll(documents.class.ControlledDocument, { space })
|
||||
}
|
||||
|
||||
const prjMeta = bundle.ProjectMeta.find((m) => m._id === obj.attachedTo)
|
||||
if (prjMeta === undefined) return false
|
||||
|
||||
const tree = new ProjectDocumentTree(bundle, { keepRemoved: true })
|
||||
|
||||
const movableStates = [DocumentState.Deleted, DocumentState.Obsolete]
|
||||
const descendants = tree.descendantsOf(prjMeta.meta)
|
||||
for (const meta of descendants) {
|
||||
const bundle = tree.bundleOf(meta)
|
||||
const docs = bundle?.ControlledDocument ?? []
|
||||
const movable = docs.every((d) => movableStates.includes(d.state))
|
||||
if (!movable) return false
|
||||
}
|
||||
|
||||
const children = tree.childrenOf(prjMeta.meta)
|
||||
if (children.length > 0) {
|
||||
await transferDocuments(client, {
|
||||
sourceDocumentIds: children,
|
||||
sourceSpaceId: obj.space,
|
||||
sourceProjectId: obj.project,
|
||||
|
||||
targetSpaceId: obj.space,
|
||||
targetProjectId: obj.project
|
||||
})
|
||||
}
|
||||
|
||||
const toRemoval = [obj, prjMeta]
|
||||
|
||||
const docsToRemove = [...objs, ...pjmeta, ...meta]
|
||||
const ops = client.apply()
|
||||
for (const doc of docsToRemove) {
|
||||
for (const doc of toRemoval) {
|
||||
await ops.remove(doc)
|
||||
}
|
||||
|
||||
await ops.commit()
|
||||
return true
|
||||
}
|
||||
|
||||
export async function createDocument (space: DocumentSpace): Promise<void> {
|
||||
|
||||
@@ -150,7 +150,7 @@ export async function createControlledDocMetadata (
|
||||
}> {
|
||||
const projectId = project ?? documents.ids.NoProject
|
||||
|
||||
const ops = client.apply()
|
||||
const ops = client.apply('create-qms-document')
|
||||
|
||||
ops.notMatch(documents.class.Document, {
|
||||
template: templateId,
|
||||
@@ -303,7 +303,7 @@ export async function createDocumentTemplateMetadata (
|
||||
path = await getParentPath(client, parent)
|
||||
}
|
||||
|
||||
const ops = client.apply()
|
||||
const ops = client.apply('create-qms-document')
|
||||
|
||||
ops.notMatch(documents.class.Document, {
|
||||
template: { $exists: false },
|
||||
|
||||
@@ -255,6 +255,7 @@ export const documentsPlugin = plugin(documentsId, {
|
||||
ControlledSnapshot: '' as IntlString,
|
||||
DraftRevision: '' as IntlString,
|
||||
CreateNewDraft: '' as IntlString,
|
||||
RestoreDraft: '' as IntlString,
|
||||
CreateOrgSpace: '' as IntlString,
|
||||
|
||||
ReviewDocumentPermission: '' as IntlString,
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
ProjectDocument,
|
||||
ProjectMeta
|
||||
} from './types'
|
||||
import { RequestStatus } from '@hcengineering/request'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -197,13 +198,17 @@ function extractPresentableStateFromDocumentBundle (bundle: DocumentBundle, prjm
|
||||
return bundle
|
||||
}
|
||||
|
||||
export interface ProjectDocumentTreeOptions {
|
||||
keepRemoved?: boolean
|
||||
}
|
||||
|
||||
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) {
|
||||
constructor (bundle?: DocumentBundle, options?: ProjectDocumentTreeOptions) {
|
||||
bundle = { ...emptyBundle(), ...bundle }
|
||||
const { bundles, links } = compileBundles(bundle)
|
||||
this.links = links
|
||||
@@ -211,6 +216,8 @@ export class ProjectDocumentTree {
|
||||
this.nodesChildren = new Map()
|
||||
this.parents = new Map()
|
||||
|
||||
const keepRemoved = options?.keepRemoved ?? false
|
||||
|
||||
bundles.sort((a, b) => {
|
||||
const rankA = a.ProjectMeta[0]?.rank ?? ''
|
||||
const rankB = b.ProjectMeta[0]?.rank ?? ''
|
||||
@@ -224,7 +231,7 @@ export class ProjectDocumentTree {
|
||||
const presentable = extractPresentableStateFromDocumentBundle(bundle, prjmeta)
|
||||
this.nodes.set(prjmeta.meta, presentable)
|
||||
|
||||
const parent = prjmeta.path[0] ?? documents.ids.NoParent
|
||||
const parent = prjmeta.parent ?? documents.ids.NoParent
|
||||
this.parents.set(prjmeta.meta, parent)
|
||||
|
||||
if (!this.nodesChildren.has(parent)) {
|
||||
@@ -234,10 +241,12 @@ export class ProjectDocumentTree {
|
||||
}
|
||||
|
||||
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 (!keepRemoved) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of this.nodes.keys()) {
|
||||
@@ -320,8 +329,12 @@ export async function findProjectDocsHierarchy (
|
||||
space: Ref<DocumentSpace>,
|
||||
project?: Ref<Project<DocumentSpace>>
|
||||
): Promise<ProjectDocumentTree> {
|
||||
const ProjectMeta = await client.findAll(documents.class.ProjectMeta, { space, project })
|
||||
return new ProjectDocumentTree({ ...emptyBundle(), ProjectMeta })
|
||||
const bundle: DocumentBundle = {
|
||||
...emptyBundle(),
|
||||
DocumentMeta: await client.findAll(documents.class.DocumentMeta, { space }),
|
||||
ProjectMeta: await client.findAll(documents.class.ProjectMeta, { space, project })
|
||||
}
|
||||
return new ProjectDocumentTree(bundle, { keepRemoved: true })
|
||||
}
|
||||
|
||||
export interface DocumentBundle {
|
||||
@@ -507,20 +520,30 @@ async function _buildDocumentTransferContext (
|
||||
client: TxOperations,
|
||||
request: DocumentTransferRequest
|
||||
): Promise<DocumentTransferContext | undefined> {
|
||||
const isSameSpace = request.sourceSpaceId === request.targetSpaceId
|
||||
|
||||
const sourceTree = await findProjectDocsHierarchy(client, request.sourceSpaceId, request.sourceProjectId)
|
||||
const targetTree = await findProjectDocsHierarchy(client, request.targetSpaceId, request.targetProjectId)
|
||||
const targetTree = isSameSpace
|
||||
? sourceTree
|
||||
: await findProjectDocsHierarchy(client, request.targetSpaceId, request.targetProjectId)
|
||||
|
||||
const docIds = new Set<Ref<DocumentMeta>>(request.sourceDocumentIds)
|
||||
for (const id of request.sourceDocumentIds) {
|
||||
sourceTree.descendantsOf(id).forEach((d) => docIds.add(d))
|
||||
}
|
||||
|
||||
if (request.targetParentId !== undefined && docIds.has(request.targetParentId)) {
|
||||
return
|
||||
}
|
||||
|
||||
const bundles = await findAllDocumentBundles(client, Array.from(docIds))
|
||||
const targetParentBundle =
|
||||
request.targetParentId !== undefined ? await findOneDocumentBundle(client, request.targetParentId) : undefined
|
||||
|
||||
const sourceSpace = await client.findOne(documents.class.DocumentSpace, { _id: request.sourceSpaceId })
|
||||
const targetSpace = await client.findOne(documents.class.DocumentSpace, { _id: request.targetSpaceId })
|
||||
const targetSpace = isSameSpace
|
||||
? sourceSpace
|
||||
: await client.findOne(documents.class.DocumentSpace, { _id: request.targetSpaceId })
|
||||
|
||||
if (sourceSpace === undefined || targetSpace === undefined) return
|
||||
|
||||
@@ -565,7 +588,6 @@ async function _transferDocuments (
|
||||
mode: 'default' | 'check' = 'default'
|
||||
): Promise<boolean> {
|
||||
if (cx.bundles.length < 1) return false
|
||||
if (cx.targetSpace._id === cx.sourceSpace._id) return false
|
||||
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
@@ -621,13 +643,17 @@ async function _transferDocuments (
|
||||
|
||||
let key: keyof DocumentBundle
|
||||
for (key in bundle) {
|
||||
bundle[key].forEach((doc) => {
|
||||
update(doc, { space: cx.targetSpace._id })
|
||||
})
|
||||
for (const doc of bundle[key]) {
|
||||
const space = cx.targetSpace._id
|
||||
if (doc.space !== space) update(doc, { space })
|
||||
}
|
||||
}
|
||||
for (const m of bundle.ProjectMeta) {
|
||||
if (m.project !== project) update(m, { project })
|
||||
}
|
||||
for (const m of bundle.ProjectDocument) {
|
||||
if (m.project !== project) update(m, { project })
|
||||
}
|
||||
|
||||
for (const m of bundle.ProjectMeta) update(m, { project })
|
||||
for (const m of bundle.ProjectDocument) update(m, { project })
|
||||
}
|
||||
|
||||
if (mode === 'check') return true
|
||||
@@ -728,6 +754,9 @@ export function extractValidationWorkflow (
|
||||
})
|
||||
|
||||
for (const request of requests) {
|
||||
if (request.status === RequestStatus.Cancelled) {
|
||||
continue
|
||||
}
|
||||
const state =
|
||||
states.find((s) => (s.snapshot?.createdOn ?? 0) > (request.createdOn ?? 0)) ?? states[states.length - 1]
|
||||
state.requests.push(request)
|
||||
|
||||
@@ -148,6 +148,10 @@
|
||||
"MailboxErrorMailboxExists": "Tento název poštovní schránky je již obsazen",
|
||||
"MailboxErrorMailboxCountLimit": "Dosažen limit počtu poštovních schránek pro váš účet",
|
||||
"DeleteMailbox": "Smazat poštovní schránku",
|
||||
"MailboxDeleteConfirmation": "Opravdu chcete smazat tuto poštovní schránku?"
|
||||
"MailboxDeleteConfirmation": "Opravdu chcete smazat tuto poštovní schránku?",
|
||||
"DisablePermissions": "Zakázat řízení přístupu na základě rolí",
|
||||
"EnablePermissions": "Povolit řízení přístupu na základě rolí",
|
||||
"DisablePermissionsConfirmation": "Opravdu chcete zakázat řízení přístupu na základě rolí? Všechny role a oprávnění budou deaktivovány.",
|
||||
"EnablePermissionsConfirmation": "Opravdu chcete povolit řízení přístupu na základě rolí? Všechny role a oprávnění budou aktivovány."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,10 @@
|
||||
"MailboxErrorMailboxExists": "Dieser Postfachname ist bereits belegt",
|
||||
"MailboxErrorMailboxCountLimit": "Postfachlimit für Ihr Konto erreicht",
|
||||
"DeleteMailbox": "Postfach löschen",
|
||||
"MailboxDeleteConfirmation": "Möchten Sie dieses Postfach wirklich löschen?"
|
||||
"MailboxDeleteConfirmation": "Möchten Sie dieses Postfach wirklich löschen?",
|
||||
"DisablePermissions": "Rollenbasierte Zugriffskontrolle deaktivieren",
|
||||
"EnablePermissions": "Rollenbasierte Zugriffskontrolle aktivieren",
|
||||
"DisablePermissionsConfirmation": "Sind Sie sicher, dass Sie die rollenbasierte Zugriffskontrolle deaktivieren möchten? Alle Rollen und Berechtigungen werden deaktiviert.",
|
||||
"EnablePermissionsConfirmation": "Sind Sie sicher, dass Sie die rollenbasierte Zugriffskontrolle aktivieren möchten? Alle Rollen und Berechtigungen werden aktiviert."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"Setting": "Setting",
|
||||
"Spaces": "Spaces",
|
||||
"Spaces": "Spaces",
|
||||
"Integrations": "Integrations",
|
||||
"Support": "Support",
|
||||
"Privacy": "Privacy",
|
||||
@@ -148,6 +148,10 @@
|
||||
"MailboxErrorMailboxExists": "This mailbox name is already occupied",
|
||||
"MailboxErrorMailboxCountLimit": "Mailbox count limit reached for your account",
|
||||
"DeleteMailbox": "Delete mailbox",
|
||||
"MailboxDeleteConfirmation": "Are you sure you want to delete this mailbox?"
|
||||
"MailboxDeleteConfirmation": "Are you sure you want to delete this mailbox?",
|
||||
"DisablePermissions": "Disable role-based access control",
|
||||
"EnablePermissions": "Enable role-based access control",
|
||||
"DisablePermissionsConfirmation": "Are you sure you want to disable role-based access control? All roles and permissions will be disabled.",
|
||||
"EnablePermissionsConfirmation": "Are you sure you want to enable role-based access control? All roles and permissions will be enabled."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,6 +139,10 @@
|
||||
"MailboxErrorMailboxExists": "Este buzón ya está ocupado",
|
||||
"MailboxErrorMailboxCountLimit": "Se ha alcanzado el límite de buzones de su cuenta",
|
||||
"DeleteMailbox": "Eliminar buzón",
|
||||
"MailboxDeleteConfirmation": "¿Está seguro de que desea eliminar este buzón?"
|
||||
"MailboxDeleteConfirmation": "¿Está seguro de que desea eliminar este buzón?",
|
||||
"DisablePermissions": "Desactivar el control de acceso basado en roles",
|
||||
"EnablePermissions": "Activar el control de acceso basado en roles",
|
||||
"DisablePermissionsConfirmation": "¿Está seguro de que desea desactivar el control de acceso basado en roles? Todos los roles y permisos serán desactivados.",
|
||||
"EnablePermissionsConfirmation": "¿Está seguro de que desea activar el control de acceso basado en roles? Todos los roles y permisos serán activados."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,10 @@
|
||||
"MailboxErrorMailboxExists": "Cette boîte aux lettres est déjà occupée",
|
||||
"MailboxErrorMailboxCountLimit": "Limite de boîtes aux lettres atteinte pour votre compte",
|
||||
"DeleteMailbox": "Supprimer la boîte aux lettres",
|
||||
"MailboxDeleteConfirmation": "Êtes-vous sûr de vouloir supprimer cette boîte aux lettres ?"
|
||||
"MailboxDeleteConfirmation": "Êtes-vous sûr de vouloir supprimer cette boîte aux lettres ?",
|
||||
"DisablePermissions": "Désactiver le contrôle d'accès basé sur les rôles",
|
||||
"EnablePermissions": "Activer le contrôle d'accès basé sur les rôles",
|
||||
"DisablePermissionsConfirmation": "Êtes-vous sûr de vouloir désactiver le contrôle d'accès basé sur les rôles ? Tous les rôles et permissions seront désactivés.",
|
||||
"EnablePermissionsConfirmation": "Êtes-vous sûr de vouloir activer le contrôle d'accès basé sur les rôles ? Tous les rôles et permissions seront activés."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,10 @@
|
||||
"MailboxErrorMailboxExists": "Questo nome di casella di posta è già occupato",
|
||||
"MailboxErrorMailboxCountLimit": "Limite di conteggio delle caselle di posta raggiunto per il tuo account",
|
||||
"DeleteMailbox": "Elimina casella di posta",
|
||||
"MailboxDeleteConfirmation": "Sei sicuro di voler eliminare questa casella di posta?"
|
||||
"MailboxDeleteConfirmation": "Sei sicuro di voler eliminare questa casella di posta?",
|
||||
"DisablePermissions": "Disabilita il controllo degli accessi basato sui ruoli",
|
||||
"EnablePermissions": "Abilita il controllo degli accessi basato sui ruoli",
|
||||
"DisablePermissionsConfirmation": "Sei sicuro di voler disabilitare il controllo degli accessi basato sui ruoli? Tutti i ruoli e le autorizzazioni verranno disabilitati.",
|
||||
"EnablePermissionsConfirmation": "Sei sicuro di voler abilitare il controllo degli accessi basato sui ruoli? Tutti i ruoli e le autorizzazioni verranno abilitati."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,10 @@
|
||||
"MailboxErrorMailboxExists": "Este nome de caixa de correio já está ocupado",
|
||||
"MailboxErrorMailboxCountLimit": "O limite de contagem de caixas de correio foi atingido para a sua conta",
|
||||
"DeleteMailbox": "Apagar caixa de correio",
|
||||
"MailboxDeleteConfirmation": "Tem a certeza de que pretende eliminar esta caixa de correio?"
|
||||
"MailboxDeleteConfirmation": "Tem a certeza de que pretende eliminar esta caixa de correio?",
|
||||
"DisablePermissions": "Desativar controle de acesso baseado em funções",
|
||||
"EnablePermissions": "Ativar controle de acesso baseado em funções",
|
||||
"DisablePermissionsConfirmation": "Tem certeza de que deseja desativar o controle de acesso baseado em funções? Todos os papéis e permissões serão desativados.",
|
||||
"EnablePermissionsConfirmation": "Tem certeza de que deseja ativar o controle de acesso baseado em funções? Todos os papéis e permissões serão ativados."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,6 @@
|
||||
"CreateMixin": "Создать Миксин",
|
||||
"OldNames": "Предыдущие значения",
|
||||
"NewClassName": "Введите новое имя класса или выберете прошлое значение...",
|
||||
|
||||
"ShowAttribute": "Показать",
|
||||
"HideAttribute": "Спрятать",
|
||||
"Visibility": "Видимость",
|
||||
@@ -149,6 +148,10 @@
|
||||
"MailboxErrorMailboxExists": "Это имя почтового ящика уже занято",
|
||||
"MailboxErrorMailboxCountLimit": "Достигнут лимит количества почтовых ящиков для вашей учетной записи",
|
||||
"DeleteMailbox": "Удалить почтовый ящик",
|
||||
"MailboxDeleteConfirmation": "Вы уверены, что хотите удалить этот почтовый ящик?"
|
||||
"MailboxDeleteConfirmation": "Вы уверены, что хотите удалить этот почтовый ящик?",
|
||||
"DisablePermissions": "Отключить ролевое управление доступом",
|
||||
"EnablePermissions": "Включить ролевое управление доступом",
|
||||
"DisablePermissionsConfirmation": "Вы уверены, что хотите отключить ролевое управление доступом? Все роли и разрешения будут отключены.",
|
||||
"EnablePermissionsConfirmation": "Вы уверены, что хотите включить ролевое управление доступом? Все роли и разрешения будут включены."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,10 @@
|
||||
"MailboxErrorMailboxExists": "此邮箱名称已被占用",
|
||||
"MailboxErrorMailboxCountLimit": "您的帐户已达到邮箱数量限制",
|
||||
"DeleteMailbox": "删除邮箱",
|
||||
"MailboxDeleteConfirmation": "您确定要删除此邮箱吗?"
|
||||
"MailboxDeleteConfirmation": "您确定要删除此邮箱吗?",
|
||||
"DisablePermissions": "禁用基于角色的访问控制",
|
||||
"EnablePermissions": "启用基于角色的访问控制",
|
||||
"DisablePermissionsConfirmation": "您确定要禁用基于角色的访问控制吗?所有角色和权限都将被禁用。",
|
||||
"EnablePermissionsConfirmation": "您确定要启用基于角色的访问控制吗?所有角色和权限都将被启用。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { PluginConfiguration } from '@hcengineering/core'
|
||||
import { configurationStore, getClient } from '@hcengineering/presentation'
|
||||
import { pluginConfigurationStore, getClient } from '@hcengineering/presentation'
|
||||
import { Breadcrumb, Button, Header, Icon, IconInfo, Label, Scroller } from '@hcengineering/ui'
|
||||
import setting from '../plugin'
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="hulyComponent-content__column content">
|
||||
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
|
||||
<div class="flex-row-center flex-wrap gap-around-4">
|
||||
{#each $configurationStore.list as config}
|
||||
{#each $pluginConfigurationStore.list as config}
|
||||
{#if config.label}
|
||||
<div class="cardBox flex-col clear-mins" class:enabled={config.enabled ?? true}>
|
||||
<div class="flex-row-center">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core from '@hcengineering/core'
|
||||
import core, { Configuration } from '@hcengineering/core'
|
||||
import {
|
||||
Breadcrumb,
|
||||
Header,
|
||||
@@ -37,14 +37,12 @@
|
||||
type DropdownTextItem
|
||||
} from '@hcengineering/ui'
|
||||
import { loginId } from '@hcengineering/login'
|
||||
import { EditableAvatar } from '@hcengineering/contact-resources'
|
||||
import { EditableAvatar, getAccountClient } from '@hcengineering/contact-resources'
|
||||
import { translateCB } from '@hcengineering/platform'
|
||||
import { getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { WorkspaceSetting } from '@hcengineering/setting'
|
||||
import { AvatarType } from '@hcengineering/contact'
|
||||
|
||||
import setting from '../plugin'
|
||||
import { getAccountClient } from '../utils'
|
||||
import settingsRes from '../plugin'
|
||||
|
||||
let loading = true
|
||||
let isEditingName = false
|
||||
@@ -90,8 +88,8 @@
|
||||
|
||||
async function handleDelete (): Promise<void> {
|
||||
showPopup(MessageBox, {
|
||||
label: setting.string.DeleteWorkspace,
|
||||
message: setting.string.DeleteWorkspaceConfirm,
|
||||
label: settingsRes.string.DeleteWorkspace,
|
||||
message: settingsRes.string.DeleteWorkspaceConfirm,
|
||||
dangerous: true,
|
||||
action: async () => {
|
||||
await accountClient.deleteWorkspace()
|
||||
@@ -105,7 +103,7 @@
|
||||
let workspaceSettings: WorkspaceSetting | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
void client.findOne(setting.class.WorkspaceSetting, {}).then((r) => {
|
||||
void client.findOne(settingsRes.class.WorkspaceSetting, {}).then((r) => {
|
||||
workspaceSettings = r
|
||||
})
|
||||
|
||||
@@ -113,10 +111,10 @@
|
||||
if (workspaceSettings === undefined) {
|
||||
const avatar = await avatarEditor.createAvatar()
|
||||
await client.createDoc(
|
||||
setting.class.WorkspaceSetting,
|
||||
settingsRes.class.WorkspaceSetting,
|
||||
core.space.Workspace,
|
||||
{ icon: avatar.avatar },
|
||||
setting.ids.WorkspaceSetting
|
||||
settingsRes.ids.WorkspaceSetting
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -131,6 +129,41 @@
|
||||
})
|
||||
}
|
||||
|
||||
const permissionConfigurationQuery = createQuery()
|
||||
let disablePermissionsConfiguration: Configuration | undefined = undefined
|
||||
$: arePermissionsDisabled = disablePermissionsConfiguration?.enabled ?? false
|
||||
|
||||
$: permissionConfigurationQuery.query(
|
||||
core.class.Configuration,
|
||||
{ _id: settingsRes.ids.DisablePermissionsConfiguration },
|
||||
(result) => {
|
||||
disablePermissionsConfiguration = result[0]
|
||||
}
|
||||
)
|
||||
|
||||
function handleTogglePermissions (): void {
|
||||
const newState = !arePermissionsDisabled
|
||||
showPopup(MessageBox, {
|
||||
label: newState ? settingsRes.string.DisablePermissions : settingsRes.string.EnablePermissions,
|
||||
message: newState
|
||||
? settingsRes.string.DisablePermissionsConfirmation
|
||||
: settingsRes.string.EnablePermissionsConfirmation,
|
||||
dangerous: true,
|
||||
action: async () => {
|
||||
if (disablePermissionsConfiguration === undefined) {
|
||||
await client.createDoc(
|
||||
core.class.Configuration,
|
||||
core.space.Workspace,
|
||||
{ enabled: newState },
|
||||
settingsRes.ids.DisablePermissionsConfiguration
|
||||
)
|
||||
} else {
|
||||
await client.update(disablePermissionsConfiguration, { enabled: newState })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const weekInfoFirstDay: number = getLocalWeekStart()
|
||||
const hasWeekInfo: boolean = hasLocalWeekStart()
|
||||
const weekNames = getWeekDayNames()
|
||||
@@ -138,7 +171,7 @@
|
||||
let selected: string
|
||||
|
||||
$: translateCB(
|
||||
hasWeekInfo ? setting.string.SystemSetupString : setting.string.DefaultString,
|
||||
hasWeekInfo ? settingsRes.string.SystemSetupString : settingsRes.string.DefaultString,
|
||||
{ day: weekNames?.get(weekInfoFirstDay)?.toLowerCase() ?? '' },
|
||||
$themeStore.language,
|
||||
(r) => {
|
||||
@@ -159,7 +192,7 @@
|
||||
|
||||
<div class="hulyComponent">
|
||||
<Header adaptive={'disabled'}>
|
||||
<Breadcrumb icon={setting.icon.Setting} label={setting.string.General} size={'large'} isCurrent />
|
||||
<Breadcrumb icon={settingsRes.icon.Setting} label={settingsRes.string.General} size={'large'} isCurrent />
|
||||
</Header>
|
||||
<div class="hulyComponent-content__column content">
|
||||
{#if loading}
|
||||
@@ -167,7 +200,7 @@
|
||||
{:else}
|
||||
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
|
||||
<div class="hulyComponent-content flex-col flex-gap-4">
|
||||
<div class="title"><Label label={setting.string.Workspace} /></div>
|
||||
<div class="title"><Label label={settingsRes.string.Workspace} /></div>
|
||||
<div class="ws">
|
||||
<EditableAvatar
|
||||
person={{
|
||||
@@ -183,7 +216,7 @@
|
||||
<div class="editBox">
|
||||
<EditBox
|
||||
bind:value={name}
|
||||
placeholder={setting.string.WorkspaceName}
|
||||
placeholder={settingsRes.string.WorkspaceName}
|
||||
kind="ghost-large"
|
||||
disabled={!isEditingName}
|
||||
/>
|
||||
@@ -202,13 +235,13 @@
|
||||
icon={IconDelete}
|
||||
kind="dangerous"
|
||||
on:click={handleDelete}
|
||||
showTooltip={{ label: setting.string.DeleteWorkspace }}
|
||||
showTooltip={{ label: settingsRes.string.DeleteWorkspace }}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-col flex-gap-4 mt-6">
|
||||
<div class="title"><Label label={setting.string.Calendar} /></div>
|
||||
<div class="title"><Label label={settingsRes.string.Calendar} /></div>
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<Label label={setting.string.StartOfTheWeek} />
|
||||
<Label label={settingsRes.string.StartOfTheWeek} />
|
||||
<DropdownLabels
|
||||
{items}
|
||||
kind={'regular'}
|
||||
@@ -219,6 +252,16 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title mt-6"><Label label={settingsRes.string.Permissions} /></div>
|
||||
<div class="delete">
|
||||
<Button
|
||||
kind="regular"
|
||||
label={arePermissionsDisabled
|
||||
? settingsRes.string.EnablePermissions
|
||||
: settingsRes.string.DisablePermissions}
|
||||
on:click={handleTogglePermissions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Scroller>
|
||||
{/if}
|
||||
|
||||
@@ -118,6 +118,10 @@ export default mergeIds(settingId, setting, {
|
||||
StartOfTheWeek: '' as IntlString,
|
||||
SystemSetupString: '' as IntlString,
|
||||
DefaultString: '' as IntlString,
|
||||
WorkspaceNamePattern: '' as IntlString
|
||||
WorkspaceNamePattern: '' as IntlString,
|
||||
DisablePermissions: '' as IntlString,
|
||||
EnablePermissions: '' as IntlString,
|
||||
DisablePermissionsConfirmation: '' as IntlString,
|
||||
EnablePermissionsConfirmation: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -136,6 +136,7 @@ export default plugin(settingId, {
|
||||
Spaces: '' as Ref<Doc>,
|
||||
Backup: '' as Ref<Doc>,
|
||||
Export: '' as Ref<Doc>,
|
||||
DisablePermissionsConfiguration: '' as Ref<Configuration>,
|
||||
Mailboxes: '' as Ref<Doc>
|
||||
},
|
||||
mixin: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Doc, DocumentQuery } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { configurationStore } from '@hcengineering/presentation'
|
||||
import { pluginConfigurationStore } from '@hcengineering/presentation'
|
||||
import tracker, { Issue, trackerId } from '@hcengineering/tracker'
|
||||
import { Icon, Label } from '@hcengineering/ui'
|
||||
import QueryIssuesList from '../edit/QueryIssuesList.svelte'
|
||||
@@ -13,7 +13,7 @@
|
||||
$: query = { 'relations._id': object._id, 'relations._class': object._class }
|
||||
</script>
|
||||
|
||||
{#if $configurationStore.has(trackerId)}
|
||||
{#if $pluginConfigurationStore.has(trackerId)}
|
||||
<QueryIssuesList {object} {query} createParams={{ relatedTo: object }} hasSubIssues>
|
||||
<svelte:fragment slot="header">
|
||||
<div class="flex-row-center">
|
||||
|
||||
@@ -12,36 +12,37 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
import core, {
|
||||
type Class,
|
||||
concatLink,
|
||||
type Doc,
|
||||
getCurrentAccount,
|
||||
type Ref,
|
||||
generateId,
|
||||
reduceCalls
|
||||
getCurrentAccount,
|
||||
reduceCalls,
|
||||
type Ref
|
||||
} from '@hcengineering/core'
|
||||
import { type Application, workbenchId, type WorkbenchTab } from '@hcengineering/workbench'
|
||||
import notification, { notificationId } from '@hcengineering/notification'
|
||||
import { type Asset, getMetadata, getResource, type IntlString, setMetadata, translate } from '@hcengineering/platform'
|
||||
import presentation, { configurationStore, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
location as locationStore,
|
||||
locationToUrl,
|
||||
parseLocation,
|
||||
type Location,
|
||||
navigate,
|
||||
type AnyComponent,
|
||||
getCurrentLocation,
|
||||
languageStore,
|
||||
type AnyComponent,
|
||||
locationStorageKeyId
|
||||
type Location,
|
||||
locationStorageKeyId,
|
||||
location as locationStore,
|
||||
locationToUrl,
|
||||
navigate,
|
||||
parseLocation
|
||||
} from '@hcengineering/ui'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import view from '@hcengineering/view'
|
||||
import { type Asset, type IntlString, getMetadata, getResource, translate } from '@hcengineering/platform'
|
||||
import { parseLinkId } from '@hcengineering/view-resources'
|
||||
import notification, { notificationId } from '@hcengineering/notification'
|
||||
import { type Application, workbenchId, type WorkbenchTab } from '@hcengineering/workbench'
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
|
||||
import { locationWorkspaceStore } from './utils'
|
||||
import setting from '@hcengineering/setting'
|
||||
import workbench from './plugin'
|
||||
import { locationWorkspaceStore } from './utils'
|
||||
|
||||
export const tabIdStore = writable<Ref<WorkbenchTab> | undefined>()
|
||||
export const prevTabIdStore = writable<Ref<WorkbenchTab> | undefined>()
|
||||
@@ -297,3 +298,8 @@ async function getDefaultTabName (loc: Location): Promise<string | undefined> {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
configurationStore.subscribe((config) => {
|
||||
const arePermissionsDisabled = config.get(setting.ids.DisablePermissionsConfiguration)?.enabled ?? false
|
||||
setMetadata(core.metadata.DisablePermissions, arePermissionsDisabled)
|
||||
})
|
||||
|
||||
@@ -13,16 +13,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, {
|
||||
import {
|
||||
AccountRole,
|
||||
Class,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
DOMAIN_CONFIGURATION,
|
||||
FindOptions,
|
||||
FindResult,
|
||||
MeasureContext,
|
||||
Ref,
|
||||
Tx,
|
||||
TxCUD,
|
||||
TxProcessor,
|
||||
@@ -68,20 +63,4 @@ export class ConfigurationMiddleware extends BaseMiddleware implements Middlewar
|
||||
}
|
||||
return this.provideTx(ctx, txes)
|
||||
}
|
||||
|
||||
findAll<T extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<FindResult<T>> {
|
||||
const domain = this.context.hierarchy.getDomain(_class)
|
||||
if (this.targetDomains.includes(domain)) {
|
||||
const account = ctx.contextData.account
|
||||
if (account.role !== AccountRole.Owner && account._id !== core.account.System && ctx.contextData.admin !== true) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
}
|
||||
return this.provideFindAll(ctx, _class, query, options)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user