Merge branch 'develop' into staging-new

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2026-01-11 20:35:31 +05:00
39 changed files with 896 additions and 609 deletions
+1 -1
View File
@@ -97,7 +97,7 @@
"MODEL_JSON": "${workspaceRoot}/models/all/bundle/model.json",
// "SERVER_PROVIDER":"uweb"
"SERVER_PROVIDER": "ws",
"MODEL_VERSION": "0.7.331",
"MODEL_VERSION": "0.7.343",
// "version": "0.7.0",
"COMMUNICATION_API_ENABLED": "true",
"ELASTIC_INDEX_NAME": "local_storage_index",
+188 -139
View File
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -54,7 +54,9 @@ commit_preprocessors = [
{ pattern = '^QFix ', replace = "fix: " },
{ pattern = '^qfix ', replace = "fix: " },
{ pattern = '^QFIX: ', replace = "fix: " },
{ pattern = '^Add ', replace = "feat: " },
{ pattern = '^Missing ', replace = "fix: Missing " },
{ pattern = '^Add missing ', replace = "fix: Add missing " },
{ pattern = '^Add ', replace = "feat: Add " },
{ pattern = '^Update ', replace = "chore: " },
{ pattern = '^Remove ', replace = "chore: " },
{ pattern = '^Bump ', replace = "chore: " },
+7 -42
View File
@@ -6815,6 +6815,9 @@ importers:
'@hcengineering/chunter':
specifier: workspace:^0.7.0
version: link:../../plugins/chunter
'@hcengineering/communication':
specifier: workspace:^0.7.0
version: link:../../plugins/communication
'@hcengineering/contact':
specifier: workspace:^0.7.0
version: link:../../plugins/contact
@@ -17320,6 +17323,9 @@ importers:
'@hcengineering/account-client':
specifier: workspace:^0.7.21
version: link:../../foundations/core/packages/account-client
'@hcengineering/activity':
specifier: workspace:^0.7.0
version: link:../activity
'@hcengineering/analytics':
specifier: workspace:^0.7.17
version: link:../../foundations/core/packages/analytics
@@ -61252,7 +61258,7 @@ snapshots:
node-loader@2.0.0(webpack@5.102.1):
dependencies:
loader-utils: 2.0.4
webpack: 5.102.1
webpack: 5.102.1(@swc/core@1.15.1)(esbuild@0.25.12)(webpack-cli@5.1.4)
node-localstorage@2.2.1:
dependencies:
@@ -63283,15 +63289,6 @@ snapshots:
optionalDependencies:
'@swc/core': 1.15.1
terser-webpack-plugin@5.3.14(webpack@5.102.1):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
serialize-javascript: 6.0.2
terser: 5.44.1
webpack: 5.102.1
terser@5.44.1:
dependencies:
'@jridgewell/source-map': 0.3.11
@@ -63958,38 +63955,6 @@ snapshots:
webpack-sources@3.3.3: {}
webpack@5.102.1:
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
'@types/json-schema': 7.0.15
'@webassemblyjs/ast': 1.14.1
'@webassemblyjs/wasm-edit': 1.14.1
'@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.15.0
acorn-import-phases: 1.0.4(acorn@8.15.0)
browserslist: 4.28.1
chrome-trace-event: 1.0.4
enhanced-resolve: 5.18.3
es-module-lexer: 1.7.0
eslint-scope: 5.1.1
events: 3.3.0
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
json-parse-even-better-errors: 2.3.1
loader-runner: 4.3.1
mime-types: 2.1.35
neo-async: 2.6.2
schema-utils: 4.3.3
tapable: 2.3.0
terser-webpack-plugin: 5.3.14(webpack@5.102.1)
watchpack: 2.4.4
webpack-sources: 3.3.3
transitivePeerDependencies:
- '@swc/core'
- esbuild
- uglify-js
webpack@5.102.1(@swc/core@1.15.1):
dependencies:
'@types/eslint-scope': 3.7.7
+1 -1
View File
@@ -1 +1 @@
"0.7.340"
"0.7.343"
-1
View File
@@ -488,7 +488,6 @@ export async function configurePlatform() {
setMetadata(textEditor.metadata.Collaborator, config.COLLABORATOR)
setMetadata(communication.metadata.Enabled, config.COMMUNICATION_API_ENABLED === 'true')
if (config.MODEL_VERSION != null) {
console.log('Minimal Model version requirement', config.MODEL_VERSION)
setMetadata(presentation.metadata.ModelVersion, config.MODEL_VERSION)
@@ -944,6 +944,7 @@ export type SocialKey = Pick<SocialId, 'type' | 'value'>
export interface ClassCollaborators<T extends Doc> extends Doc {
attachedTo: Ref<Class<T>>
allFields?: boolean // for all (PersonId | Ref<Employee> | PersonId[] | Ref<Employee>[]) attributes
fields: (keyof T)[] // PersonId | Ref<Employee> | PersonId[] | Ref<Employee>[]
provideSecurity?: boolean // If true, will provide security for collaborators
}
+1
View File
@@ -36,6 +36,7 @@
"dependencies": {
"@hcengineering/activity": "workspace:^0.7.0",
"@hcengineering/contact": "workspace:^0.7.0",
"@hcengineering/communication": "workspace:^0.7.0",
"@hcengineering/core": "workspace:^0.7.24",
"@hcengineering/model": "workspace:^0.7.17",
"@hcengineering/chunter": "workspace:^0.7.0",
+34
View File
@@ -12,6 +12,7 @@
// limitations under the License.
import activity from '@hcengineering/activity'
import communication from '@hcengineering/communication'
import {
type CanCreateCardResource,
type Card,
@@ -37,6 +38,7 @@ import core, {
AccountRole,
type Blobs,
type Class,
type ClassCollaborators,
ClassifierKind,
type CollectionSize,
type Doc,
@@ -859,6 +861,12 @@ export function createModel (builder: Builder): void {
})
createPublicLinkAction(builder, card.class.Card, card.action.PublicLink)
builder.createDoc<ClassCollaborators<Card>>(core.class.ClassCollaborators, core.space.Model, {
attachedTo: card.class.Card,
fields: ['modifiedBy'],
allFields: true
})
}
function defineTabs (builder: Builder): void {
@@ -923,6 +931,32 @@ function defineTabs (builder: Builder): void {
card.section.Relations
)
builder.createDoc(
card.class.CardSection,
core.space.Model,
{
label: activity.string.Messages,
component: card.sectionComponent.OldMessagesSection,
order: 1000,
navigation: [],
checkVisibility: card.function.CheckOldMessagesSectionVisibility
},
card.section.OldMessages
)
builder.createDoc(
card.class.CardSection,
core.space.Model,
{
label: activity.string.Messages,
component: card.sectionComponent.CommunicationMessagesSection,
order: 1000,
navigation: [],
checkVisibility: card.function.CheckCommunicationMessagesSectionVisibility
},
communication.ids.CardMessagesSection
)
builder.createDoc<Viewlet>(view.class.Viewlet, core.space.Model, {
attachTo: card.class.FavoriteCard,
descriptor: view.viewlet.Table,
+160
View File
@@ -17,6 +17,8 @@ import cardPlugin, { cardId, DOMAIN_CARD, type Card, type Role } from '@hcengine
import core, {
DOMAIN_MODEL,
TxOperations,
type Class,
type ClassPermission,
type Client,
type Data,
type Doc,
@@ -121,11 +123,169 @@ export const cardOperation: MigrateOperation = {
state: 'version-for-versionable-types',
mode: 'upgrade',
func: addVersionForVersionableTypes
},
{
state: 'migrate-restricted-permissions',
mode: 'upgrade',
func: migrateRestrictedPermissions
}
])
}
}
async function migrateRestrictedPermissions (_client: MigrationUpgradeClient): Promise<void> {
const client = new TxOperations(_client, core.account.System)
const hierarchy = client.getHierarchy()
const desc = hierarchy.getDescendants(card.class.Card)
const permissions = await client.findAll(core.class.ClassPermission, { objectClass: { $in: desc } })
const restrictedTargets = new Set<Ref<Class<Doc>>>()
for (const perm of permissions) {
if (perm.targetClass !== undefined) {
restrictedTargets.add(perm.targetClass)
}
}
const targets = await client.findAll(card.class.MasterTag, { _id: { $in: [...restrictedTargets] } })
for (const masterTag of targets) {
const isMixin = hierarchy.isMixin(masterTag._id)
const objectClass = hierarchy.getBaseClass(masterTag._id)
if (isMixin) {
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxMixin,
txMatch: {
mixin: masterTag._id
},
scope: 'space',
forbid: false,
label: card.string.AddTagPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxMixin,
txMatch: {
mixin: masterTag._id
},
scope: 'space',
forbid: true,
label: card.string.ForbidAddTagPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_forbidden` as Ref<ClassPermission>
)
const key = `operations.$unset.${masterTag._id}`
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxUpdateDoc,
txMatch: {
[key]: {
$exists: true
}
},
scope: 'space',
forbid: false,
label: card.string.RemoveTag,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxUpdateDoc,
txMatch: {
[key]: { $exists: true }
},
scope: 'space',
forbid: true,
label: card.string.ForbidRemoveTag,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_forbidden` as Ref<ClassPermission>
)
} else {
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxCreateDoc,
scope: 'space',
forbid: false,
label: card.string.CreateCardPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxCreateDoc,
scope: 'space',
forbid: true,
label: card.string.ForbidCreateCardPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_forbidden` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxRemoveDoc,
scope: 'space',
forbid: false,
label: card.string.RemoveCard,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxRemoveDoc,
txMatch: {
objectClass: masterTag._id
},
scope: 'space',
forbid: true,
label: card.string.ForbidRemoveCard,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_forbidden` as Ref<ClassPermission>
)
}
}
}
async function addVersionForVersionableTypes (client: MigrationUpgradeClient): Promise<void> {
const txOp = new TxOperations(client, core.account.System)
const versionableTypes = await client.findAll(card.class.MasterTag, {})
+4 -24
View File
@@ -17,7 +17,7 @@ import { type Card, cardId } from '@hcengineering/card'
import card from '@hcengineering/card-resources/src/plugin'
import type { Client, Doc, Ref } from '@hcengineering/core'
import {} from '@hcengineering/core'
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
import { mergeIds, type Resource } from '@hcengineering/platform'
import { type TagCategory } from '@hcengineering/tags'
import { type Location, type ResolvedLocation } from '@hcengineering/ui/src/types'
import { type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
@@ -39,28 +39,6 @@ export default mergeIds(cardId, card, {
PublicLink: '' as Ref<Action<Doc, any>>,
Duplicate: '' as Ref<Action>
},
string: {
CreateCardPersmissionDescription: '' as IntlString,
UpdateCardPersmissionDescription: '' as IntlString,
RemoveCardPersmissionDescription: '' as IntlString,
AddTagPersmissionDescription: '' as IntlString,
RemoveTagPersmissionDescription: '' as IntlString,
RemoveCard: '' as IntlString,
UpdateCard: '' as IntlString,
CreateCardPermission: '' as IntlString,
AddTagPermission: '' as IntlString,
RemoveTag: '' as IntlString,
ForbidCreateCardPersmissionDescription: '' as IntlString,
ForbidUpdateCardPersmissionDescription: '' as IntlString,
ForbidRemoveCardPersmissionDescription: '' as IntlString,
ForbidAddTagPersmissionDescription: '' as IntlString,
ForbidRemoveTagPersmissionDescription: '' as IntlString,
ForbidRemoveCard: '' as IntlString,
ForbidUpdateCard: '' as IntlString,
ForbidCreateCardPermission: '' as IntlString,
ForbidAddTagPermission: '' as IntlString,
ForbidRemoveTag: '' as IntlString
},
category: {
Card: '' as Ref<ActionCategory>,
Labels: '' as Ref<TagCategory>
@@ -79,6 +57,8 @@ export default mergeIds(cardId, card, {
GetCardLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
CardCustomLinkMatch: '' as Resource<(doc: Doc) => boolean>,
CardCustomLinkEncode: '' as Resource<(doc: Doc) => Location>,
CheckRelationsSectionVisibility: '' as Resource<(doc: Card) => Promise<boolean>>
CheckRelationsSectionVisibility: '' as Resource<(doc: Card) => Promise<boolean>>,
CheckOldMessagesSectionVisibility: '' as Resource<(doc: Card) => Promise<boolean>>,
CheckCommunicationMessagesSectionVisibility: '' as Resource<(doc: Card) => Promise<boolean>>
}
})
+1
View File
@@ -413,6 +413,7 @@ export class TCustomSequence extends TSequence implements CustomSequence {
@Model(core.class.ClassCollaborators, core.class.Doc, DOMAIN_MODEL)
export class TClassCollaborators extends TDoc implements ClassCollaborators<Doc> {
attachedTo!: Ref<Class<Doc>>
allFields?: boolean
fields!: (keyof Doc)[]
provideSecurity?: boolean
}
+4 -3
View File
@@ -49,7 +49,7 @@ import {
type CheckFunc,
type ContextId,
type CreatedContext,
type ProcessCustomEvent,
type EventButton,
type Execution,
type ExecutionContext,
type ExecutionError,
@@ -60,6 +60,7 @@ import {
type MethodParams,
type Process,
type ProcessContext,
type ProcessCustomEvent,
type ProcessFunction,
type ProcessToDo,
type State,
@@ -67,8 +68,7 @@ import {
type Transition,
type Trigger,
type UpdateCriteriaComponent,
processId,
type EventButton
processId
} from '@hcengineering/process'
import time from '@hcengineering/time'
import { type AnyComponent } from '@hcengineering/ui'
@@ -616,6 +616,7 @@ export function createModel (builder: Builder): void {
{
label: process.string.Processes,
component: process.component.ProcessesCardSection,
checkVisibility: process.function.CheckProcessSectionVisibility,
order: 350,
navigation: []
},
+4
View File
@@ -1367,6 +1367,10 @@ export function createModel (builder: Builder): void {
searchDisabled: true
})
builder.mixin(core.class.TypePersonId, core.class.Class, view.mixin.AttributeEditor, {
inlineEditor: view.component.PersonIdPresenter
})
builder.mixin(core.class.TypePersonId, core.class.Class, view.mixin.AttributePresenter, {
presenter: view.component.PersonIdPresenter,
arrayPresenter: view.component.PersonArrayEditor
+1
View File
@@ -43,6 +43,7 @@
"@hcengineering/presentation": "workspace:^0.7.0",
"@hcengineering/communication-types": "workspace:^0.7.12",
"@hcengineering/communication-resources": "workspace:^0.7.0",
"@hcengineering/activity": "workspace:^0.7.0",
"@hcengineering/core": "workspace:^0.7.24",
"@hcengineering/ui": "workspace:^0.7.0",
"@hcengineering/setting": "workspace:^0.7.0",
+9 -7
View File
@@ -13,7 +13,7 @@
import { getClient } from '@hcengineering/presentation'
import cardPlugin, { type Card, type CardSection } from '@hcengineering/card'
import { getResource } from '@hcengineering/platform'
import { getMetadata, getResource } from '@hcengineering/platform'
import { type Heading } from '@hcengineering/text-editor'
import communication from '@hcengineering/communication'
@@ -69,12 +69,14 @@ export function getCardToc (sections: CardSection[], tocBySection: Record<string
}
}
newToc.push({
id: communication.ids.CardMessagesSection,
titleIntl: communication.string.Messages,
level: 0,
group: communication.ids.CardMessagesSection
})
if (getMetadata(communication.metadata.Enabled) === true) {
newToc.push({
id: communication.ids.CardMessagesSection,
titleIntl: communication.string.Messages,
level: 0,
group: communication.ids.CardMessagesSection
})
}
return newToc
}
@@ -17,11 +17,11 @@
import { PermissionsStore } from '@hcengineering/contact'
import { permissionsStore } from '@hcengineering/contact-resources'
import core, { AnyAttribute, Class, Doc, Ref, toRank, TypedSpace } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import {
AttributeBarEditor,
createQuery,
getClient,
getFiltredKeys,
isCollectionAttr,
KeyedAttribute
} from '@hcengineering/presentation'
@@ -29,11 +29,12 @@
export let object: Card
export let _class: Ref<Class<Doc>>
export let to: Ref<Class<Doc>> | undefined = core.class.Doc
export let to: Ref<Class<Doc>> | undefined = core.class.Obj
export let ignoreKeys: string[] = []
export let readonly = false
export let showHeader: boolean = true
export let fourRows: boolean = false
export let showCollaborators: boolean = false
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -41,14 +42,18 @@
let keys: KeyedAttribute[] = []
function updateKeys (_class: Ref<Class<Doc>>, ignoreKeys: string[], to: Ref<Class<Doc>> | undefined): void {
const filtredKeys = getFiltredKeys(hierarchy, _class, ignoreKeys, to)
keys = filtredKeys
.filter((key) => !isCollectionAttr(hierarchy, key))
.sort((a, b) => {
const rankA = a.attr.rank ?? toRank(a.attr._id) ?? ''
const rankB = b.attr.rank ?? toRank(b.attr._id) ?? ''
return rankA.localeCompare(rankB)
})
const filtredKeys = [...hierarchy.getAllAttributes(_class, to).entries()]
.filter(
([key, value]) =>
value.hidden !== true && !ignoreKeys.includes(key) && !isCollectionAttr(hierarchy, { key, attr: value })
)
.map(([key, attr]) => ({ key, attr }))
keys = filtredKeys.sort((a, b) => {
const rankA = a.attr.rank ?? toRank(a.attr._id) ?? ''
const rankB = b.attr.rank ?? toRank(b.attr._id) ?? ''
return rankA.localeCompare(rankB)
})
}
$: updateKeys(_class, ignoreKeys, to)
@@ -75,6 +80,17 @@
on:update
/>
{/each}
{#if showCollaborators}
<AttributeBarEditor
key={'collaborators'}
_class={notification.mixin.Collaborators}
{object}
{showHeader}
{readonly}
withIcon
on:update
/>
{/if}
</div>
<style lang="scss">
@@ -15,15 +15,12 @@
//
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import { Card, CardEvents } from '@hcengineering/card'
import { Ref } from '@hcengineering/core'
import { onMount } from 'svelte'
import { Analytics } from '@hcengineering/analytics'
import communication from '@hcengineering/communication'
import { getMetadata } from '@hcengineering/platform'
import EditCardNew from './EditCardNew.svelte'
import EditCardOld from './EditCardOld.svelte'
export let _id: Ref<Card>
export let readonly: boolean = false
@@ -39,8 +36,4 @@
})
</script>
{#if getMetadata(communication.metadata.Enabled) === true}
<EditCardNew {_id} {readonly} {embedded} {allowClose} on:close on:open />
{:else}
<EditCardOld {_id} {readonly} {embedded} on:close on:open />
{/if}
<EditCardNew {_id} {readonly} {embedded} {allowClose} on:close on:open />
@@ -15,12 +15,14 @@
<script lang="ts">
import { Card } from '@hcengineering/card'
import { resizeObserver } from '@hcengineering/ui'
import communication from '@hcengineering/communication'
import { NotificationContext } from '@hcengineering/communication-types'
import { Ref } from '@hcengineering/core'
import { resizeObserver } from '@hcengineering/ui'
import EditCardTableOfContents from './EditCardTableOfContents.svelte'
import { MessageInput } from '@hcengineering/communication-resources'
import { getMetadata } from '@hcengineering/platform'
import EditCardTableOfContents from './EditCardTableOfContents.svelte'
export let _id: Ref<Card>
export let doc: Card
@@ -55,7 +57,7 @@
<EditCardTableOfContents bind:this={content} bind:scrollDiv {doc} {readonly} {context} {isContextLoaded} />
{/key}
{/if}
{#if !readonly}
{#if !readonly && getMetadata(communication.metadata.Enabled) === true}
<div class="message-input" use:resizeObserver={onInputResize}>
<MessageInput
card={doc}
@@ -1,238 +0,0 @@
<!--
//
// 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.
//
-->
<script lang="ts">
import { Attachments } from '@hcengineering/attachment-resources'
import { Card } from '@hcengineering/card'
import { Doc, Mixin, Ref, WithLookup } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import { Panel } from '@hcengineering/panel'
import { getResource } from '@hcengineering/platform'
import { ComponentExtensions, createQuery, getClient } from '@hcengineering/presentation'
import {
Button,
EditBox,
FocusHandler,
IconMoreH,
createFocusManager,
getCurrentLocation,
navigate
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import {
ParentsNavigator,
RelationsEditor,
canChangeDoc,
getDocMixins,
showMenu
} from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy } from 'svelte'
import card from '../plugin'
import CardAttributeEditor from './CardAttributeEditor.svelte'
import CardIcon from './CardIcon.svelte'
import CardPresenter from './CardPresenter.svelte'
import Childs from './Childs.svelte'
import Content from './Content.svelte'
import TagsEditor from './TagsEditor.svelte'
import { permissionsStore } from '@hcengineering/contact-resources'
import CardVersionSelector from './CardVersionSelector.svelte'
export let _id: Ref<Card>
export let readonly: boolean = false
export let embedded: boolean = false
let useMaxWidth = getUseMaxWidth()
$: saveUseMaxWidth(useMaxWidth)
let lastId: Ref<Doc> = _id
const query = createQuery()
const dispatch = createEventDispatcher()
const client = getClient()
let doc: WithLookup<Card> | undefined
let title = ''
let innerWidth: number
const notificationClient = getResource(notification.function.GetInboxNotificationsClient).then((res) => res())
$: read(_id)
function read (_id: Ref<Doc>): void {
if (lastId !== _id) {
const prev = lastId
lastId = _id
void notificationClient.then((client) => client.readDoc(prev))
}
}
onDestroy(async () => {
void notificationClient.then((client) => client.readDoc(_id))
})
$: _id !== undefined &&
query.query(card.class.Card, { _id }, async (result) => {
if (result.length > 0) {
;[doc] = result
title = doc?.title ?? ''
} else {
const loc = getCurrentLocation()
loc.path.length = 3
navigate(loc)
}
})
$: _readonly = (readonly || doc?.readonly) ?? false
$: updatePermissionForbidden = doc && !canChangeDoc(doc?._class, doc?.space, $permissionsStore)
$: canSave = title.trim().length > 0 && !_readonly
async function saveTitle (ev: Event): Promise<void> {
ev.preventDefault()
if (doc === undefined || !canSave) {
return
}
const nameTrimmed = title.trim()
if (nameTrimmed.length > 0 && nameTrimmed !== doc.title) {
await client.update(doc, { title: nameTrimmed })
}
}
function getUseMaxWidth (): boolean {
const useMaxWidth = localStorage.getItem('document.useMaxWidth')
return useMaxWidth === 'true'
}
function saveUseMaxWidth (useMaxWidth: boolean): void {
localStorage.setItem('document.useMaxWidth', useMaxWidth.toString())
}
let content: HTMLElement
const manager = createFocusManager()
let mixins: Array<Mixin<Doc>> = []
$: mixins = doc !== undefined ? getDocMixins(doc) : []
</script>
<FocusHandler {manager} />
{#if doc !== undefined}
<Panel
object={doc}
allowClose={!embedded}
isAside={false}
isHeader={false}
isSub={false}
bind:useMaxWidth
printHeader={false}
{embedded}
adaptive={'default'}
bind:content
bind:innerWidth
floatAside={false}
on:open
on:close={() => dispatch('close')}
>
<svelte:fragment slot="beforeTitle">
<CardIcon value={doc} />
</svelte:fragment>
<svelte:fragment slot="title">
<ParentsNavigator element={doc} />
<CardPresenter value={doc} noUnderline />
</svelte:fragment>
<svelte:fragment slot="extra">
<ComponentExtensions
extension={card.extensions.EditCardHeaderExtension}
props={{
card: doc
}}
/>
<slot name="extra" />
</svelte:fragment>
<div class="container">
<div class="title flex-row-center">
<EditBox
focusIndex={1}
bind:value={title}
disabled={_readonly || updatePermissionForbidden}
placeholder={card.string.Card}
on:blur={(evt) => saveTitle(evt)}
shrink
/>
</div>
<CardVersionSelector bind:value={doc} />
<TagsEditor {doc} readonly={_readonly} />
<CardAttributeEditor value={doc} {mixins} readonly={_readonly} ignoreKeys={['title', 'content', 'parent']} />
<Content {doc} readonly={_readonly || updatePermissionForbidden} bind:content />
</div>
<ComponentExtensions
extension={card.extensions.EditCardExtension}
props={{
card: doc
}}
/>
<Childs object={doc} readonly={_readonly} />
<RelationsEditor object={doc} readonly={_readonly} />
<Attachments
objectId={doc._id}
_class={doc._class}
space={doc.space}
readonly={_readonly}
attachments={doc.attachments ?? 0}
/>
<svelte:fragment slot="utils">
{#if !_readonly}
<Button
icon={IconMoreH}
iconProps={{ size: 'medium' }}
kind={'icon'}
dataId={'btnMoreActions'}
on:click={(e) => {
showMenu(e, { object: doc, excludedActions: [view.action.Open] })
}}
/>
{/if}
</svelte:fragment>
</Panel>
{/if}
<style lang="scss">
.container {
display: flex;
flex-direction: column;
width: 100%;
margin: auto;
}
.title {
font-size: 2.25rem;
margin-top: 1.75rem;
margin-bottom: 1rem;
}
</style>
@@ -15,18 +15,18 @@
<script lang="ts">
import card, { Card, CardSection, CardViewDefaults } from '@hcengineering/card'
import { Component, Loading, Scroller, ModernButton } from '@hcengineering/ui'
import { getClient } from '@hcengineering/presentation'
import communication from '@hcengineering/communication'
import { NotificationContext } from '@hcengineering/communication-types'
import { Ref } from '@hcengineering/core'
import { SvelteComponent, tick } from 'svelte'
import { TableOfContents } from '@hcengineering/text-editor-resources'
import { getClient } from '@hcengineering/presentation'
import { Heading } from '@hcengineering/text-editor'
import communication from '@hcengineering/communication'
import { MessagesSection } from '@hcengineering/communication-resources'
import { TableOfContents } from '@hcengineering/text-editor-resources'
import { Component, Loading, ModernButton, Scroller } from '@hcengineering/ui'
import { SvelteComponent, tick } from 'svelte'
import { CardSectionAction } from '../types'
import { getMetadata } from '@hcengineering/platform'
import { getCardSections, getCardToc } from '../card'
import { CardSectionAction } from '../types'
export let doc: Card
export let context: NotificationContext | undefined = undefined
@@ -216,6 +216,12 @@
$: updateToc(sections, subTocBySection)
$: showOverlay = !isScrollInitialized || Object.values(sectionOverlays).some((it) => it)
const onRenderTopChange = (active: boolean): void => {
renderTopSections = active
}
const bottomPadding = getMetadata(communication.metadata.Enabled) === true ? 'var(--spacing-3)' : undefined
</script>
<div class="hulyComponent-content__container columns relative">
@@ -240,7 +246,7 @@
<Scroller
padding="0"
{hideBar}
bottomPadding="var(--spacing-3)"
{bottomPadding}
disablePointerEventsOnScroll
disableOverscroll
bind:divScroll={scrollDiv}
@@ -259,7 +265,12 @@
scrollDiv,
contentDiv: sectionElement[section._id],
navigation: selectedToc?.id,
hidden: !renderTopSections
hidden: !renderTopSections,
isDefault: defaults?.defaultSection === section._id,
active: selectedToc?.group === section._id,
context,
isContextLoaded,
onRenderTopChange
}}
on:loaded={() => {
handleSectionLoaded(section._id)
@@ -273,34 +284,6 @@
/>
</div>
{/each}
<div id={messagesId} bind:this={sectionElement[messagesId]} class="section">
<MessagesSection
bind:this={sectionRef[messagesId]}
{doc}
{readonly}
{isContextLoaded}
{scrollDiv}
{context}
contentDiv={sectionElement[messagesId]}
active={selectedToc?.group === messagesId}
isDefault={defaults?.defaultSection === messagesId}
on:top-loaded={() => {
renderTopSections = true
}}
on:top-hidden={() => {
renderTopSections = false
}}
on:loaded={() => {
handleSectionLoaded(messagesId)
}}
on:change={(ev) => {
handleChangeNavigation(messagesId, ev.detail)
}}
on:action={(ev) => {
handleAction(messagesId, ev.detail)
}}
/>
</div>
</div>
</Scroller>
{#if toc.length > 0 && (bottomOffset > 400 || canScrollDown()) && selectedToc?.group === messagesId}
@@ -88,7 +88,7 @@
{/if}
</div>
<ExpandCollapse isExpanded={!isCollapsed}>
<CardAttributes object={value} _class={value._class} {readonly} {ignoreKeys} {fourRows} />
<CardAttributes object={value} _class={value._class} {readonly} {ignoreKeys} {fourRows} showCollaborators />
</ExpandCollapse>
<style lang="scss">
@@ -38,8 +38,8 @@
export let ignoreKeys: string[]
const client = getClient()
const hierarchy = client.getHierarchy()
const label = hierarchy.getClass(tag._id).label
const h = client.getHierarchy()
const label = h.getClass(tag._id).label
let isCollapsed = false
@@ -52,6 +52,8 @@
}
$: color = getPlatformColorDef(tag.background ?? 0, $themeStore.dark).color
$: isEditable = h.hasMixin(tag, setting.mixin.Editable) && h.as(tag, setting.mixin.Editable).value
</script>
<div class="header flex flex-gap-2">
@@ -66,7 +68,7 @@
</span>
<Chevron expanded={!isCollapsed} outline fill={'var(--content-color'} />
</div>
{#if hasAccountRole(getCurrentAccount(), AccountRole.Maintainer)}
{#if hasAccountRole(getCurrentAccount(), AccountRole.Maintainer) && isEditable}
<div class="btns">
<Button
icon={IconAdd}
@@ -0,0 +1,50 @@
<!--
// Copyright © 2026 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Card } from '@hcengineering/card'
import { MessagesSection } from '@hcengineering/communication-resources'
import { NotificationContext } from '@hcengineering/communication-types'
export let doc: Card
export let readonly: boolean = false
export let scrollDiv: HTMLDivElement | undefined | null = undefined
export let context: NotificationContext | undefined = undefined
export let contentDiv: HTMLDivElement
export let isContextLoaded: boolean = false
export let active: boolean = false
export let isDefault: boolean = false
export let onRenderTopChange: (active: boolean) => void
</script>
<MessagesSection
{doc}
{readonly}
{isContextLoaded}
{scrollDiv}
{context}
{contentDiv}
{active}
{isDefault}
on:top-loaded={() => {
onRenderTopChange(true)
}}
on:top-hidden={() => {
onRenderTopChange(false)
}}
on:loaded
on:change
on:action
/>
@@ -0,0 +1,36 @@
<!--
// Copyright © 2026 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Component } from '@hcengineering/ui'
import activity from '@hcengineering/activity'
import { Card } from '@hcengineering/card'
export let doc: Card
export let readonly: boolean = false
export let contentDiv: HTMLDivElement
</script>
<div class="flex-col w-full px-4">
<Component
is={activity.component.Activity}
props={{
object: doc,
showCommenInput: !readonly,
focusIndex: 1000,
boundary: contentDiv
}}
/>
</div>
@@ -20,6 +20,7 @@
Class,
ClassPermission,
Doc,
notEmpty,
Permission,
Ref
} from '@hcengineering/core'
@@ -58,7 +59,8 @@
.getModel()
.findAllSync(cardPlugin.class.PermissionObjectClass, {})
.map((poc) => poc.objectClass)
const tagDesc = Array.from(new Set(role?.types?.map((p) => [...h.getAncestors(p), ...h.getDescendants(p)]).flat()))
const types = role?.types?.map((p) => h.findClass(p)).filter(notEmpty)
const tagDesc = Array.from(new Set(types?.map((p) => [...h.getAncestors(p._id), ...h.getDescendants(p._id)]).flat()))
const allPermissions = client.getModel().findAllSync(core.class.Permission, {
scope: 'space',
objectClass: { $in: [...cardPermissionsObjectClasses, ...tagDesc] }
@@ -14,18 +14,17 @@
-->
<script lang="ts">
import { MasterTag } from '@hcengineering/card'
import { ClassPermission, Ref } from '@hcengineering/core'
import { getClient, MessageBox } from '@hcengineering/presentation'
import { ClassAttributes } from '@hcengineering/setting-resources'
import setting from '@hcengineering/setting-resources/src/plugin'
import { ButtonIcon, showPopup } from '@hcengineering/ui'
import card from '../../plugin'
import { getClient, MessageBox } from '@hcengineering/presentation'
import core, { ClassPermission, Ref } from '@hcengineering/core'
import view from '@hcengineering/view'
import { createTypePermissions } from '../../utils'
export let masterTag: MasterTag
const client = getClient()
const hierarchy = client.getHierarchy()
let isRestricted: boolean =
client.getModel().findObject(getPermissionRef(false)) !== undefined ||
@@ -43,43 +42,7 @@
message: setting.string.RestrictedAttributeWarning,
action: async () => {
isRestricted = true
const isMixin = hierarchy.isMixin(masterTag._id)
const objectClass = hierarchy.getBaseClass(masterTag._id)
const txClass = isMixin ? core.class.TxMixin : core.class.TxUpdateDoc
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass,
txMatch: {
[isMixin ? 'mixin' : 'objectClass']: masterTag._id
},
scope: 'space',
forbid: false,
label: view.string.AllowAttributeChanges,
description: masterTag.label,
targetClass: masterTag._id
},
getPermissionRef(false)
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass,
txMatch: {
[isMixin ? 'mixin' : 'objectClass']: masterTag._id
},
scope: 'space',
forbid: true,
label: view.string.ForbidAttributeChanges,
description: masterTag.label,
targetClass: masterTag._id
},
getPermissionRef(true)
)
await createTypePermissions(masterTag)
}
},
'top',
+11 -9
View File
@@ -128,11 +128,22 @@ async function exportType (_id: Ref<Class<Doc>>, processed: Set<Ref<Doc>>): Prom
const m = client.getModel()
const h = client.getHierarchy()
const type = m.findObject(_id)
const parent = h.getClass(_id).extends
if (parent !== undefined && h.isDerived(parent, card.class.Card) && parent !== card.class.Card) {
if (!processed.has(parent)) {
res.push(...(await exportType(parent, processed)))
}
}
processed.add(_id)
if (type === undefined) return res
if (type.icon === view.ids.IconWithEmoji && typeof type.color === 'string') {
type.icon = card.icon.Card
}
res.push(...m.findAllSync(view.class.Viewlet, { attachTo: _id }))
res.push(type)
res.push(...m.findAllSync(core.class.ClassPermission, { targetClass: _id }))
@@ -152,8 +163,6 @@ async function exportType (_id: Ref<Class<Doc>>, processed: Set<Ref<Doc>>): Prom
res.push(...m.findAllSync(card.class.Role, { types: _id }))
res.push(...m.findAllSync(view.class.Viewlet, { attachTo: _id }))
const assocA = m.findAllSync(core.class.Association, { classA: _id })
for (const assoc of assocA) {
res.push(assoc)
@@ -174,13 +183,6 @@ async function exportType (_id: Ref<Class<Doc>>, processed: Set<Ref<Doc>>): Prom
required.push(...extRes.required)
}
const parent = h.getClass(_id).extends
if (parent !== undefined && h.isDerived(parent, card.class.Card) && parent !== card.class.Card) {
if (!processed.has(parent)) {
required.push(parent)
}
}
for (const req of required) {
res.push(...(await exportType(req, processed)))
}
+10 -1
View File
@@ -30,6 +30,8 @@ import {
cardCustomLinkMatch,
openCardInSidebar,
checkRelationsSectionVisibility,
checkOldMessagesSectionVisibility,
checkCommunicationMessagesSectionVisibility,
getSpaceAccessPublicLink,
canGetSpaceAccessPublicLink,
cardFactory,
@@ -68,6 +70,9 @@ import ChildrenCardSection from './components/sections/ChildrenSection.svelte'
import ContentCardSection from './components/sections/ContentSection.svelte'
import PropertiesCardSection from './components/sections/PropertiesSection.svelte'
import RelationsCardSection from './components/sections/RelationsSection.svelte'
import CardCommunicatiomMessages from './components/sections/CardCommunicatiomMessages.svelte'
import OldMessagesCardSection from './components/sections/OldMessagesCardSection.svelte'
import FavoriteCardPresenter from './components/FavoriteCardPresenter.svelte'
import CardTagsColored from './components/CardTagsColored.svelte'
import CardTagColored from './components/CardTagColored.svelte'
@@ -137,7 +142,9 @@ export default async (): Promise<Resources> => ({
ChildrenSection: ChildrenCardSection,
ContentSection: ContentCardSection,
PropertiesSection: PropertiesCardSection,
RelationsSection: RelationsCardSection
RelationsSection: RelationsCardSection,
OldMessagesSection: OldMessagesCardSection,
CommunicationMessagesSection: CardCommunicatiomMessages
},
completion: {
CardQuery: queryCard
@@ -158,6 +165,8 @@ export default async (): Promise<Resources> => ({
CardCustomLinkEncode: cardCustomLinkEncode,
OpenCardInSidebar: openCardInSidebar,
CheckRelationsSectionVisibility: checkRelationsSectionVisibility,
CheckOldMessagesSectionVisibility: checkOldMessagesSectionVisibility,
CheckCommunicationMessagesSectionVisibility: checkCommunicationMessagesSectionVisibility,
GetSpaceAccessPublicLink: getSpaceAccessPublicLink,
CanGetSpaceAccessPublicLink: canGetSpaceAccessPublicLink,
CardFactory: cardFactory
+24 -2
View File
@@ -76,7 +76,9 @@ export default mergeIds(cardId, card, {
ChildrenSection: '' as AnyComponent,
ContentSection: '' as AnyComponent,
PropertiesSection: '' as AnyComponent,
RelationsSection: '' as AnyComponent
RelationsSection: '' as AnyComponent,
CommunicationMessagesSection: '' as AnyComponent,
OldMessagesSection: '' as AnyComponent
},
completion: {
CardQuery: '' as Resource<ObjectSearchFactory>,
@@ -135,6 +137,26 @@ export default mergeIds(cardId, card, {
EnableVersioning: '' as IntlString,
EnableVersioningConfirm: '' as IntlString,
NewVersionConfirmation: '' as IntlString,
RelationCopyDescr: '' as IntlString
RelationCopyDescr: '' as IntlString,
CreateCardPersmissionDescription: '' as IntlString,
UpdateCardPersmissionDescription: '' as IntlString,
RemoveCardPersmissionDescription: '' as IntlString,
AddTagPersmissionDescription: '' as IntlString,
RemoveTagPersmissionDescription: '' as IntlString,
RemoveCard: '' as IntlString,
UpdateCard: '' as IntlString,
CreateCardPermission: '' as IntlString,
AddTagPermission: '' as IntlString,
RemoveTag: '' as IntlString,
ForbidCreateCardPersmissionDescription: '' as IntlString,
ForbidUpdateCardPersmissionDescription: '' as IntlString,
ForbidRemoveCardPersmissionDescription: '' as IntlString,
ForbidAddTagPersmissionDescription: '' as IntlString,
ForbidRemoveTagPersmissionDescription: '' as IntlString,
ForbidRemoveCard: '' as IntlString,
ForbidUpdateCard: '' as IntlString,
ForbidCreateCardPermission: '' as IntlString,
ForbidAddTagPermission: '' as IntlString,
ForbidRemoveTag: '' as IntlString
}
})
+188 -1
View File
@@ -13,10 +13,12 @@
import { type AccountClient, getClient as getAccountClientRaw } from '@hcengineering/account-client'
import { Analytics } from '@hcengineering/analytics'
import { type Card, CardEvents, cardId, type CardSpace, type MasterTag } from '@hcengineering/card'
import communication from '@hcengineering/communication'
import { type Card, CardEvents, cardId, type CardSpace, type MasterTag, type Tag } from '@hcengineering/card'
import core, {
AccountRole,
type Class,
type ClassPermission,
type Client,
type Data,
type Doc,
@@ -97,6 +99,183 @@ export async function deleteMasterTag (tag: MasterTag | undefined, onDelete?: ()
}
}
export async function createTypePermissions (masterTag: MasterTag | Tag): Promise<void> {
const client = getClient()
const hierarchy = client.getHierarchy()
const isMixin = hierarchy.isMixin(masterTag._id)
const objectClass = hierarchy.getBaseClass(masterTag._id)
const txClass = isMixin ? core.class.TxMixin : core.class.TxUpdateDoc
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass,
txMatch: {
[isMixin ? 'mixin' : 'objectClass']: masterTag._id
},
scope: 'space',
forbid: false,
label: view.string.AllowAttributeChanges,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass,
txMatch: {
[isMixin ? 'mixin' : 'objectClass']: masterTag._id
},
scope: 'space',
forbid: true,
label: view.string.ForbidAttributeChanges,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_forbidden` as Ref<ClassPermission>
)
if (isMixin) {
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxMixin,
txMatch: {
mixin: masterTag._id
},
scope: 'space',
forbid: false,
label: card.string.AddTagPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxMixin,
txMatch: {
mixin: masterTag._id
},
scope: 'space',
forbid: true,
label: card.string.ForbidAddTagPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_forbidden` as Ref<ClassPermission>
)
const key = `operations.$unset.${masterTag._id}`
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxUpdateDoc,
txMatch: {
[key]: {
$exists: true
}
},
scope: 'space',
forbid: false,
label: card.string.RemoveTag,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxUpdateDoc,
txMatch: {
[key]: { $exists: true }
},
scope: 'space',
forbid: true,
label: card.string.ForbidRemoveTag,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_forbidden` as Ref<ClassPermission>
)
} else {
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxCreateDoc,
scope: 'space',
forbid: false,
label: card.string.CreateCardPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxCreateDoc,
scope: 'space',
forbid: true,
label: card.string.ForbidCreateCardPermission,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_create_forbidden` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxRemoveDoc,
scope: 'space',
forbid: false,
label: card.string.RemoveCard,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_allowed` as Ref<ClassPermission>
)
await client.createDoc(
core.class.ClassPermission,
core.space.Model,
{
objectClass,
txClass: core.class.TxRemoveDoc,
txMatch: {
objectClass: masterTag._id
},
scope: 'space',
forbid: true,
label: card.string.ForbidRemoveCard,
description: masterTag.label,
targetClass: masterTag._id
},
`${masterTag._id}_remove_forbidden` as Ref<ClassPermission>
)
}
}
async function cloneCard (
origin: Card,
overrideProps: Record<string, any>,
@@ -437,6 +616,14 @@ export function cardCustomLinkEncode (doc: Card): Location {
return loc
}
export async function checkOldMessagesSectionVisibility (doc: Card): Promise<boolean> {
return getMetadata(communication.metadata.Enabled) !== true
}
export async function checkCommunicationMessagesSectionVisibility (doc: Card): Promise<boolean> {
return getMetadata(communication.metadata.Enabled) === true
}
export async function checkRelationsSectionVisibility (doc: Card): Promise<boolean> {
const client = getClient()
const h = client.getHierarchy()
+3 -1
View File
@@ -211,7 +211,9 @@ const cardPlugin = plugin(cardId, {
Children: '' as Ref<CardSection>,
Content: '' as Ref<CardSection>,
Properties: '' as Ref<CardSection>,
Relations: '' as Ref<CardSection>
Relations: '' as Ref<CardSection>,
OldMessages: '' as Ref<CardSection>,
CommunicationMessages: '' as Ref<CardSection>
},
ids: {
CardWidget: '' as Ref<Doc>
+7 -1
View File
@@ -218,7 +218,13 @@ export class ProjectDocumentTree {
bundles.sort((a, b) => {
const rankA = a.ProjectMeta[0]?.rank ?? ''
const rankB = b.ProjectMeta[0]?.rank ?? ''
return rankA.localeCompare(rankB)
if (rankA !== rankB) {
return rankA.localeCompare(rankB)
} else {
const titleA = a.DocumentMeta[0]?.title ?? ''
const titleB = b.DocumentMeta[0]?.title ?? ''
return titleA.localeCompare(titleB)
}
})
for (const bundle of bundles) {
+19 -17
View File
@@ -12,18 +12,18 @@
// limitations under the License.
import { type Resources } from '@hcengineering/platform'
import ExecutionContextSelector from './components/attributeEditors/ExecutionContextSelector.svelte'
import FunctionSelector from './components/attributeEditors/FunctionSelector.svelte'
import NestedContextSelector from './components/attributeEditors/NestedContextSelector.svelte'
import RelatedContextSelector from './components/attributeEditors/RelatedContextSelector.svelte'
import ExecutionContextSelector from './components/attributeEditors/ExecutionContextSelector.svelte'
import RequestUserInput from './components/contextEditors/RequestUserInput.svelte'
import ResultInput from './components/contextEditors/ResultInput.svelte'
import RoleEditor from './components/contextEditors/RoleEditor.svelte'
import ErrorPresenter from './components/ErrorPresenter.svelte'
import ExecutionMyToDos from './components/ExecutionMyToDos.svelte'
import ExecutonPresenter from './components/ExecutonPresenter.svelte'
import ExecutonProgressPresenter from './components/ExecutonProgressPresenter.svelte'
import Main from './components/Main.svelte'
import ExecutionMyToDos from './components/ExecutionMyToDos.svelte'
import SubProcessPresenter from './components/presenters/SubProcessPresenter.svelte'
import ToDoPresenter from './components/presenters/ToDoPresenter.svelte'
import UpdateCardPresenter from './components/presenters/UpdateCardPresenter.svelte'
@@ -34,6 +34,7 @@ import ProcessPresenter from './components/ProcessPresenter.svelte'
import RunProcessCardPopup from './components/RunProcessCardPopup.svelte'
import RunProcessPopup from './components/RunProcessPopup.svelte'
import ActionsPresenter from './components/settings/ActionsPresenter.svelte'
import FunctionSubmenu from './components/settings/FunctionSubmenu.svelte'
import ProcessEditor from './components/settings/ProcessEditor.svelte'
import StatePresenter from './components/settings/StatePresenter.svelte'
import SubProcessEditor from './components/settings/SubProcessEditor.svelte'
@@ -44,11 +45,10 @@ import TransitionEditor from './components/settings/TransitionEditor.svelte'
import TriggerPresenter from './components/settings/TriggerPresenter.svelte'
import UpdateCardEditor from './components/settings/UpdateCardEditor.svelte'
import ArrayElementEditor from './components/transformEditors/ArrayElementEditor.svelte'
import MultiArrayElementEditor from './components/transformEditors/MultiArrayElementEditor.svelte'
import DateOffsetEditor from './components/transformEditors/DateOffsetEditor.svelte'
import NumberEditor from './components/transformEditors/NumberEditor.svelte'
import FilterEditor from './components/transformEditors/FilterEditor.svelte'
import FunctionSubmenu from './components/settings/FunctionSubmenu.svelte'
import MultiArrayElementEditor from './components/transformEditors/MultiArrayElementEditor.svelte'
import NumberEditor from './components/transformEditors/NumberEditor.svelte'
import ArraySizeCriteria from './components/criterias/ArraySizeCriteria.svelte'
import BaseCriteria from './components/criterias/BaseCriteria.svelte'
@@ -56,40 +56,41 @@ import RangeCriteria from './components/criterias/RangeCriteria.svelte'
import LogActionPresenter from './components/LogActionPresenter.svelte'
import NotifierExtension from './components/NotifierExtension.svelte'
import AddRelationPresenter from './components/presenters/AddRelationPresenter.svelte'
import AddTagPresenter from './components/presenters/AddTagPresenter.svelte'
import CreateCardPresenter from './components/presenters/CreateCardPresenter.svelte'
import ProcessesHeaderExtension from './components/ProcessesHeaderExtension.svelte'
import AddRelationEditor from './components/settings/AddRelationEditor.svelte'
import AddTagEditor from './components/settings/AddTagEditor.svelte'
import AddTagPresenter from './components/presenters/AddTagPresenter.svelte'
import CardUpdateEditor from './components/settings/CardUpdateEditor.svelte'
import CardUpdatePresenter from './components/settings/CardUpdatePresenter.svelte'
import CreateCardEditor from './components/settings/CreateCardEditor.svelte'
import FieldChangesEditor from './components/settings/FieldChangesEditor.svelte'
import SubProcessMatchEditor from './components/settings/SubProcessMatchEditor.svelte'
import SubProcessMatchPresenter from './components/settings/SubProcessMatchPresenter.svelte'
import TimeEditor from './components/settings/TimeEditor.svelte'
import TimePresenter from './components/settings/TimePresenter.svelte'
import ToDoSettingPresenter from './components/settings/ToDoPresenter.svelte'
import TransitionRefPresenter from './components/settings/TransitionRefPresenter.svelte'
import SubProcessMatchEditor from './components/settings/SubProcessMatchEditor.svelte'
import SubProcessMatchPresenter from './components/settings/SubProcessMatchPresenter.svelte'
import AppendEditor from './components/transformEditors/AppendEditor.svelte'
import CutEditor from './components/transformEditors/CutEditor.svelte'
import ReplaceEditor from './components/transformEditors/ReplaceEditor.svelte'
import SplitEditor from './components/transformEditors/SplitEditor.svelte'
import NumberPresenter from './components/transformPresenters/NumberPresenter.svelte'
import RolePresenter from './components/transformPresenters/RolePresenter.svelte'
import ProcessesHeaderExtension from './components/ProcessesHeaderExtension.svelte'
import { exportProcess } from './exporter'
import { ProcessMiddleware } from './middleware'
import {
checkProcessSectionVisibility,
continueExecution,
showDoneQuery,
timeTransitionCheck,
todoTranstionCheck,
matchCardCheck,
subProcessesDoneCheck,
eventCheck,
fieldChangesCheck,
matchCardCheck,
showDoneQuery,
subProcessesDoneCheck,
subProcessMatchCheck,
eventCheck
timeTransitionCheck,
todoTranstionCheck
} from './utils'
import FieldChangesEditor from './components/settings/FieldChangesEditor.svelte'
import { exportProcess } from './exporter'
export * from './query'
@@ -181,6 +182,7 @@ export default async (): Promise<Resources> => ({
function: {
ExportProcess: exportProcess,
ShowDoneQuery: showDoneQuery,
CheckProcessSectionVisibility: checkProcessSectionVisibility,
// eslint-disable-next-line @typescript-eslint/unbound-method
CreateMiddleware: ProcessMiddleware.create
}
+7
View File
@@ -749,3 +749,10 @@ export function getMockAttribute (_class: Ref<Class<Doc>>, label: IntlString, ty
label
}
}
export async function checkProcessSectionVisibility (doc: Card): Promise<boolean> {
const client = getClient()
const anc = client.getHierarchy().getAncestors(doc._class)
const processes = client.getModel().findAllSync(process.class.Process, { masterTag: { $in: anc } })
return processes.length > 0
}
+3 -2
View File
@@ -204,10 +204,10 @@ export interface UpdateCriteriaComponent extends Doc {
props: Record<string, any>
}
export * from './dslContext'
export * from './errors'
export * from './types'
export * from './utils'
export * from './dslContext'
export default plugin(processId, {
class: {
@@ -340,6 +340,7 @@ export default plugin(processId, {
ExecutionInitiator: '' as Ref<ProcessFunction>,
EmptyArray: '' as Ref<ProcessFunction>,
CurrentDate: '' as Ref<ProcessFunction>,
ExportProcess: '' as Resource<ExportFunc>
ExportProcess: '' as Resource<ExportFunc>,
CheckProcessSectionVisibility: '' as Resource<(doc: Card) => Promise<boolean>>
}
})
@@ -14,9 +14,18 @@
-->
<script lang="ts">
import { Person } from '@hcengineering/contact'
import { type Class, type Doc, type DocumentQuery, type FindOptions, type Ref } from '@hcengineering/core'
import {
getObjectValue,
type Class,
type Doc,
type DocumentQuery,
type FindOptions,
type Ref
} from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform'
import presentation, { ObjectCreate, ObjectPopup } from '@hcengineering/presentation'
import presentation, { getClient, ObjectCreate, ObjectPopup } from '@hcengineering/presentation'
import { Component } from '@hcengineering/ui'
import { findAttributePresenter } from '../utils'
import ObjectPresenter from './ObjectPresenter.svelte'
export let _class: Ref<Class<Doc>>
@@ -35,6 +44,8 @@
export let searchField: string = 'name'
export let docProps: Record<string, any> = {}
export let groupBy = '_class'
const client = getClient()
</script>
<ObjectPopup
@@ -57,6 +68,14 @@
on:close
on:changeContent
>
<svelte:fragment slot="category" let:item={it}>
{@const presenter = findAttributePresenter(client, it._class, groupBy)}
{#if presenter}
<div class="menu-group__header">
<Component is={presenter} props={{ value: getObjectValue(groupBy, it) }} />
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="item" let:item={doc}>
<div class="flex flex-grow overflow-label">
<ObjectPresenter
@@ -262,7 +262,12 @@ async function OnMasterTagCreate (ctx: TxCreateDoc<MasterTag | Tag>[], control:
attachTo: tag.extends,
variant: { $exists: false }
})
const existingViewlets = await control.findAll(control.ctx, view.class.Viewlet, {
attachTo: createTx.objectId,
variant: { $exists: false }
})
for (const viewlet of viewlets) {
if (existingViewlets.find((it) => it.descriptor === viewlet.descriptor) !== undefined) continue
const base = extractObjectData(viewlet)
res.push(
control.txFactory.createTxCreateDoc(view.class.Viewlet, core.space.Model, {
@@ -311,14 +311,30 @@ export async function getDocCollaborators (
control: TriggerControl
): Promise<AccountUuid[]> {
const collaborators = new Set<AccountUuid>()
for (const field of mixin.fields) {
const value = (doc as any)[field]
const newCollaborators = await ctx.with('getKeyCollaborators', {}, (ctx) =>
getKeyCollaborators(doc._class, value, field, control)
)
if (newCollaborators !== undefined) {
for (const newCollaborator of newCollaborators) {
collaborators.add(newCollaborator)
if (mixin.allFields !== true) {
for (const field of mixin.fields) {
const value = (doc as any)[field]
const newCollaborators = await ctx.with('getKeyCollaborators', {}, (ctx) =>
getKeyCollaborators(doc._class, value, field, control)
)
if (newCollaborators !== undefined) {
for (const newCollaborator of newCollaborators) {
collaborators.add(newCollaborator)
}
}
}
} else {
const attrs = control.hierarchy.getAllAttributes(doc._class)
// attr type will be checked into getKeyCollaborators
for (const [field] of attrs) {
const value = (doc as any)[field]
const newCollaborators = await ctx.with('getKeyCollaborators', {}, (ctx) =>
getKeyCollaborators(doc._class, value, field, control)
)
if (newCollaborators !== undefined) {
for (const newCollaborator of newCollaborators) {
collaborators.add(newCollaborator)
}
}
}
}
@@ -1137,7 +1153,7 @@ async function getNewCollaborators (
const newCollaborators = new Set<AccountUuid>()
if (ops.$push !== undefined) {
for (const key in ops.$push) {
if (mixin.fields.includes(key as any)) {
if (mixin.fields.includes(key as any) || mixin.allFields === true) {
let value = (ops.$push as any)[key]
if (typeof value !== 'string') {
value = value.$each
@@ -1153,7 +1169,7 @@ async function getNewCollaborators (
}
for (const key in ops) {
if (key.startsWith('$')) continue
if (mixin.fields.includes(key as any)) {
if (mixin.fields.includes(key as any) || mixin.allFields === true) {
const value = (ops as any)[key]
const newCollabs = await getKeyCollaborators(docClass, value, key, control)
if (newCollabs !== undefined) {
@@ -1176,7 +1192,7 @@ async function getRemovedMembers (
const removedCollaborators: AccountUuid[] = []
if (ops.$pull !== undefined && 'members' in ops.$pull) {
const key = 'members'
if (mixin.fields.includes(key as any)) {
if (mixin.fields.includes(key as any) || mixin.allFields === true) {
let value = (ops.$pull as any)[key]
if (typeof value !== 'string') {
value = value.$in