Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2024-05-08 14:41:44 +05:00 committed by GitHub
parent 820bbd1d17
commit 6967255648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
190 changed files with 1320 additions and 1708 deletions

View File

@ -51,11 +51,11 @@ import { clearTelegramHistory } from './telegram'
import { diffWorkspace, updateField } from './workspace'
import core, {
AccountRole,
getWorkspaceId,
MeasureMetricsContext,
metricsToString,
versionToString,
type AccountRole,
type Data,
type Tx,
type Version
@ -215,7 +215,7 @@ export function devTool (
}
console.log('assigning to workspace', workspaceInfo)
try {
await assignWorkspace(toolCtx, db, productId, email, workspaceInfo.workspace)
await assignWorkspace(toolCtx, db, productId, email, workspaceInfo.workspace, AccountRole.User)
} catch (err: any) {
console.error(err)
}

View File

@ -13,8 +13,8 @@
// limitations under the License.
//
import core, { TxOperations } from '@hcengineering/core'
import {
createDefaultSpace,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
@ -23,35 +23,14 @@ import {
import bitrix from './plugin'
import { bitrixId } from '@hcengineering/bitrix'
async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: bitrix.space.Mappings
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Bitrix mappings',
description: 'Bitrix mappings',
private: false,
archived: false,
members: []
},
bitrix.space.Mappings
)
}
}
export const bitrixOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, bitrixId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
await createDefaultSpace(client, bitrix.space.Mappings, { name: 'Bitrix mappings' })
}
}
])

View File

@ -38,7 +38,8 @@ import {
type Ref,
type Space,
type Timestamp,
IndexKind
IndexKind,
SortingOrder
} from '@hcengineering/core'
import {
ArrOf,
@ -205,7 +206,7 @@ const actionTemplates = template({
}
})
export function createModel (builder: Builder, options = { addApplication: true }): void {
export function createModel (builder: Builder): void {
builder.createModel(
TChunterSpace,
TChannel,
@ -345,6 +346,25 @@ export function createModel (builder: Builder, options = { addApplication: true
chunter.action.ArchiveChannel
)
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: chunter.class.Channel,
descriptor: view.viewlet.Table,
viewOptions: {
orderBy: [['modifiedOn', SortingOrder.Descending]],
groupBy: [],
other: []
},
configOptions: {
strict: true
},
config: ['', 'topic', 'private', 'archived', 'members']
},
chunter.viewlet.Channels
)
createAction(
builder,
{
@ -382,21 +402,19 @@ export function createModel (builder: Builder, options = { addApplication: true
chunter.action.ConvertToPrivate
)
if (options.addApplication) {
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: chunter.string.ApplicationLabelChunter,
icon: chunter.icon.Chunter,
alias: chunterId,
hidden: false,
component: chunter.component.Chat,
aside: chunter.component.ChatAside
},
chunter.app.Chunter
)
}
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: chunter.string.ApplicationLabelChunter,
icon: chunter.icon.Chunter,
alias: chunterId,
hidden: false,
component: chunter.component.Chat,
aside: chunter.component.ChatAside
},
chunter.app.Chunter
)
builder.mixin(activity.class.ActivityMessage, core.class.Class, view.mixin.LinkProvider, {
encode: chunter.function.GetMessageLink

View File

@ -14,17 +14,26 @@
//
import { chunterId } from '@hcengineering/chunter'
import core, { type Class, type Doc, type Domain, type Ref, TxOperations } from '@hcengineering/core'
import core, {
type Account,
TxOperations,
type Class,
type Doc,
type Domain,
type Ref,
type Space
} from '@hcengineering/core'
import {
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient,
tryMigrate,
tryUpgrade
type MigrationUpgradeClient
} from '@hcengineering/model'
import activity, { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
import notification from '@hcengineering/notification'
import contactPlugin, { type PersonAccount } from '@hcengineering/contact'
import chunter from './plugin'
export const DOMAIN_COMMENT = 'comment' as Domain
@ -59,48 +68,91 @@ export async function createDocNotifyContexts (
}
export async function createGeneral (client: MigrationUpgradeClient, tx: TxOperations): Promise<void> {
const createTx = await tx.findOne(core.class.TxCreateDoc, {
objectId: chunter.space.General
})
const current = await tx.findOne(chunter.class.Channel, { _id: chunter.space.General })
if (current !== undefined) {
if (current.autoJoin === undefined) {
await tx.update(current, {
autoJoin: true
})
await joinEmployees(current, tx)
}
} else {
const createTx = await tx.findOne(core.class.TxCreateDoc, {
objectId: chunter.space.General
})
if (createTx === undefined) {
await tx.createDoc(
chunter.class.Channel,
core.space.Space,
{
name: 'general',
description: 'General Channel',
topic: 'General Channel',
private: false,
archived: false,
members: []
},
chunter.space.General
)
if (createTx === undefined) {
await tx.createDoc(
chunter.class.Channel,
core.space.Space,
{
name: 'general',
description: 'General Channel',
topic: 'General Channel',
private: false,
archived: false,
members: await getAllEmployeeAccounts(tx),
autoJoin: true
},
chunter.space.General
)
}
}
await createDocNotifyContexts(client, tx, chunter.space.General, chunter.class.Channel)
}
export async function createRandom (client: MigrationUpgradeClient, tx: TxOperations): Promise<void> {
const createTx = await tx.findOne(core.class.TxCreateDoc, {
objectId: chunter.space.Random
async function getAllEmployeeAccounts (tx: TxOperations): Promise<Ref<PersonAccount>[]> {
const employees = await tx.findAll(contactPlugin.mixin.Employee, { active: true })
const accounts = await tx.findAll(contactPlugin.class.PersonAccount, {
person: { $in: employees.map((it) => it._id) }
})
return accounts.map((it) => it._id)
}
if (createTx === undefined) {
await tx.createDoc(
chunter.class.Channel,
core.space.Space,
{
name: 'random',
description: 'Random Talks',
topic: 'Random Talks',
private: false,
archived: false,
members: []
},
chunter.space.Random
)
async function joinEmployees (current: Space, tx: TxOperations): Promise<void> {
const accs = await getAllEmployeeAccounts(tx)
const newMembers: Ref<Account>[] = [...current.members]
for (const acc of accs) {
if (!newMembers.includes(acc)) {
newMembers.push(acc)
}
}
await tx.update(current, {
members: newMembers
})
}
export async function createRandom (client: MigrationUpgradeClient, tx: TxOperations): Promise<void> {
const current = await tx.findOne(chunter.class.Channel, { _id: chunter.space.Random })
if (current !== undefined) {
if (current.autoJoin === undefined) {
await tx.update(current, {
autoJoin: true
})
await joinEmployees(current, tx)
}
} else {
const createTx = await tx.findOne(core.class.TxCreateDoc, {
objectId: chunter.space.Random
})
if (createTx === undefined) {
await tx.createDoc(
chunter.class.Channel,
core.space.Space,
{
name: 'random',
description: 'Random Talks',
topic: 'Random Talks',
private: false,
archived: false,
members: await getAllEmployeeAccounts(tx),
autoJoin: true
},
chunter.space.Random
)
}
}
await createDocNotifyContexts(client, tx, chunter.space.Random, chunter.class.Channel)
@ -139,7 +191,7 @@ export const chunterOperation: MigrateOperation = {
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, chunterId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createGeneral(client, tx)

View File

@ -21,7 +21,7 @@ import { type NotificationGroup } from '@hcengineering/notification'
import type { IntlString, Resource } from '@hcengineering/platform'
import { mergeIds } from '@hcengineering/platform'
import type { AnyComponent, Location } from '@hcengineering/ui/src/types'
import type { Action, ActionCategory, ViewAction, ViewletDescriptor } from '@hcengineering/view'
import type { Action, ActionCategory, ViewAction, Viewlet, ViewletDescriptor } from '@hcengineering/view'
export default mergeIds(chunterId, chunter, {
component: {
@ -76,7 +76,8 @@ export default mergeIds(chunterId, chunter, {
RepliedToThread: '' as IntlString
},
viewlet: {
Chat: '' as Ref<ViewletDescriptor>
Chat: '' as Ref<ViewletDescriptor>,
Channels: '' as Ref<Viewlet>
},
ids: {
TxCommentCreate: '' as Ref<TxViewlet>,

View File

@ -27,21 +27,20 @@ import {
type GetAvatarUrl,
type Member,
type Organization,
type Organizations,
type Person,
type PersonAccount,
type Persons,
type Status
} from '@hcengineering/contact'
import {
AccountRole,
DOMAIN_MODEL,
DateRangeMode,
IndexKind,
type Class,
type Domain,
type Markup,
type Ref,
type Timestamp,
type Markup
type Timestamp
} from '@hcengineering/core'
import {
Collection,
@ -53,17 +52,18 @@ import {
ReadOnly,
TypeAttachment,
TypeBoolean,
TypeCollaborativeMarkup,
TypeDate,
TypeRef,
TypeString,
TypeTimestamp,
UX,
type Builder,
TypeCollaborativeMarkup
type Builder
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
import core, { TAccount, TAttachedDoc, TDoc, TSpace } from '@hcengineering/model-core'
import core, { TAccount, TAttachedDoc, TDoc } from '@hcengineering/model-core'
import { createPublicLinkAction } from '@hcengineering/model-guest'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import presentation from '@hcengineering/model-presentation'
import view, { createAction, type Viewlet } from '@hcengineering/model-view'
@ -75,7 +75,6 @@ import templates from '@hcengineering/templates'
import { type AnyComponent } from '@hcengineering/ui/src/types'
import { type Action } from '@hcengineering/view'
import contact from './plugin'
import { createPublicLinkAction } from '@hcengineering/model-guest'
export { contactId } from '@hcengineering/contact'
export { contactOperation } from './migration'
@ -197,14 +196,6 @@ export class TPersonAccount extends TAccount implements PersonAccount {
person!: Ref<Person>
}
@Model(contact.class.Organizations, core.class.Space)
@UX(contact.string.OrganizationsFolder, contact.icon.Company)
export class TOrganizations extends TSpace implements Organizations {}
@Model(contact.class.Persons, core.class.Space)
@UX(contact.string.PersonsFolder, contact.icon.Person)
export class TPersons extends TSpace implements Persons {}
@Model(contact.class.ContactsTab, core.class.Doc, DOMAIN_MODEL)
export class TContactsTab extends TDoc implements ContactsTab {
label!: IntlString
@ -218,9 +209,7 @@ export function createModel (builder: Builder): void {
TChannelProvider,
TContact,
TPerson,
TPersons,
TOrganization,
TOrganizations,
TEmployee,
TPersonAccount,
TChannel,
@ -311,6 +300,7 @@ export function createModel (builder: Builder): void {
label: contact.string.Contacts,
icon: contact.icon.ContactApplication,
alias: contactId,
accessLevel: AccountRole.User,
hidden: false,
// component: contact.component.ContactsTabs,
locationResolver: contact.resolver.Location,
@ -735,7 +725,8 @@ export function createModel (builder: Builder): void {
presenter: contact.component.PersonAccountPresenter
})
builder.mixin(core.class.Account, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.PersonAccountRefPresenter
presenter: contact.component.PersonAccountRefPresenter,
arrayPresenter: contact.component.AccountArrayEditor
})
builder.mixin(contact.class.Organization, core.class.Class, view.mixin.ObjectPresenter, {

View File

@ -1,57 +1,21 @@
//
import { type Class, DOMAIN_TX, type Doc, type Domain, type Ref, TxOperations } from '@hcengineering/core'
import { DOMAIN_TX, type Space, TxOperations, type Class, type Doc, type Domain, type Ref } from '@hcengineering/core'
import {
createDefaultSpace,
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient,
type ModelLogger,
tryMigrate,
tryUpgrade
type ModelLogger
} from '@hcengineering/model'
import activity, { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
import core from '@hcengineering/model-core'
import { DOMAIN_VIEW } from '@hcengineering/model-view'
import activity, { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
import contact, { DOMAIN_CONTACT, contactId } from './index'
async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: contact.space.Employee
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Employees',
description: 'Employees',
private: false,
archived: false,
members: []
},
contact.space.Employee
)
}
const contacts = await tx.findOne(core.class.Space, {
_id: contact.space.Contacts
})
if (contacts === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Contacts',
description: 'Contacts',
private: false,
archived: false,
members: []
},
contact.space.Contacts
)
}
}
async function createEmployeeEmail (client: TxOperations): Promise<void> {
const employees = await client.findAll(contact.mixin.Employee, {})
const channels = (
@ -205,16 +169,29 @@ export const contactOperation: MigrateOperation = {
}
)
}
},
{
state: 'removeEmployeeSpace',
func: async (client) => {
await client.update(
DOMAIN_CONTACT,
{
space: 'contact:space:Employee' as Ref<Space>
},
{
space: contact.space.Contacts
}
)
}
}
])
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, contactId, [
{
state: 'createSpace',
state: 'createSpace-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
await createDefaultSpace(client, contact.space.Contacts, { name: 'Contacts', description: 'Contacts' })
}
},
{

View File

@ -88,7 +88,8 @@ import {
TSpace,
TSpaceType,
TSpaceTypeDescriptor,
TTypedSpace
TTypedSpace,
TSystemSpace
} from './security'
import { TStatus, TStatusCategory, TDomainStatusPlaceholder } from './status'
import { TUserStatus } from './transient'
@ -131,6 +132,7 @@ export function createModel (builder: Builder): void {
TTxApplyIf,
TTxWorkspaceEvent,
TSpace,
TSystemSpace,
TTypedSpace,
TSpaceType,
TSpaceTypeDescriptor,

View File

@ -14,23 +14,23 @@
//
import core, {
coreId,
DOMAIN_DOC_INDEX_STATE,
DOMAIN_STATUS,
isClassIndexable,
type Status,
TxOperations,
generateId,
DOMAIN_TX,
type TxCreateDoc,
type Space
coreId,
generateId,
isClassIndexable,
type Space,
type Status,
type TxCreateDoc
} from '@hcengineering/core'
import {
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
type MigrationUpgradeClient,
createDefaultSpace,
tryMigrate,
tryUpgrade
} from '@hcengineering/model'
import { DOMAIN_SPACE } from './security'
@ -149,28 +149,14 @@ export const coreOperation: MigrateOperation = {
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, coreId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
const spaceSpace = await tx.findOne(core.class.Space, {
_id: core.space.Space
})
if (spaceSpace === undefined) {
await tx.createDoc(
core.class.TypedSpace,
core.space.Space,
{
name: 'Space for all spaces',
description: 'Spaces',
private: false,
archived: false,
members: [],
type: core.spaceType.SpacesType
},
core.space.Space
)
}
await createDefaultSpace(
client,
core.space.Space,
{ name: 'Spaces', description: 'Space for all spaces', type: core.spaceType.SpacesType },
core.class.TypedSpace
)
}
}
])

View File

@ -70,13 +70,20 @@ export class TSpace extends TDoc implements Space {
archived!: boolean
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members)
@Hidden()
members!: Arr<Ref<Account>>
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Owners)
owners?: Ref<Account>[]
@Prop(TypeBoolean(), getEmbeddedLabel('Auto-Join members'))
@Hidden() // let's hide it for now
autoJoin?: boolean
}
@Model(core.class.SystemSpace, core.class.Space)
@UX(core.string.Space, undefined, undefined, 'name')
export class TSystemSpace extends TSpace implements Space {}
@Model(core.class.TypedSpace, core.class.Space)
@UX(core.string.TypedSpace, undefined, undefined, 'name')
export class TTypedSpace extends TSpace implements TypedSpace {

View File

@ -15,7 +15,7 @@
import activity from '@hcengineering/activity'
import type { Class, CollaborativeDoc, CollectionSize, Domain, Role, RolesAssignment } from '@hcengineering/core'
import { IndexKind, Account, Ref } from '@hcengineering/core'
import { IndexKind, Account, Ref, AccountRole } from '@hcengineering/core'
import {
type Document,
type DocumentEmbedding,
@ -207,6 +207,20 @@ function defineTeamspace (builder: Builder): void {
presenter: document.component.TeamspaceSpacePresenter
})
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: document.class.Teamspace,
descriptor: view.viewlet.Table,
configOptions: {
hiddenKeys: ['name', 'description']
},
config: ['', 'members', 'private', 'archived']
},
document.viewlet.TeamspaceTable
)
// Actions
builder.mixin(document.class.Teamspace, core.class.Class, view.mixin.IgnoreActions, {
@ -451,7 +465,20 @@ function defineApplication (builder: Builder): void {
hidden: false,
locationResolver: document.resolver.Location,
navigatorModel: {
specials: [],
specials: [
{
id: 'browser',
accessLevel: AccountRole.User,
label: document.string.Teamspaces,
icon: view.icon.List,
component: workbench.component.SpecialView,
componentProps: {
_class: document.class.Teamspace,
label: document.string.Teamspaces
},
position: 'top'
}
],
spaces: [
{
id: 'teamspaces',
@ -459,7 +486,6 @@ function defineApplication (builder: Builder): void {
spaceClass: document.class.Teamspace,
addSpaceLabel: document.string.CreateTeamspace,
createComponent: document.component.CreateTeamspace,
visibleIf: document.function.IsTeamspaceVisible,
icon: document.icon.Teamspace,
// intentionally left empty in order to make space presenter working
specials: []

View File

@ -14,14 +14,13 @@
//
import { type Attachment } from '@hcengineering/attachment'
import { type Class, type Doc, type Ref, TxOperations, DOMAIN_TX, getCollaborativeDoc } from '@hcengineering/core'
import { DOMAIN_TX, getCollaborativeDoc, type Class, type Doc, type Ref } from '@hcengineering/core'
import { type Document, type Teamspace } from '@hcengineering/document'
import {
tryMigrate,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient,
tryMigrate,
tryUpgrade
type MigrationUpgradeClient
} from '@hcengineering/model'
import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
@ -29,26 +28,6 @@ import { type Asset } from '@hcengineering/platform'
import document, { documentId, DOMAIN_DOCUMENT } from './index'
async function createSpace (tx: TxOperations): Promise<void> {
const documents = await tx.findOne(core.class.Space, {
_id: document.space.Documents
})
if (documents === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Documents',
description: 'Documents',
private: false,
archived: false,
members: []
},
document.space.Documents
)
}
}
async function migrateCollaborativeContent (client: MigrationClient): Promise<void> {
const attachedFiles = await client.find<Attachment>(DOMAIN_ATTACHMENT, {
_class: 'document:class:CollaboratorDocument' as Ref<Class<Doc>>,
@ -330,15 +309,5 @@ export const documentOperation: MigrateOperation = {
])
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, documentId, [
{
state: 'u-default-project',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
}
}
])
}
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
}

View File

@ -21,7 +21,7 @@ import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineer
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
import { type TagCategory } from '@hcengineering/tags'
import { type AnyComponent } from '@hcengineering/ui'
import { type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
import { type Viewlet, type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
export default mergeIds(documentId, document, {
component: {
@ -45,6 +45,9 @@ export default mergeIds(documentId, document, {
action: {
PublicLink: '' as Ref<Action<Doc, any>>
},
viewlet: {
TeamspaceTable: '' as Ref<Viewlet>
},
category: {
Document: '' as Ref<ActionCategory>,
Other: '' as Ref<TagCategory>

View File

@ -13,41 +13,27 @@
// limitations under the License.
//
import core, { TxOperations } from '@hcengineering/core'
import { gmailId } from '@hcengineering/gmail'
import {
createDefaultSpace,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import gmail from './plugin'
import { gmailId } from '@hcengineering/gmail'
export const gmailOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, gmailId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
const current = await tx.findOne(core.class.Space, {
_id: gmail.space.Gmail
await createDefaultSpace(client, gmail.space.Gmail, {
name: 'Gmail',
description: 'Space for all gmail messages'
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Gmail',
description: 'Space for all gmail messages',
private: false,
archived: false,
members: []
},
gmail.space.Gmail
)
}
}
}
])

View File

@ -31,7 +31,7 @@ export function createModel (builder: Builder): void {
core.space.Model,
{
email: guestAccountEmail,
role: AccountRole.Guest
role: AccountRole.DocGuest
},
guest.account.Guest
)

View File

@ -1,48 +1,17 @@
import {
type Account,
AccountRole,
DOMAIN_TX,
type TxCreateDoc,
TxOperations,
type TxUpdateDoc
} from '@hcengineering/core'
import { AccountRole, DOMAIN_TX, type Account, type TxCreateDoc, type TxUpdateDoc } from '@hcengineering/core'
import { guestId } from '@hcengineering/guest'
import {
createDefaultSpace,
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient,
type ModelLogger,
tryMigrate,
tryUpgrade
type ModelLogger
} from '@hcengineering/model'
import core from '@hcengineering/model-core'
import guest from './plugin'
async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: guest.space.Links
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Links',
description: 'Space for all guest links',
private: false,
archived: false,
members: []
},
guest.space.Links
)
}
}
async function createDefaults (client: MigrationUpgradeClient): Promise<void> {
const txOp = new TxOperations(client, core.account.System)
await createSpace(txOp)
}
export const guestOperation: MigrateOperation = {
async migrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
await tryMigrate(client, guestId, [
@ -95,9 +64,12 @@ export const guestOperation: MigrateOperation = {
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, guestId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
await createDefaults(client)
await createDefaultSpace(client, guest.space.Links, {
name: 'Links',
description: 'Space for all guest links'
})
}
}
])

View File

@ -22,7 +22,8 @@ import {
IndexKind,
type Markup,
type Ref,
type Type
type Type,
AccountRole
} from '@hcengineering/core'
import {
type Department,
@ -194,6 +195,7 @@ export function createModel (builder: Builder): void {
{
label: hr.string.HRApplication,
icon: hr.icon.HR,
accessLevel: AccountRole.User,
alias: hrId,
hidden: false,
component: hr.component.Schedule

View File

@ -13,62 +13,31 @@
// limitations under the License.
//
import core, { TxOperations } from '@hcengineering/core'
import { inventoryId } from '@hcengineering/inventory'
import {
createDefaultSpace,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import inventory from './plugin'
import { inventoryId } from '@hcengineering/inventory'
async function createSpace (tx: TxOperations): Promise<void> {
const categories = await tx.findOne(core.class.Space, {
_id: inventory.space.Category
})
if (categories === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Categories',
description: 'Categories',
private: false,
archived: false,
members: []
},
inventory.space.Category
)
}
const products = await tx.findOne(core.class.Space, {
_id: inventory.space.Products
})
if (products === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Products',
description: 'Products',
private: false,
archived: false,
members: []
},
inventory.space.Products
)
}
}
export const inventoryOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, inventoryId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
await createDefaultSpace(client, inventory.space.Category, {
name: 'Categories',
description: 'Space for all inventory categories'
})
await createDefaultSpace(client, inventory.space.Products, {
name: 'Products',
description: 'Space for all inventory products'
})
}
}
])

View File

@ -15,8 +15,8 @@
// To help typescript locate view plugin properly
import activity from '@hcengineering/activity'
import { type FindOptions, SortingOrder } from '@hcengineering/core'
import { type Lead, leadId } from '@hcengineering/lead'
import { AccountRole, SortingOrder, type FindOptions } from '@hcengineering/core'
import { leadId, type Lead } from '@hcengineering/lead'
import { type Builder } from '@hcengineering/model'
import chunter from '@hcengineering/model-chunter'
import contact from '@hcengineering/model-contact'
@ -30,19 +30,17 @@ import notification from '@hcengineering/notification'
import setting from '@hcengineering/setting'
import { type ViewOptionsModel } from '@hcengineering/view'
import { TFunnel, TLead, TCustomer } from './types'
import lead from './plugin'
import { defineSpaceType } from './spaceType'
import { TCustomer, TFunnel, TLead } from './types'
export { leadId } from '@hcengineering/lead'
export { leadOperation } from './migration'
export { default } from './plugin'
export * from './types'
export * from './spaceType'
export * from './types'
export function createModel (builder: Builder): void {
const archiveId = 'archive'
builder.createModel(TFunnel, TLead, TCustomer)
builder.mixin(lead.class.Lead, core.class.Class, activity.mixin.ActivityDoc, {})
@ -116,6 +114,7 @@ export function createModel (builder: Builder): void {
label: lead.string.Customers,
icon: contact.icon.Person, // <-- Put contact general icon here.
component: workbench.component.SpecialView,
accessLevel: AccountRole.User,
componentProps: {
_class: lead.mixin.Customer,
icon: lead.icon.Lead,
@ -124,18 +123,22 @@ export function createModel (builder: Builder): void {
position: 'top'
},
{
id: archiveId,
component: workbench.component.Archive,
icon: view.icon.Archive,
label: workbench.string.Archive,
id: 'funnels',
component: workbench.component.SpecialView,
icon: view.icon.List,
label: lead.string.Funnels,
position: 'bottom',
visibleIf: workbench.function.HasArchiveSpaces,
spaceClass: lead.class.Funnel
accessLevel: AccountRole.User,
componentProps: {
_class: lead.class.Funnel,
label: lead.string.Funnels,
createComponent: lead.component.CreateFunnel,
createLabel: lead.string.CreateFunnel
}
}
],
spaces: [
{
id: 'funnels',
label: lead.string.Funnels,
spaceClass: lead.class.Funnel,
addSpaceLabel: lead.string.CreateFunnel,
@ -148,6 +151,21 @@ export function createModel (builder: Builder): void {
lead.app.Lead
)
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: lead.class.Funnel,
descriptor: view.viewlet.Table,
configOptions: {
hiddenKeys: ['identifier', 'name', 'description'],
sortable: true
},
config: ['', 'members', 'private', 'archived']
},
lead.viewlet.TableFunnel
)
createAction(builder, { ...actionTemplates.archiveSpace, target: lead.class.Funnel })
createAction(builder, { ...actionTemplates.unarchiveSpace, target: lead.class.Funnel })

View File

@ -33,8 +33,7 @@ export default mergeIds(leadId, lead, {
Title: '' as IntlString,
ManageFunnelStatuses: '' as IntlString,
GotoLeadApplication: '' as IntlString,
ConfigDescription: '' as IntlString,
EditFunnel: '' as IntlString
ConfigDescription: '' as IntlString
},
component: {
CreateLead: '' as AnyComponent,
@ -47,6 +46,7 @@ export default mergeIds(leadId, lead, {
},
viewlet: {
TableCustomer: '' as Ref<Viewlet>,
TableFunnel: '' as Ref<Viewlet>,
TableLead: '' as Ref<Viewlet>,
ListLead: '' as Ref<Viewlet>,
DashboardLead: '' as Ref<Viewlet>,

View File

@ -14,9 +14,8 @@
//
import activity, { type ActivityMessage, type DocUpdateMessage } from '@hcengineering/activity'
import core, {
import {
DOMAIN_TX,
TxOperations,
TxProcessor,
generateId,
type AttachedDoc,
@ -29,6 +28,7 @@ import core, {
type DocumentQuery
} from '@hcengineering/core'
import {
createDefaultSpace,
tryMigrate,
tryUpgrade,
type MigrateOperation,
@ -52,27 +52,6 @@ interface InboxData {
const DOMAIN_ACTIVITY = 'activity' as Domain
async function createSpace (client: MigrationUpgradeClient): Promise<void> {
const txop = new TxOperations(client, core.account.System)
const currentTemplate = await txop.findOne(core.class.Space, {
_id: notification.space.Notifications
})
if (currentTemplate === undefined) {
await txop.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Notification space',
description: 'Notification space',
private: false,
archived: false,
members: []
},
notification.space.Notifications
)
}
}
async function getActivityMessages (
client: MigrationClient,
contexts: {
@ -309,9 +288,12 @@ export const notificationOperation: MigrateOperation = {
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, notificationId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
await createSpace(client)
await createDefaultSpace(client, notification.space.Notifications, {
name: 'Notifications',
description: 'Space for all notifications'
})
}
}
])

View File

@ -13,49 +13,23 @@
// limitations under the License.
//
import { TxOperations } from '@hcengineering/core'
import {
createDefaultSpace,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import core from '@hcengineering/model-core'
import preference, { preferenceId } from '@hcengineering/preference'
async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: preference.space.Preference
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Preference',
description: 'Preference space',
private: false,
archived: false,
members: []
},
preference.space.Preference
)
}
}
async function createDefaults (tx: TxOperations): Promise<void> {
await createSpace(tx)
}
export const preferenceOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, preferenceId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
const ops = new TxOperations(client, core.account.System)
await createDefaults(ops)
await createDefaultSpace(client, preference.space.Preference, { name: 'Preference' })
}
}
])

View File

@ -14,7 +14,7 @@
//
import activity from '@hcengineering/activity'
import { type Lookup, type Ref, SortingOrder } from '@hcengineering/core'
import { AccountRole, SortingOrder, type Lookup, type Ref } from '@hcengineering/core'
import { type Builder } from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import calendar from '@hcengineering/model-calendar'
@ -26,36 +26,27 @@ import presentation from '@hcengineering/model-presentation'
import tags from '@hcengineering/model-tags'
import task, { actionTemplates } from '@hcengineering/model-task'
import tracker from '@hcengineering/model-tracker'
import view, { actionTemplates as viewTemplates, createAction, showColorsViewOption } from '@hcengineering/model-view'
import workbench, { type Application, createNavigateAction } from '@hcengineering/model-workbench'
import view, { createAction, showColorsViewOption, actionTemplates as viewTemplates } from '@hcengineering/model-view'
import workbench, { createNavigateAction, type Application } from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
import { type IntlString } from '@hcengineering/platform'
import { type Applicant, recruitId } from '@hcengineering/recruit'
import { recruitId, type Applicant } from '@hcengineering/recruit'
import setting from '@hcengineering/setting'
import { type KeyBinding, type ViewOptionModel, type ViewOptionsModel } from '@hcengineering/view'
import recruit from './plugin'
import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
import {
TApplicant,
TApplicantMatch,
TCandidate,
TCandidates,
TOpinion,
TReview,
TVacancy,
TVacancyList
} from './types'
import { defineSpaceType } from './spaceType'
import { TApplicant, TApplicantMatch, TCandidate, TOpinion, TReview, TVacancy, TVacancyList } from './types'
export { recruitId } from '@hcengineering/recruit'
export { recruitOperation } from './migration'
export { default } from './plugin'
export * from './types'
export * from './spaceType'
export * from './types'
export function createModel (builder: Builder): void {
builder.createModel(TVacancy, TCandidates, TCandidate, TApplicant, TReview, TOpinion, TVacancyList, TApplicantMatch)
builder.createModel(TVacancy, TCandidate, TApplicant, TReview, TOpinion, TVacancyList, TApplicantMatch)
builder.mixin(recruit.class.Vacancy, core.class.Class, activity.mixin.ActivityDoc, {})
builder.mixin(recruit.class.Applicant, core.class.Class, activity.mixin.ActivityDoc, {})
@ -206,6 +197,7 @@ export function createModel (builder: Builder): void {
component: workbench.component.SpecialView,
icon: recruit.icon.Talents,
label: recruit.string.Talents,
accessLevel: AccountRole.User,
componentProps: {
_class: recruit.mixin.Candidate,
icon: contact.icon.Person,
@ -219,6 +211,7 @@ export function createModel (builder: Builder): void {
{
id: skillsId,
component: recruit.component.SkillsView,
accessLevel: AccountRole.User,
icon: recruit.icon.Skills,
label: recruit.string.SkillsLabel,
createItemLabel: recruit.string.SkillCreateLabel,
@ -373,8 +366,6 @@ export function createModel (builder: Builder): void {
'comments',
'$lookup.company',
'$lookup.company.$lookup.channels',
'location',
'description',
{
key: '@applications.modifiedOn',
label: core.string.ModifiedDate

View File

@ -17,6 +17,7 @@ import { getCategories } from '@anticrm/skillset'
import core, { DOMAIN_TX, type Status, TxOperations, type Ref } from '@hcengineering/core'
import {
type ModelLogger,
createDefaultSpace,
createOrUpdate,
tryMigrate,
tryUpgrade,
@ -58,10 +59,10 @@ export const recruitOperation: MigrateOperation = {
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, recruitId, [
{
state: 'create-default-project',
state: 'create-defaults-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createDefaults(tx)
await createDefaults(client, tx)
}
},
{
@ -172,8 +173,8 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise<void>
)
}
async function createDefaults (tx: TxOperations): Promise<void> {
await createSpaces(tx)
async function createDefaults (client: MigrationUpgradeClient, tx: TxOperations): Promise<void> {
await createDefaultSpace(client, recruit.space.Reviews, { name: 'Reviews' })
await createOrUpdate(
tx,
@ -210,43 +211,3 @@ async function createDefaults (tx: TxOperations): Promise<void> {
await createSequence(tx, recruit.class.Applicant)
await createSequence(tx, recruit.class.Vacancy)
}
async function createSpaces (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: recruit.space.CandidatesPublic
})
if (current === undefined) {
await tx.createDoc(
recruit.class.Candidates,
core.space.Space,
{
name: 'public',
description: 'Public Candidates',
private: false,
members: [],
archived: false
},
recruit.space.CandidatesPublic
)
}
const currentReviews = await tx.findOne(core.class.Space, {
_id: recruit.space.Reviews
})
if (currentReviews === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Reviews',
description: 'Public reviews',
private: false,
members: [],
archived: false
},
recruit.space.Reviews
)
} else if (currentReviews.private) {
await tx.update(currentReviews, { private: false })
}
}

View File

@ -15,15 +15,15 @@
import type { Employee, Organization } from '@hcengineering/contact'
import {
type Domain,
Account,
IndexKind,
type Domain,
type Markup,
type Ref,
type Status,
type Timestamp,
type RolesAssignment,
type Role,
Account
type RolesAssignment,
type Status,
type Timestamp
} from '@hcengineering/core'
import {
Collection,
@ -45,7 +45,7 @@ import attachment from '@hcengineering/model-attachment'
import calendar, { TEvent } from '@hcengineering/model-calendar'
import chunter from '@hcengineering/model-chunter'
import contact, { TOrganization, TPerson } from '@hcengineering/model-contact'
import core, { TAttachedDoc, TSpace } from '@hcengineering/model-core'
import core, { TAttachedDoc } from '@hcengineering/model-core'
import tags from '@hcengineering/model-tags'
import task, { DOMAIN_TASK, TProject, TTask } from '@hcengineering/model-task'
import { getEmbeddedLabel } from '@hcengineering/platform'
@ -53,7 +53,6 @@ import type {
Applicant,
ApplicantMatch,
Candidate,
Candidates,
Opinion,
Review,
Vacancy,
@ -90,10 +89,6 @@ export class TVacancy extends TProject implements Vacancy {
number!: number
}
@Model(recruit.class.Candidates, core.class.Space)
@UX(recruit.string.TalentPools, recruit.icon.RecruitApplication)
export class TCandidates extends TSpace implements Candidates {}
@Mixin(recruit.mixin.Candidate, contact.class.Person)
@UX(recruit.string.Talent, recruit.icon.RecruitApplication, 'TLNT', 'name', undefined, recruit.string.Talents)
export class TCandidate extends TPerson implements Candidate {

View File

@ -48,14 +48,7 @@ export function createModel (builder: Builder): void {
component: contact.component.Avatar,
props: ['avatar', 'name']
},
title: { props: ['name'] },
scoring: [
{
attr: 'space',
value: contact.space.Employee as string,
boost: 2
}
]
title: { props: ['name'] }
},
getSearchTitle: {
name: serverContact.function.ContactNameProvider
@ -78,6 +71,24 @@ export function createModel (builder: Builder): void {
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverContact.trigger.OnEmployeeCreate,
txMatch: {
objectClass: contact.class.Person,
_class: core.class.TxMixin,
mixin: contact.mixin.Employee,
'attributes.active': true
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverContact.trigger.OnPersonAccountCreate,
txMatch: {
objectClass: contact.class.PersonAccount,
_class: core.class.TxCreateDoc
}
})
builder.mixin(
contact.templateField.CurrentEmployeeName,
templates.class.TemplateField,

View File

@ -13,45 +13,24 @@
// limitations under the License.
//
import core, { TxOperations } from '@hcengineering/core'
import {
createDefaultSpace,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import setting from './plugin'
import { settingId } from '@hcengineering/setting'
async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: setting.space.Setting
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Setting',
description: 'Setting space',
private: false,
archived: false,
members: []
},
setting.space.Setting
)
}
}
import setting from './plugin'
export const settingOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, settingId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
await createDefaultSpace(client, setting.space.Setting, { name: 'Setting' })
}
}
])

View File

@ -1,40 +1,21 @@
import core, { TxOperations } from '@hcengineering/core'
import {
createDefaultSpace,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import tags from './plugin'
import { tagsId } from '@hcengineering/tags'
import tags from './plugin'
export const tagsOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, tagsId, [
{
state: 'create-defaults',
state: 'create-defaults-v2',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
const current = await tx.findOne(core.class.Space, {
_id: tags.space.Tags
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Tags',
description: 'Space for all tags',
private: false,
archived: false,
members: []
},
tags.space.Tags
)
} else if (current.private) {
await tx.update(current, { private: false })
}
await createDefaultSpace(client, tags.space.Tags, { name: 'Tags', description: 'Space for all tags' })
}
}
])

View File

@ -13,40 +13,41 @@
// limitations under the License.
//
import activity, { type DocUpdateMessage } from '@hcengineering/activity'
import {
type Attribute,
type Space,
type Status,
type TxCreateDoc,
DOMAIN_STATUS,
DOMAIN_TX,
TxOperations,
type Attribute,
type Class,
type Doc,
type Ref,
DOMAIN_TX,
DOMAIN_STATUS,
type TxUpdateDoc,
type TxCollectionCUD
type Space,
type Status,
type TxCollectionCUD,
type TxCreateDoc,
type TxUpdateDoc
} from '@hcengineering/core'
import {
type ModelLogger,
createDefaultSpace,
createOrUpdate,
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
type MigrationUpgradeClient,
type ModelLogger
} from '@hcengineering/model'
import activity, { type DocUpdateMessage } from '@hcengineering/activity'
import { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import tags from '@hcengineering/model-tags'
import {
taskId,
type ProjectStatus,
type ProjectType,
type ProjectTypeDescriptor,
type Task,
type TaskType,
taskId,
type ProjectStatus
type TaskType
} from '@hcengineering/task'
import task from './plugin'
@ -64,49 +65,9 @@ export async function createSequence (tx: TxOperations, _class: Ref<Class<Doc>>)
}
}
async function createDefaultSequence (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: task.space.Sequence
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Sequences',
description: 'Internal space to store sequence numbers',
members: [],
private: false,
archived: false
},
task.space.Sequence
)
}
}
async function createDefaultStatesSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: task.space.Statuses
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Statuses',
description: 'Internal space to store all Statuses',
members: [],
private: false,
archived: false
},
task.space.Statuses
)
}
}
async function createDefaults (tx: TxOperations): Promise<void> {
await createDefaultSequence(tx)
await createDefaultStatesSpace(tx)
async function createDefaults (client: MigrationUpgradeClient): Promise<void> {
await createDefaultSpace(client, task.space.Sequence, { name: 'Sequences' })
await createDefaultSpace(client, task.space.Statuses, { name: 'Statuses' })
}
export async function migrateDefaultStatusesBase<T extends Task> (
@ -609,11 +570,14 @@ export const taskOperation: MigrateOperation = {
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, taskId, [
{
state: 'defaults-v2',
func: createDefaults
},
{
state: 'u-task-001',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createDefaults(tx)
await createOrUpdate(
tx,

View File

@ -13,42 +13,30 @@
// limitations under the License.
//
import core, { TxOperations } from '@hcengineering/core'
import {
createDefaultSpace,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import telegram from './plugin'
import { telegramId } from '@hcengineering/telegram'
import telegram from './plugin'
export async function createSpace (client: MigrationUpgradeClient): Promise<void> {
await createDefaultSpace(client, telegram.space.Telegram, {
name: 'Telegram',
description: 'Space for all telegram messages'
})
}
export const telegramOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, telegramId, [
{
state: 'create-defaults',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
const current = await tx.findOne(core.class.Space, {
_id: telegram.space.Telegram
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Telegram',
description: 'Space for all telegram messages',
private: false,
archived: false,
members: []
},
telegram.space.Telegram
)
}
}
state: 'defaults-v2',
func: createSpace
}
])
}

View File

@ -43,12 +43,15 @@ export const templatesOperation: MigrateOperation = {
description: 'Space for public templates',
private: false,
archived: false,
members: []
members: [],
autoJoin: true
},
templates.space.Templates
)
} else if (current.private) {
await tx.update(current, { private: false })
} else if (current.autoJoin !== true) {
await tx.update(current, { autoJoin: true })
}
}
}

View File

@ -21,7 +21,8 @@ import {
type MigrationUpgradeClient,
createOrUpdate,
tryMigrate,
tryUpgrade
tryUpgrade,
createDefaultSpace
} from '@hcengineering/model'
import { makeRank } from '@hcengineering/rank'
import core from '@hcengineering/model-core'
@ -149,26 +150,6 @@ async function migrateTodosRanks (client: TxOperations): Promise<void> {
}
}
async function createDefaultSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: time.space.ToDos
})
if (current === undefined) {
await tx.createDoc(
core.class.Space,
core.space.Space,
{
name: 'Todos',
description: 'Space for all todos',
private: false,
archived: false,
members: []
},
time.space.ToDos
)
}
}
async function fillProps (client: MigrationClient): Promise<void> {
await client.update(
DOMAIN_TIME,
@ -196,11 +177,16 @@ export const timeOperation: MigrateOperation = {
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, timeId, [
{
state: 'create-defaults-v2',
func: async (client) => {
await createDefaultSpace(client, time.space.ToDos, { name: 'Todos', description: 'Space for all todos' })
}
},
{
state: 'u-time-0001',
func: async (client) => {
const tx = new TxOperations(client, core.account.System)
await createDefaultSpace(tx)
await createOrUpdate(
tx,
tags.class.TagCategory,

View File

@ -719,9 +719,6 @@ export function createActions (builder: Builder, issuesId: string, componentsId:
},
tracker.action.DeleteMilestone
)
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.IgnoreActions, {
actions: [view.action.Open]
})
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.IgnoreActions, {
actions: [view.action.Delete]
})

View File

@ -329,14 +329,13 @@ function defineApplication (
{
id: 'all-projects',
component: workbench.component.SpecialView,
icon: view.icon.Archive,
icon: view.icon.List,
label: tracker.string.AllProjects,
position: 'bottom',
spaceClass: tracker.class.Project,
componentProps: {
_class: tracker.class.Project,
label: tracker.string.AllProjects,
icon: tracker.icon.Issues
label: tracker.string.AllProjects
}
}
],

View File

@ -114,6 +114,7 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
archived: false,
identifier: 'TSK',
sequence: 0,
autoJoin: true,
defaultIssueStatus: state._id,
defaultTimeReportDay: TimeReportDayType.PreviousWorkDay,
defaultAssignee: undefined,

View File

@ -523,37 +523,13 @@ export function defineViewlets (builder: Builder): void {
core.space.Model,
{
attachTo: tracker.class.Project,
descriptor: view.viewlet.List,
viewOptions: {
groupBy: ['createdBy'],
orderBy: [
['type', SortingOrder.Descending],
['modifiedOn', SortingOrder.Descending],
['createdOn', SortingOrder.Descending]
],
other: [showColorsViewOption]
},
descriptor: view.viewlet.Table,
configOptions: {
strict: true,
hiddenKeys: ['label', 'description']
hiddenKeys: ['identifier', 'name', 'description']
},
config: [
{
key: '',
props: { kind: 'list' }
},
{ key: '', displayProps: { grow: true } },
{
key: '',
presenter: tracker.component.MembersArrayEditor,
sortingKey: 'members',
props: { readonly: true, kind: 'list' }
},
// TODO: Need return type in future
// {
// key: 'type',
// props: { kind: 'list' }
// },
'',
'members',
{
key: 'defaultAssignee',
props: { kind: 'list' }

View File

@ -197,6 +197,7 @@ export class TArrayEditor extends TClass implements ArrayEditor {
@Mixin(view.mixin.AttributePresenter, core.class.Class)
export class TAttributePresenter extends TClass implements AttributePresenter {
presenter!: AnyComponent
arrayPresenter?: AnyComponent
}
@Mixin(view.mixin.AttributeFilterPresenter, core.class.Class)
@ -1104,6 +1105,10 @@ export function createModel (builder: Builder): void {
domain: DOMAIN_VIEW,
disabled: [{ space: 1 }, { modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }]
})
builder.mixin(core.class.Space, core.class.Class, view.mixin.IgnoreActions, {
actions: [view.action.Open, view.action.OpenInNewTab, view.action.Delete]
})
}
export default view

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import { type Class, DOMAIN_MODEL, type Ref, type Space } from '@hcengineering/core'
import { type Class, DOMAIN_MODEL, type Ref, type Space, type AccountRole } from '@hcengineering/core'
import { type Builder, Mixin, Model, Prop, TypeRef, UX } from '@hcengineering/model'
import preference, { TPreference } from '@hcengineering/model-preference'
import { createAction } from '@hcengineering/model-view'
@ -41,6 +41,7 @@ export class TApplication extends TDoc implements Application {
alias!: string
position?: 'top' | 'mid'
hidden!: boolean
accessLevel?: AccountRole
}
@Model(workbench.class.ApplicationNavModel, core.class.Doc, DOMAIN_MODEL)

View File

@ -22,12 +22,10 @@ import workbench from '@hcengineering/workbench-resources/src/plugin'
export default mergeIds(workbenchId, workbench, {
component: {
ApplicationPresenter: '' as AnyComponent,
SpecialView: '' as AnyComponent,
ServerManager: '' as AnyComponent
},
string: {
Application: '' as IntlString,
SpaceBrowser: '' as IntlString,
HiddenApplication: '' as IntlString
},
function: {

View File

@ -367,8 +367,14 @@ export interface Space extends Doc {
members: Arr<Ref<Account>>
archived: boolean
owners?: Ref<Account>[]
autoJoin?: boolean
}
/**
* @public
*/
export interface SystemSpace extends Space {}
/**
* @public
*
@ -442,6 +448,7 @@ export interface Account extends Doc {
* @public
*/
export enum AccountRole {
DocGuest = 'DocGuest',
Guest = 'GUEST',
User = 'USER',
Maintainer = 'MAINTAINER',
@ -452,6 +459,7 @@ export enum AccountRole {
* @public
*/
export const roleOrder: Record<AccountRole, number> = {
[AccountRole.DocGuest]: 0,
[AccountRole.Guest]: 1,
[AccountRole.User]: 2,
[AccountRole.Maintainer]: 3,

View File

@ -14,7 +14,7 @@
//
import type { IntlString, Plugin, StatusCode } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { Mixin, type Rank, Version } from '.'
import { Mixin, Version, type Rank } from '.'
import type {
Account,
AnyAttribute,
@ -49,6 +49,7 @@ import type {
Space,
SpaceType,
SpaceTypeDescriptor,
SystemSpace,
Timestamp,
Type,
TypeAny,
@ -102,6 +103,7 @@ export default plugin(coreId, {
TxUpdateDoc: '' as Ref<Class<TxUpdateDoc<Doc>>>,
TxRemoveDoc: '' as Ref<Class<TxRemoveDoc<Doc>>>,
Space: '' as Ref<Class<Space>>,
SystemSpace: '' as Ref<Class<SystemSpace>>,
TypedSpace: '' as Ref<Class<TypedSpace>>,
SpaceTypeDescriptor: '' as Ref<Class<SpaceTypeDescriptor>>,
SpaceType: '' as Ref<Class<SpaceType>>,

View File

@ -36,6 +36,7 @@ import {
Permission,
Ref,
Role,
roleOrder,
Space,
TypedSpace
} from './classes'
@ -773,6 +774,9 @@ export function reduceCalls<T extends (...args: ReduceParameters<T>) => Promise<
export function isOwnerOrMaintainer (): boolean {
const account = getCurrentAccount()
return [AccountRole.Owner, AccountRole.Maintainer].includes(account.role)
return hasAccountRole(account, AccountRole.Maintainer)
}
export function hasAccountRole (acc: Account, targerRole: AccountRole): boolean {
return roleOrder[acc.role] >= roleOrder[targerRole]
}

View File

@ -1,4 +1,5 @@
import core, {
Class,
Client,
DOMAIN_MIGRATION,
Data,
@ -13,6 +14,7 @@ import core, {
ObjQueryType,
PushOptions,
Ref,
Space,
TxOperations,
UnsetOptions,
generateId
@ -178,3 +180,37 @@ export async function tryUpgrade (
await tx.createDoc(core.class.MigrationState, core.space.Configuration, st)
}
}
type DefaultSpaceData<T extends Space> = Pick<T, 'description' | 'private' | 'archived' | 'members'>
type RequiredData<T extends Space> = Omit<Data<T>, keyof DefaultSpaceData<T>> & Partial<DefaultSpaceData<T>>
/**
* @public
*/
export async function createDefaultSpace<T extends Space> (
client: MigrationUpgradeClient,
_id: Ref<T>,
props: RequiredData<T>,
_class: Ref<Class<T>> = core.class.SystemSpace
): Promise<void> {
const defaults: DefaultSpaceData<T> = {
description: '',
private: false,
archived: false,
members: []
}
const data: Data<Space> = {
...defaults,
...props
}
const tx = new TxOperations(client, core.account.System)
const current = await tx.findOne(core.class.Space, {
_id
})
if (current === undefined || current._class !== _class) {
if (current !== undefined && current._class !== _class) {
await tx.remove(current)
}
await tx.createDoc(_class, core.space.Space, data, _id)
}
}

View File

@ -1367,7 +1367,7 @@ export class LiveQuery implements WithTx, Client {
private async changePrivateHandler (evt: TxWorkspaceEvent): Promise<void> {
if (evt.event === WorkspaceEvent.SecurityChange) {
for (const q of [...this.queue]) {
if (typeof q.query.space !== 'string') {
if (typeof q.query.space !== 'string' || q.query.space === evt.objectSpace) {
if (!this.removeFromQueue(q)) {
try {
await this.refresh(q)
@ -1380,7 +1380,7 @@ export class LiveQuery implements WithTx, Client {
}
for (const v of this.queries.values()) {
for (const q of v) {
if (typeof q.query.space !== 'string') {
if (typeof q.query.space !== 'string' || q.query.space === evt.objectSpace) {
try {
await this.refresh(q)
} catch (err: any) {

View File

@ -30,21 +30,21 @@
$: icon = client.getHierarchy().getClass(value._class).icon
</script>
{#if value && type === 'link'}
<NavLink app={chunterId} space={value._id}>
<div class="flex-presenter">
{#if !inline && shouldShowAvatar}
<div class="icon">
{#if icon}
<Icon {icon} size={'small'} />
{/if}
</div>
{/if}
<span class="label">{value.name}</span>
</div>
</NavLink>
{/if}
{#if value && type === 'text'}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>{value.name}</span>
{#if value}
{#if type === 'link'}
<NavLink app={chunterId} space={`${value._id}|${value._class}`}>
<div class="flex-presenter">
{#if !inline && shouldShowAvatar}
<div class="icon">
{#if icon}
<Icon {icon} size={'small'} />
{/if}
</div>
{/if}
<span class="label">{value.name}</span>
</div>
</NavLink>
{:else}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>{value.name}</span>
{/if}
{/if}

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Doc, Ref } from '@hcengineering/core'
import { AccountRole, Doc, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { Scroller, SearchEdit, Label, ButtonIcon, IconAdd, showPopup, Menu } from '@hcengineering/ui'
import { DocNotifyContext } from '@hcengineering/notification'
import { SpecialNavModel } from '@hcengineering/workbench'
@ -74,7 +74,9 @@
<span class="overflow-label">
<Label label={chunter.string.Chat} />
</span>
<ButtonIcon icon={IconAdd} kind={'primary'} size={'small'} on:click={addButtonClicked} />
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
<ButtonIcon icon={IconAdd} kind={'primary'} size={'small'} on:click={addButtonClicked} />
{/if}
</div>
{#each chatSpecials as special, row}

View File

@ -1,257 +0,0 @@
<!--
// Copyright © 2023 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 {
Class,
DocumentQuery,
FindOptions,
getCurrentAccount,
Ref,
SortingOrder,
SortingQuery,
Space
} from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { AnyComponent, Button, Icon, Label, Scroller, SearchEdit, showPopup } from '@hcengineering/ui'
import { FilterBar, FilterButton, SpacePresenter } from '@hcengineering/view-resources'
import workbench from '@hcengineering/workbench'
import { Channel } from '@hcengineering/chunter'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
import { openChannel } from '../../../navigation'
import { getObjectIcon, joinChannel, leaveChannel } from '../../../utils'
import chunter from './../../../plugin'
export let _class: Ref<Class<Channel>> = chunter.class.Channel
export let label: IntlString
export let createItemDialog: AnyComponent | undefined = undefined
export let createItemLabel: IntlString = presentation.string.Create
export let withHeader: boolean = true
export let withFilterButton: boolean = true
export let search: string = ''
const me = getCurrentAccount()._id
const channelsQuery = createQuery()
const client = getClient()
const notificationsClient = InboxNotificationsClientImpl.getClient()
const sort: SortingQuery<Space> = {
name: SortingOrder.Ascending
}
let searchQuery: DocumentQuery<Channel>
let resultQuery: DocumentQuery<Channel>
let channels: Channel[] = []
$: updateSearchQuery(search)
$: update(sort, resultQuery)
async function update (sort: SortingQuery<Channel>, resultQuery: DocumentQuery<Channel>): Promise<void> {
const options: FindOptions<Channel> = {
sort
}
channelsQuery.query(
_class,
{
...resultQuery,
private: false
},
(res) => {
channels = res
},
options
)
}
function updateSearchQuery (search: string): void {
searchQuery = search.length ? { $search: search } : {}
}
function showCreateDialog (_: Event) {
showPopup(createItemDialog as AnyComponent, {}, 'middle')
}
async function join (channel: Channel): Promise<void> {
if (channel.members.includes(me)) {
return
}
await joinChannel(channel, me)
}
async function leave (channel: Channel): Promise<void> {
if (!channel.members.includes(me)) {
return
}
await leaveChannel(channel, me)
}
async function view (channel: Channel): Promise<void> {
openChannel(channel._id, channel._class)
}
</script>
{#if withHeader}
<div class="ac-header full divide">
<div class="ac-header__wrap-title">
<span class="ac-header__title"><Label {label} /></span>
</div>
{#if createItemDialog}
<div class="mb-1 clear-mins">
<Button
label={createItemLabel}
kind={'primary'}
size={'medium'}
on:click={(ev) => {
showCreateDialog(ev)
}}
/>
</div>
{/if}
</div>
<div class="ac-header full divide search-start">
<div class="ac-header-full small-gap">
<SearchEdit
bind:value={search}
on:change={() => {
updateSearchQuery(search)
update(sort, resultQuery)
}}
/>
<!-- <ActionIcon icon={IconMoreH} size={'small'} /> -->
<div class="buttons-divider" />
{#if withFilterButton}
<FilterButton {_class} />
{/if}
</div>
</div>
{:else if withFilterButton}
<div class="ac-header full divide">
<div class="ac-header-full small-gap">
<FilterButton {_class} />
</div>
</div>
{/if}
<FilterBar {_class} query={searchQuery} space={undefined} on:change={(e) => (resultQuery = e.detail)} />
<Scroller padding={'2.5rem'}>
<div class="spaces-container">
{#each channels as channel (channel._id)}
{@const icon = getObjectIcon(channel._class)}
{@const joined = channel.members.includes(me)}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div class="item flex-between" tabindex="0">
<div class="flex-col clear-mins">
<div class="fs-title flex-row-center">
{#if icon}
<div class="icon"><Icon {icon} size={'small'} /></div>
{/if}
<SpacePresenter value={channel} />
</div>
<div class="flex-row-center">
{#if joined}
<Label label={workbench.string.Joined} />
&#183
{/if}
{channel.members.length}
&#183
{channel.description}
</div>
</div>
<div class="tools flex-row-center gap-2">
{#if joined}
<Button
size={'x-large'}
label={workbench.string.Leave}
on:click={async () => {
await leave(channel)
}}
/>
{:else}
<Button
size={'x-large'}
label={workbench.string.View}
on:click={async () => {
await view(channel)
}}
/>
<Button
size={'x-large'}
kind={'primary'}
label={workbench.string.Join}
on:click={async () => {
await join(channel)
}}
/>
{/if}
</div>
</div>
{/each}
</div>
{#if createItemDialog}
<div class="flex-center mt-10">
<Button
size={'x-large'}
kind={'primary'}
label={createItemLabel}
on:click={(ev) => {
showCreateDialog(ev)
}}
/>
</div>
{/if}
</Scroller>
<style lang="scss">
.spaces-container {
display: flex;
flex-direction: column;
border: 1px solid var(--theme-list-border-color);
border-radius: 0.25rem;
.item {
padding: 1rem 0.75rem;
color: var(--theme-caption-color);
cursor: pointer;
.icon {
margin-right: 0.375rem;
color: var(--theme-trans-color);
}
&:not(:last-child) {
border-bottom: 1px solid var(--theme-divider-color);
}
&:hover,
&:focus {
background-color: var(--highlight-hover);
.icon {
color: var(--theme-caption-color);
}
.tools {
visibility: visible;
}
}
.tools {
position: relative;
visibility: hidden;
}
}
}
</style>

View File

@ -17,7 +17,6 @@
import { FileBrowser } from '@hcengineering/attachment-resources'
import { AnySvelteComponent, Button, Scroller } from '@hcengineering/ui'
import workbench from '@hcengineering/workbench'
import { SpaceBrowser } from '@hcengineering/workbench-resources'
import contact from '@hcengineering/contact-resources/src/plugin'
import { EmployeeBrowser } from '@hcengineering/contact-resources'
import MessagesBrowser from './MessagesBrowser.svelte'
@ -28,7 +27,6 @@
import { SearchType } from '../../../utils'
import plugin from '../../../plugin'
import Header from '../../Header.svelte'
import ChannelBrowser from './ChannelBrowser.svelte'
let userSearch_: string = ''
userSearch.subscribe((v) => (userSearch_ = v))
@ -42,16 +40,6 @@
props?: Record<string, any>
}[] = [
{ searchType: SearchType.Messages, component: MessagesBrowser },
{
searchType: SearchType.Channels,
component: ChannelBrowser,
filterClass: plugin.class.Channel,
props: {
_class: plugin.class.Channel,
label: plugin.string.ChannelBrowser,
withFilterButton: false
}
},
{
searchType: SearchType.Files,
component: FileBrowser,
@ -95,16 +83,6 @@
}}
/>
</div>
<div class="ml-1 p-1 btn">
<Button
label={plugin.string.Channels}
kind="ghost"
selected={searchType === SearchType.Channels}
on:click={() => {
searchType = SearchType.Channels
}}
/>
</div>
<div class="ml-1 p-1 btn">
<Button
label={attachment.string.Files}

View File

@ -13,11 +13,21 @@
// limitations under the License.
//
import notification, { type DocNotifyContext } from '@hcengineering/notification'
import { type Class, type Doc, generateId, type Ref, SortingOrder, type WithLookup } from '@hcengineering/core'
import {
type Class,
type Doc,
generateId,
type Ref,
SortingOrder,
type WithLookup,
hasAccountRole,
getCurrentAccount,
AccountRole
} from '@hcengineering/core'
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
import { get, writable } from 'svelte/store'
import view from '@hcengineering/view'
import { type SpecialNavModel } from '@hcengineering/workbench'
import workbench, { type SpecialNavModel } from '@hcengineering/workbench'
import attachment, { type SavedAttachments } from '@hcengineering/attachment'
import activity, { type ActivityMessage } from '@hcengineering/activity'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
@ -113,6 +123,19 @@ export const chatSpecials: SpecialNavModel[] = [
icon: view.icon.Database,
component: chunter.component.ChunterBrowser,
position: 'top'
},
{
id: 'channels',
label: chunter.string.Channels,
icon: view.icon.List,
component: workbench.component.SpecialView,
componentProps: {
_class: chunter.class.Channel,
label: chunter.string.Channels,
createLabel: chunter.string.CreateChannel,
createComponent: chunter.component.CreateChannel
},
position: 'top'
}
// TODO: Should be reworked or removed
// {
@ -246,27 +269,31 @@ async function unpinAllChannels (contexts: DocNotifyContext[]): Promise<void> {
}
function getChannelsActions (): Action[] {
return [
{
icon: chunter.icon.Hashtag,
label: chunter.string.CreateChannel,
action: async (): Promise<void> => {
showPopup(chunter.component.CreateChannel, {}, 'top')
}
}
]
return hasAccountRole(getCurrentAccount(), AccountRole.User)
? [
{
icon: chunter.icon.Hashtag,
label: chunter.string.CreateChannel,
action: async (): Promise<void> => {
showPopup(chunter.component.CreateChannel, {}, 'top')
}
}
]
: []
}
function getDirectActions (): Action[] {
return [
{
label: chunter.string.NewDirectChat,
icon: chunter.icon.Thread,
action: async (): Promise<void> => {
showPopup(chunter.component.CreateDirectChat, {}, 'top')
}
}
]
return hasAccountRole(getCurrentAccount(), AccountRole.User)
? [
{
label: chunter.string.NewDirectChat,
icon: chunter.icon.Thread,
action: async (): Promise<void> => {
showPopup(chunter.component.CreateDirectChat, {}, 'top')
}
}
]
: []
}
function getActivityActions (contexts: DocNotifyContext[]): Action[] {

View File

@ -212,7 +212,6 @@ export async function ChannelTitleProvider (client: Client, id: Ref<Channel>): P
export enum SearchType {
Messages,
Channels,
Files,
Contacts
}

View File

@ -102,6 +102,7 @@
"Employees": "Employees",
"People": "People",
"For": "For",
"SelectUsers": "Select users"
"SelectUsers": "Select users",
"AddGuest": "Add guest"
}
}

View File

@ -102,6 +102,7 @@
"Employees": "Empleados",
"People": "Personas",
"For": "Para",
"SelectUsers": "Seleccionar usuarios"
"SelectUsers": "Seleccionar usuarios",
"AddGuest": "Añadir invitado"
}
}

View File

@ -102,6 +102,7 @@
"Employees": "Funcionários",
"People": "Pessoas",
"For": "Para",
"SelectUsers": "Selecionar utilizadores"
"SelectUsers": "Selecionar utilizadores",
"AddGuest": "Adicionar convidado"
}
}

View File

@ -102,6 +102,7 @@
"Employees": "Сотрудники",
"People": "Люди",
"For": "Для",
"SelectUsers": "Выберите пользователей"
"SelectUsers": "Выберите пользователей",
"AddGuest": "Добавить гостя"
}
}

View File

@ -13,12 +13,13 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee, PersonAccount } from '@hcengineering/contact'
import { Employee, PersonAccount } from '@hcengineering/contact'
import core, { Account, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import contact from '../plugin'
import { personAccountByIdStore } from '../utils'
import UserBoxList from './UserBoxList.svelte'
@ -32,6 +33,7 @@
export let includeItems: Ref<Account>[] | undefined = undefined
export let excludeItems: Ref<Account>[] | undefined = undefined
export let emptyLabel: IntlString | undefined = undefined
export let allowGuests: boolean = true
let timer: any = null
const client = getClient()
@ -104,6 +106,7 @@
</script>
<UserBoxList
_class={!allowGuests ? contact.mixin.Employee : contact.class.Person}
items={employees}
{label}
{emptyLabel}
@ -114,4 +117,5 @@
justify={'left'}
width={width ?? 'min-content'}
{kind}
create={allowGuests ? { component: contact.component.CreateGuest, label: contact.string.AddGuest } : undefined}
/>

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Channel, combineName, Employee, Person, PersonAccount } from '@hcengineering/contact'
import { Channel, combineName, Employee, PersonAccount } from '@hcengineering/contact'
import core, { AccountRole, AttachedData, Data, generateId, Ref } from '@hcengineering/core'
import login from '@hcengineering/login'
import { getResource } from '@hcengineering/platform'
@ -39,7 +39,11 @@
return firstName === '' && lastName === '' && email === ''
}
const object: Employee = {} as unknown as Employee
const person: Data<Employee> = {
name: '',
city: '',
active: true
}
const dispatch = createEventDispatcher()
const client = getClient()
@ -47,11 +51,7 @@
async function createPerson () {
changeEmail()
const name = combineName(firstName, lastName)
const person: Data<Person> = {
name,
city: object.city
}
person.name = name
person.avatar = await avatarEditor.createAvatar()
await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
@ -68,7 +68,7 @@
})
const sendInvite = await getResource(login.function.SendInvite)
await sendInvite(email.trim())
await sendInvite(email.trim(), id, AccountRole.User)
for (const channel of channels) {
await client.addCollection(contact.class.Channel, contact.space.Contacts, id, contact.class.Person, 'channels', {
@ -82,12 +82,7 @@
dispatch('close')
}
let channels: AttachedData<Channel>[] = [
{
provider: contact.channelProvider.Email,
value: ''
}
]
let channels: AttachedData<Channel>[] = []
let exists: PersonAccount | undefined
const query = createQuery()
@ -170,7 +165,7 @@
</div>
<div class="ml-4">
<EditableAvatar
avatar={object.avatar}
avatar={person.avatar}
name={combineName(firstName, lastName)}
{email}
size={'large'}

View File

@ -0,0 +1,167 @@
<!--
// Copyright © 2022 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 { Channel, combineName, Person, PersonAccount } from '@hcengineering/contact'
import core, { AccountRole, AttachedData, Data, generateId, Ref } from '@hcengineering/core'
import login from '@hcengineering/login'
import { getResource } from '@hcengineering/platform'
import { Card, createQuery, getClient } from '@hcengineering/presentation'
import { createFocusManager, EditBox, FocusHandler, IconInfo, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { ChannelsDropdown } from '..'
import contact from '../plugin'
export let canSave: boolean = true
export let onCreate: ((id: Ref<Person>) => Promise<void>) | undefined = undefined
let firstName = ''
let lastName = ''
let email = ''
const id: Ref<Person> = generateId()
export function canClose (): boolean {
return firstName === '' && lastName === '' && email === ''
}
const dispatch = createEventDispatcher()
const client = getClient()
async function createPerson () {
changeEmail()
const name = combineName(firstName, lastName)
const person: Data<Person> = {
name,
city: ''
}
await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
const mail = email.trim()
await client.createDoc(contact.class.PersonAccount, core.space.Model, {
email: mail,
person: id,
role: AccountRole.Guest
})
const sendInvite = await getResource(login.function.SendInvite)
await sendInvite(email.trim(), id, AccountRole.Guest)
for (const channel of channels) {
await client.addCollection(contact.class.Channel, contact.space.Contacts, id, contact.class.Person, 'channels', {
value: channel.value,
provider: channel.provider
})
}
if (onCreate) {
await onCreate(id)
}
dispatch('close')
}
let channels: AttachedData<Channel>[] = []
let exists: PersonAccount | undefined
const query = createQuery()
$: query.query(
contact.class.PersonAccount,
{
email: email.trim()
},
(p) => {
exists = p[0]
}
)
const manager = createFocusManager()
function changeEmail () {
const index = channels.findIndex((p) => p.provider === contact.channelProvider.Email)
if (index !== -1) {
channels[index].value = email.trim()
} else {
channels.push({
provider: contact.channelProvider.Email,
value: email.trim()
})
}
channels = channels
}
</script>
<FocusHandler {manager} />
<Card
label={contact.string.AddGuest}
okAction={createPerson}
canSave={firstName.trim().length > 0 &&
lastName.trim().length > 0 &&
email.trim().length > 0 &&
exists === undefined &&
canSave}
on:close={() => {
dispatch('close')
}}
on:changeContent
>
<svelte:fragment slot="error">
{#if exists !== undefined}
<div class="flex-row-center error-color">
<IconInfo size={'small'} />
<span class="text-sm overflow-label ml-2">
<Label label={contact.string.PersonAlreadyExists} />
</span>
</div>
{/if}
</svelte:fragment>
<div class="flex-row-center">
<div class="flex-grow flex-col">
<EditBox
placeholder={contact.string.PersonFirstNamePlaceholder}
bind:value={firstName}
kind={'large-style'}
autoFocus
focusIndex={1}
/>
<EditBox
placeholder={contact.string.PersonLastNamePlaceholder}
bind:value={lastName}
kind={'large-style'}
focusIndex={2}
/>
<div class="mt-1">
<EditBox
placeholder={contact.string.Email}
bind:value={email}
kind={'small-style'}
focusIndex={3}
on:blur={changeEmail}
/>
</div>
<slot name="extraControls" />
</div>
</div>
<svelte:fragment slot="pool">
<ChannelsDropdown
bind:value={channels}
focusIndex={10}
kind={'regular'}
size={'large'}
editable
restricted={[contact.channelProvider.Email]}
/>
</svelte:fragment>
</Card>

View File

@ -23,7 +23,7 @@
getFirstName,
getLastName
} from '@hcengineering/contact'
import { AccountRole, Ref, getCurrentAccount, roleOrder } from '@hcengineering/core'
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { AttributeEditor, createQuery, getClient } from '@hcengineering/presentation'
import setting, { IntegrationType } from '@hcengineering/setting'
import { EditBox, FocusHandler, Scroller, createFocusManager } from '@hcengineering/ui'
@ -46,7 +46,7 @@
let avatarEditor: EditableAvatar
$: owner = account.person === object._id
$: editable = !readonly && (roleOrder[account.role] >= roleOrder[AccountRole.Maintainer] || owner)
$: editable = !readonly && (hasAccountRole(account, AccountRole.Maintainer) || owner)
let firstName = getFirstName(object.name)
let lastName = getLastName(object.name)

View File

@ -13,16 +13,16 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Person, PersonAccount, Employee } from '@hcengineering/contact'
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
import {
Account,
AccountRole,
DocumentQuery,
getCurrentAccount,
Ref,
roleOrder,
SortingOrder,
Space
Space,
getCurrentAccount,
hasAccountRole
} from '@hcengineering/core'
import { translate } from '@hcengineering/platform'
import presentation, { getClient } from '@hcengineering/presentation'
@ -86,7 +86,7 @@
}
const account = getCurrentAccount()
$: canRemove = roleOrder[account.role] >= roleOrder[AccountRole.Maintainer] && space.createdBy === account._id
$: canRemove = hasAccountRole(account, AccountRole.Maintainer) || space.createdBy === account._id
</script>
<div class="flex-row-reverse mb-3 mt-3"><SearchEdit bind:value={search} /></div>

View File

@ -13,15 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee } from '@hcengineering/contact'
import contact, { Employee, Person } from '@hcengineering/contact'
import type { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { ObjectCreate, getClient } from '@hcengineering/presentation'
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@hcengineering/ui'
import { Button, Label, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../plugin'
import { employeeByIdStore } from '../utils'
import { personByIdStore } from '../utils'
import CombineAvatars from './CombineAvatars.svelte'
import UserInfo from './UserInfo.svelte'
import UsersPopup from './UsersPopup.svelte'
@ -39,22 +39,29 @@
export let labelDirection: TooltipAlignment | undefined = undefined
export let emptyLabel: IntlString = plugin.string.Members
export let readonly: boolean = false
export let create: ObjectCreate | undefined = undefined
function filter (items: Ref<Employee>[]): Ref<Employee>[] {
return items.filter((it, idx, arr) => arr.indexOf(it) === idx)
}
let persons: Employee[] = filter(items)
.map((p) => $employeeByIdStore.get(p))
let persons: Person[] = filter(items)
.map((p) => $personByIdStore.get(p))
.filter((p) => p !== undefined) as Employee[]
$: persons = filter(items)
.map((p) => $employeeByIdStore.get(p))
.map((p) => $personByIdStore.get(p))
.filter((p) => p !== undefined) as Employee[]
const dispatch = createEventDispatcher()
const client = getClient()
async function addPerson (evt: Event): Promise<void> {
const accounts = new Set(
client
.getModel()
.findAllSync(contact.class.PersonAccount, {})
.map((p) => p.person)
)
showPopup(
UsersPopup,
{
@ -71,8 +78,10 @@
const isSelected = items.some((selectedItem) => selectedItem === it._id)
return isActive || isSelected
}
return accounts.has(it._id as Ref<Person>)
},
readonly
readonly,
create
},
evt.target as HTMLElement,
undefined,

View File

@ -106,6 +106,7 @@ import IconAddMember from './components/icons/AddMember.svelte'
import UserDetails from './components/UserDetails.svelte'
import EditOrganizationPanel from './components/EditOrganizationPanel.svelte'
import ChannelIcon from './components/ChannelIcon.svelte'
import CreateGuest from './components/CreateGuest.svelte'
import contact from './plugin'
import {
@ -294,6 +295,7 @@ export default async (): Promise<Resources> => ({
NameChangedActivityMessage
},
component: {
CreateGuest,
ContactArrayEditor,
PersonEditor,
OrganizationEditor,

View File

@ -79,7 +79,8 @@ export default mergeIds(contactId, contact, {
DeleteEmployee: '' as IntlString,
DeleteEmployeeDescr: '' as IntlString,
HasMessagesIn: '' as IntlString,
HasNewMessagesIn: '' as IntlString
HasNewMessagesIn: '' as IntlString,
AddGuest: '' as IntlString
},
function: {
GetContactLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,

View File

@ -21,16 +21,6 @@ import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates'
import type { AnyComponent, IconSize, ResolvedLocation } from '@hcengineering/ui'
import { Action, FilterMode, Viewlet } from '@hcengineering/view'
/**
* @public
*/
export interface Organizations extends Space {}
/**
* @public
*/
export interface Persons extends Space {}
/**
* @public
*/
@ -174,10 +164,8 @@ export const contactPlugin = plugin(contactId, {
Channel: '' as Ref<Class<Channel>>,
Contact: '' as Ref<Class<Contact>>,
Person: '' as Ref<Class<Person>>,
Persons: '' as Ref<Class<Persons>>,
Member: '' as Ref<Class<Member>>,
Organization: '' as Ref<Class<Organization>>,
Organizations: '' as Ref<Class<Organizations>>,
PersonAccount: '' as Ref<Class<PersonAccount>>,
Status: '' as Ref<Class<Status>>,
ContactsTab: '' as Ref<Class<ContactsTab>>
@ -199,7 +187,8 @@ export const contactPlugin = plugin(contactId, {
AccountArrayEditor: '' as AnyComponent,
PersonIcon: '' as AnyComponent,
EditOrganizationPanel: '' as AnyComponent,
CollaborationUserAvatar: '' as AnyComponent
CollaborationUserAvatar: '' as AnyComponent,
CreateGuest: '' as AnyComponent
},
channelProvider: {
Email: '' as Ref<ChannelProvider>,
@ -251,7 +240,6 @@ export const contactPlugin = plugin(contactId, {
KickUser: '' as Asset
},
space: {
Employee: '' as Ref<Space>,
Contacts: '' as Ref<Space>
},
app: {

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref, Space } from '@hcengineering/core'
import { AccountRole, Ref, Space, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import {
Button,
@ -72,6 +72,13 @@
await newTeamspace()
}
}
const dropdownItems = hasAccountRole(getCurrentAccount(), AccountRole.User)
? [
{ id: document.string.CreateDocument, label: document.string.CreateDocument },
{ id: document.string.CreateTeamspace, label: document.string.CreateTeamspace }
]
: [{ id: document.string.CreateDocument, label: document.string.CreateDocument }]
</script>
{#if loading}
@ -87,10 +94,7 @@
on:click={newDocument}
mainButtonId={'new-document'}
dropdownIcon={IconDropdown}
dropdownItems={[
{ id: document.string.CreateDocument, label: document.string.CreateDocument },
{ id: document.string.CreateTeamspace, label: document.string.CreateTeamspace }
]}
{dropdownItems}
on:dropdown-selected={(ev) => {
void dropdownItemSelected(ev.detail)
}}

View File

@ -14,36 +14,35 @@
//
import {
generateId,
type Class,
type Client,
type DocumentQuery,
type Ref,
type RelatedDocument,
type WithLookup,
generateId,
getCurrentAccount
type WithLookup
} from '@hcengineering/core'
import { type Document, type Teamspace } from '@hcengineering/document'
import { type Resources } from '@hcengineering/platform'
import preference from '@hcengineering/preference'
import { type ObjectSearchResult, getClient } from '@hcengineering/presentation'
import { getClient, type ObjectSearchResult } from '@hcengineering/presentation'
import { showPopup } from '@hcengineering/ui'
import { openDoc } from '@hcengineering/view-resources'
import CreateDocument from './components/CreateDocument.svelte'
import DocumentPresenter from './components/DocumentPresenter.svelte'
import Documents from './components/Documents.svelte'
import MyDocuments from './components/MyDocuments.svelte'
import EditDoc from './components/EditDoc.svelte'
import DocumentItem from './components/DocumentItem.svelte'
import NewDocumentHeader from './components/NewDocumentHeader.svelte'
import CreateTeamspace from './components/teamspace/CreateTeamspace.svelte'
import TeamspaceSpacePresenter from './components/navigator/TeamspaceSpacePresenter.svelte'
import DocumentSearchIcon from './components/DocumentSearchIcon.svelte'
import NotificationDocumentPresenter from './components/NotificationDocumentPresenter.svelte'
import Move from './components/Move.svelte'
import DocumentToDoPresenter from './components/DocumentToDoPresenter.svelte'
import DocumentIcon from './components/DocumentIcon.svelte'
import DocumentItem from './components/DocumentItem.svelte'
import DocumentPresenter from './components/DocumentPresenter.svelte'
import DocumentSearchIcon from './components/DocumentSearchIcon.svelte'
import DocumentToDoPresenter from './components/DocumentToDoPresenter.svelte'
import Documents from './components/Documents.svelte'
import EditDoc from './components/EditDoc.svelte'
import Move from './components/Move.svelte'
import MyDocuments from './components/MyDocuments.svelte'
import NewDocumentHeader from './components/NewDocumentHeader.svelte'
import NotificationDocumentPresenter from './components/NotificationDocumentPresenter.svelte'
import TeamspaceSpacePresenter from './components/navigator/TeamspaceSpacePresenter.svelte'
import CreateTeamspace from './components/teamspace/CreateTeamspace.svelte'
import document from './plugin'
import { createEmptyDocument, documentTitleProvider, getDocumentLink, getDocumentUrl, resolveLocation } from './utils'
@ -154,7 +153,6 @@ export default async (): Promise<Resources> => ({
function: {
GetDocumentLink: getDocumentUrl,
GetObjectLinkFragment: getDocumentLink,
IsTeamspaceVisible: async (space: Teamspace) => !space.private || space.members.includes(getCurrentAccount()._id),
DocumentTitleProvider: documentTitleProvider
},
resolver: {

View File

@ -14,8 +14,8 @@
//
import { type Client, type Doc, type Ref } from '@hcengineering/core'
import document, { type Teamspace, documentId } from '@hcengineering/document'
import { type IntlString, type Resource, mergeIds } from '@hcengineering/platform'
import document, { documentId } from '@hcengineering/document'
import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform'
import { type AnyComponent, type Location } from '@hcengineering/ui'
export default mergeIds(documentId, document, {
@ -29,8 +29,7 @@ export default mergeIds(documentId, document, {
function: {
DocumentTitleProvider: '' as Resource<<T extends Doc>(client: Client, ref: Ref<T>, doc?: T) => Promise<string>>,
GetDocumentLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
GetObjectLinkFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
IsTeamspaceVisible: '' as Resource<(space: Teamspace) => Promise<boolean>>
GetObjectLinkFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>
},
string: {
DocumentNamePlaceholder: '' as IntlString,

View File

@ -107,6 +107,7 @@ export default plugin(gmailId, {
EmailNotification: '' as Ref<NotificationType>
},
space: {
// todo remove, should be in contact
Gmail: '' as Ref<Space>
},
metadata: {

View File

@ -15,10 +15,10 @@
import type { Contact, Employee, PersonAccount } from '@hcengineering/contact'
import type { Arr, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Type } from '@hcengineering/core'
import { NotificationType } from '@hcengineering/notification'
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { Viewlet } from '@hcengineering/view'
import { NotificationType } from '@hcengineering/notification'
/**
* @public

View File

@ -33,6 +33,7 @@
import { createEventDispatcher } from 'svelte'
import leadRes from '../plugin'
import view from '@hcengineering/view'
export let funnel: Funnel | undefined = undefined
const dispatch = createEventDispatcher()
@ -163,7 +164,7 @@
</script>
<SpaceCreateCard
label={leadRes.string.CreateFunnel}
label={funnel ? view.string.EdifFunnel : leadRes.string.CreateFunnel}
okAction={save}
okLabel={!isNew ? ui.string.Save : undefined}
{canSave}

View File

@ -16,7 +16,17 @@
<script lang="ts">
import contact, { Contact } from '@hcengineering/contact'
import { UserBox } from '@hcengineering/contact-resources'
import { AttachedData, AttachedDoc, generateId, Ref, SortingOrder, Status as TaskStatus } from '@hcengineering/core'
import {
AccountRole,
AttachedData,
AttachedDoc,
generateId,
getCurrentAccount,
hasAccountRole,
Ref,
SortingOrder,
Status as TaskStatus
} from '@hcengineering/core'
import type { Customer, Funnel, Lead } from '@hcengineering/lead'
import { OK, Status } from '@hcengineering/platform'
import { Card, createQuery, getClient, InlineAttributeBar, SpaceSelector } from '@hcengineering/presentation'
@ -140,10 +150,12 @@
kind={'regular'}
size={'large'}
bind:space={_space}
create={{
component: lead.component.CreateFunnel,
label: lead.string.CreateFunnel
}}
create={hasAccountRole(getCurrentAccount(), AccountRole.User)
? {
component: lead.component.CreateFunnel,
label: lead.string.CreateFunnel
}
: undefined}
/>
<TaskKindSelector projectType={funnel?.type} bind:value={kind} baseClass={lead.class.Lead} />
</svelte:fragment>

View File

@ -44,7 +44,8 @@ export default mergeIds(leadId, lead, {
Assignee: '' as IntlString,
UnAssign: '' as IntlString,
FunnelMembers: '' as IntlString,
RoleLabel: '' as IntlString
RoleLabel: '' as IntlString,
EditFunnel: '' as IntlString
},
component: {
CreateCustomer: '' as AnyComponent,

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { AccountRole, getCurrentAccount, roleOrder, Timestamp } from '@hcengineering/core'
import { AccountRole, getCurrentAccount, hasAccountRole, Timestamp } from '@hcengineering/core'
import { loginId } from '@hcengineering/login'
import { getMetadata } from '@hcengineering/platform'
import presentation, { copyTextToClipboard, createQuery } from '@hcengineering/presentation'
@ -22,18 +22,21 @@
Button,
EditBox,
getCurrentLocation,
Grid,
Label,
Loading,
locationToUrl,
MiniToggle,
ticker,
Grid
ticker
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import login from '../plugin'
import { getInviteLink } from '../utils'
import InviteWorkspace from './icons/InviteWorkspace.svelte'
export let role: AccountRole = AccountRole.User
export let ignoreSettings: boolean = false
const dispatch = createEventDispatcher()
const query = createQuery()
@ -44,22 +47,23 @@
limit: number | undefined
}
$: query.query(setting.class.InviteSettings, {}, (set) => {
if (set !== undefined && set.length > 0) {
expHours = set[0].expirationTime
emailMask = set[0].emailMask
limit = set[0].limit
} else {
expHours = 48
limit = -1
}
if (limit === -1) noLimit = true
defaultValues = {
expirationTime: expHours,
emailMask,
limit
}
})
$: !ignoreSettings &&
query.query(setting.class.InviteSettings, {}, (set) => {
if (set !== undefined && set.length > 0) {
expHours = set[0].expirationTime
emailMask = set[0].emailMask
limit = set[0].limit
} else {
expHours = 48
limit = -1
}
if (limit === -1) noLimit = true
defaultValues = {
expirationTime: expHours,
emailMask,
limit
}
})
function setToDefault (): void {
expHours = defaultValues.expirationTime
@ -67,9 +71,9 @@
limit = defaultValues.limit
}
async function getLink (expHours: number, mask: string, limit: number | undefined): Promise<void> {
async function getLink (expHours: number, mask: string, limit: number | undefined, role: AccountRole): Promise<void> {
loading = true
const inviteId = await getInviteLink(expHours, mask, limit)
const inviteId = await getInviteLink(expHours, mask, limit, role)
const loc = getCurrentLocation()
loc.path[0] = loginId
loc.path[1] = 'join'
@ -103,14 +107,14 @@
copiedTime = Date.now()
}
let expHours: number = 1
let expHours: number = 48
let emailMask: string = ''
let limit: number | undefined = undefined
let useDefault: boolean | undefined = true
let noLimit: boolean = false
const isOwnerOrMaintainer: boolean = roleOrder[getCurrentAccount().role] > roleOrder[AccountRole.Maintainer]
const isOwnerOrMaintainer: boolean = hasAccountRole(getCurrentAccount(), AccountRole.Maintainer)
let defaultValues: InviteParams = {
expirationTime: 1,
expirationTime: 48,
emailMask: '',
limit: undefined
}
@ -124,7 +128,7 @@
<Label label={login.string.InviteDescription} />
<InviteWorkspace size={'large'} />
</div>
{#if isOwnerOrMaintainer}
{#if isOwnerOrMaintainer && !ignoreSettings}
<Grid column={1} rowGap={1.5}>
<MiniToggle
bind:on={useDefault}
@ -183,7 +187,7 @@
size={'medium'}
kind={'primary'}
on:click={() => {
;((limit !== undefined && limit > 0) || noLimit) && getLink(expHours, emailMask, limit)
;((limit !== undefined && limit > 0) || noLimit) && getLink(expHours, emailMask, limit, role)
}}
/>
</div>

View File

@ -14,7 +14,7 @@
//
import { Analytics } from '@hcengineering/analytics'
import { concatLink } from '@hcengineering/core'
import { AccountRole, type Doc, type Ref, concatLink } from '@hcengineering/core'
import login, { type LoginInfo, type Workspace, type WorkspaceLoginInfo } from '@hcengineering/login'
import {
OK,
@ -526,7 +526,13 @@ export async function checkJoined (inviteId: string): Promise<[Status, Workspace
}
}
export async function getInviteLink (expHours: number = 1, emailMask: string = '', limit: number = -1): Promise<string> {
export async function getInviteLink (
expHours: number,
emailMask: string,
limit: number,
role: AccountRole = AccountRole.User,
personId?: Ref<Doc>
): Promise<string> {
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
const exp = expHours * 1000 * 60 * 60
@ -544,9 +550,14 @@ export async function getInviteLink (expHours: number = 1, emailMask: string = '
return ''
}
const params = [exp, emailMask, limit, role]
if (personId !== undefined) {
params.push(personId)
}
const request = {
method: 'getInviteLink',
params: [exp, emailMask, limit]
params
}
const response = await fetch(accountsUrl, {
@ -724,7 +735,7 @@ export async function leaveWorkspace (email: string): Promise<void> {
})
}
export async function sendInvite (email: string): Promise<void> {
export async function sendInvite (email: string, personId?: Ref<Doc>, role?: AccountRole): Promise<void> {
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
if (accountsUrl === undefined) {
@ -740,9 +751,11 @@ export async function sendInvite (email: string): Promise<void> {
}
const token = getMetadata(presentation.metadata.Token) as string
const params = [email, personId, role]
const request = {
method: 'sendInvite',
params: [email]
params
}
await fetch(accountsUrl, {

View File

@ -32,6 +32,7 @@
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/core": "^0.6.28",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/ui": "^0.6.11"
},

View File

@ -13,6 +13,7 @@
// limitations under the License.
//
import { Ref, Doc, AccountRole } from '@hcengineering/core'
import type { Asset, IntlString, Metadata, Plugin, Resource, Status } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import type { AnyComponent } from '@hcengineering/ui'
@ -77,7 +78,7 @@ export default plugin(loginId, {
InviteLimit: '' as IntlString
},
function: {
SendInvite: '' as Resource<(email: string) => Promise<void>>,
SendInvite: '' as Resource<(email: string, personId?: Ref<Doc>, role?: AccountRole) => Promise<void>>,
LeaveWorkspace: '' as Resource<(email: string) => Promise<void>>,
ChangePassword: '' as Resource<(oldPassword: string, password: string) => Promise<void>>,
SelectWorkspace: '' as Resource<(workspace: string) => Promise<[Status, WorkspaceLoginInfo | undefined]>>,

View File

@ -30,7 +30,10 @@
Space,
fillDefaults,
generateId,
Status as TaskStatus
Status as TaskStatus,
AccountRole,
getCurrentAccount,
hasAccountRole
} from '@hcengineering/core'
import { OK, Resource, Severity, Status, getResource } from '@hcengineering/platform'
import presentation, {
@ -318,10 +321,12 @@
spaceOptions={orgOptions}
readonly={preserveVacancy}
label={recruit.string.Vacancy}
create={{
component: recruit.component.CreateVacancy,
label: recruit.string.CreateVacancy
}}
create={hasAccountRole(getCurrentAccount(), AccountRole.User)
? {
component: recruit.component.CreateVacancy,
label: recruit.string.CreateVacancy
}
: undefined}
bind:value={_space}
component={VacancyOrgPresenter}
componentProps={{ inline: true }}

View File

@ -19,35 +19,34 @@
import core, {
Account,
Data,
fillDefaults,
FindResult,
generateId,
getCurrentAccount,
Ref,
Role,
RolesAssignment,
SortingOrder
SortingOrder,
fillDefaults,
generateId,
getCurrentAccount
} from '@hcengineering/core'
import { Card, createQuery, getClient, InlineAttributeBar, MessageBox } from '@hcengineering/presentation'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Card, InlineAttributeBar, MessageBox, createQuery, getClient } from '@hcengineering/presentation'
import { Vacancy as VacancyClass } from '@hcengineering/recruit'
import tags from '@hcengineering/tags'
import task, { makeRank, ProjectType } from '@hcengineering/task'
import task, { ProjectType, makeRank } from '@hcengineering/task'
import { selectedTypeStore, typeStore } from '@hcengineering/task-resources'
import tracker, { Issue, IssueStatus, IssueTemplate, IssueTemplateData, Project } from '@hcengineering/tracker'
import {
Button,
Component,
createFocusManager,
EditBox,
FocusHandler,
IconAttachment,
createFocusManager,
showPopup
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
import Company from './icons/Company.svelte'
import Vacancy from './icons/Vacancy.svelte'
import { selectedTypeStore, typeStore } from '@hcengineering/task-resources'
import { getEmbeddedLabel } from '@hcengineering/platform'
const dispatch = createEventDispatcher()
@ -59,7 +58,7 @@
let appliedTemplateId: Ref<ProjectType> | undefined
let objectId: Ref<VacancyClass> = generateId()
let issueTemplates: FindResult<IssueTemplate> = []
let issueTemplates: IssueTemplate[] = []
let fullDescription: string = ''
export let company: Ref<Organization> | undefined
@ -68,7 +67,7 @@
let vacancyData: Data<VacancyClass> = {
archived: false,
description: '',
members: [],
members: [getCurrentAccount()._id],
name: '',
number: 0,
private: false,
@ -157,13 +156,16 @@
if (taskType === undefined) {
return
}
const number = (incResult as any).object.sequence
const identifier = `${project?.identifier}-${number}`
const resId = await client.addCollection(tracker.class.Issue, space, parent, tracker.class.Issue, 'subIssues', {
title: template.title + ` (${name})`,
description: template.description,
assignee: template.assignee,
component: template.component,
milestone: template.milestone,
number: (incResult as any).object.sequence,
number,
status: project?.defaultIssueStatus as Ref<IssueStatus>,
priority: template.priority,
rank,
@ -177,7 +179,8 @@
reports: 0,
relations: [{ _id: id, _class: recruit.class.Vacancy }],
childInfo: [],
kind: taskType._id
kind: taskType._id,
identifier
})
if ((template.labels?.length ?? 0) > 0) {
const tagElements = await client.findAll(tags.class.TagElement, { _id: { $in: template.labels } })
@ -216,7 +219,7 @@
archived: false,
number: (incResult as any).object.sequence,
company,
members: [],
members: [getCurrentAccount()._id],
owners: [getCurrentAccount()._id],
type: typeId
},

View File

@ -104,7 +104,7 @@
}
function showCreateDialog () {
showPopup(CreateOrganization, { space: recruit.space.CandidatesPublic }, 'top')
showPopup(CreateOrganization, {}, 'top')
}
const applicationSorting = (a: Doc, b: Doc) =>
(applications?.get(b._id as Ref<Organization>)?.count ?? 0) -

View File

@ -13,7 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import core, { Doc, DocumentQuery, Ref, WithLookup } from '@hcengineering/core'
import core, {
AccountRole,
Doc,
DocumentQuery,
Ref,
WithLookup,
getCurrentAccount,
hasAccountRole
} from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Vacancy } from '@hcengineering/recruit'
import { Button, Component, IconAdd, Label, Loading, SearchEdit, showPopup, tableToCSV } from '@hcengineering/ui'
@ -59,7 +67,7 @@
)
function showCreateDialog () {
showPopup(CreateVacancy, { space: recruit.space.CandidatesPublic }, 'top')
showPopup(CreateVacancy, {}, 'top')
}
const applicationSorting = (a: Doc, b: Doc) =>
(applications?.get(b._id as Ref<Vacancy>)?.count ?? 0) - (applications?.get(a._id as Ref<Vacancy>)?.count ?? 0) ?? 0
@ -151,7 +159,9 @@
}}
/>
{/if}
<Button icon={IconAdd} label={recruit.string.VacancyCreateLabel} kind={'primary'} on:click={showCreateDialog} />
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
<Button icon={IconAdd} label={recruit.string.VacancyCreateLabel} kind={'primary'} on:click={showCreateDialog} />
{/if}
</div>
</div>
<div class="ac-header full divide search-start">

View File

@ -17,9 +17,9 @@
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Vacancy } from '@hcengineering/recruit'
import { Icon, getPlatformAvatarColorForTextDef, themeStore, tooltip } from '@hcengineering/ui'
import { ObjectPresenterType } from '@hcengineering/view'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import { createEventDispatcher, onMount } from 'svelte'
import { ObjectPresenterType } from '@hcengineering/view'
import recruit from '../plugin'
@ -43,14 +43,16 @@
{#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} component={recruit.component.EditVacancy} />
{:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline} component={recruit.component.EditVacancy}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
<div class="icon"><Icon icon={recruit.icon.Vacancy} size={'small'} /></div>
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
{value.name}
</span>
</div>
</DocNavLink>
<div class="flex-between flex-gap-2 w-full">
<DocNavLink {disabled} object={value} {accent} {noUnderline} component={recruit.component.EditVacancy}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
<div class="icon"><Icon icon={recruit.icon.Vacancy} size={'small'} /></div>
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
{value.name}
</span>
</div>
</DocNavLink>
</div>
{:else if type === 'text'}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{value.name}

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import { type Client, type Doc, type Ref, type Space } from '@hcengineering/core'
import { type Client, type Doc, type Ref } from '@hcengineering/core'
import type { IntlString, Resource, StatusCode } from '@hcengineering/platform'
import { mergeIds } from '@hcengineering/platform'
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/presentation'
@ -135,9 +135,6 @@ export default mergeIds(recruitId, recruit, {
GetTalentIds: '' as IntlString,
CreateNewSkills: '' as IntlString
},
space: {
CandidatesPublic: '' as Ref<Space>
},
category: {
Other: '' as Ref<TagCategory>,
Category: '' as Ref<TagCategory>

View File

@ -13,13 +13,12 @@
// limitations under the License.
//
import { Calendar } from '@hcengineering/calendar'
import type { Attribute, Class, Doc, Mixin, Ref, Status } from '@hcengineering/core'
import type { Attribute, Class, Doc, Mixin, Ref, Space, Status } from '@hcengineering/core'
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import type { ProjectTypeDescriptor, TaskType } from '@hcengineering/task'
import { AnyComponent, ResolvedLocation } from '@hcengineering/ui'
import type { Applicant, ApplicantMatch, Candidate, Candidates, Opinion, Review, Vacancy, VacancyList } from './types'
import type { Applicant, ApplicantMatch, Candidate, Opinion, Review, Vacancy, VacancyList } from './types'
export * from './types'
@ -38,7 +37,6 @@ const recruit = plugin(recruitId, {
class: {
Applicant: '' as Ref<Class<Applicant>>,
ApplicantMatch: '' as Ref<Class<ApplicantMatch>>,
Candidates: '' as Ref<Class<Candidates>>,
Vacancy: '' as Ref<Class<Vacancy>>,
Review: '' as Ref<Class<Review>>,
Opinion: '' as Ref<Class<Opinion>>
@ -82,7 +80,7 @@ const recruit = plugin(recruitId, {
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
},
space: {
Reviews: '' as Ref<Calendar>
Reviews: '' as Ref<Space>
},
taskTypes: {
Applicant: '' as Ref<TaskType>

View File

@ -15,13 +15,10 @@
import { Event } from '@hcengineering/calendar'
import type { Channel, Organization, Person } from '@hcengineering/contact'
import type { AttachedData, AttachedDoc, Markup, Ref, Space, Status, Timestamp } from '@hcengineering/core'
import type { AttachedData, AttachedDoc, Markup, Ref, Status, Timestamp } from '@hcengineering/core'
import { TagReference } from '@hcengineering/tags'
import type { Project, Task } from '@hcengineering/task'
/** @public */
export interface Candidates extends Space {}
/** @public */
export interface Vacancy extends Project {
fullDescription?: string

View File

@ -13,12 +13,12 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import contact, { PersonAccount } from '@hcengineering/contact'
import { EmployeePresenter, personByIdStore } from '@hcengineering/contact-resources'
import { AccountRole, SortingOrder, getCurrentAccount, roleOrder } from '@hcengineering/core'
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Header, Breadcrumb, Scroller } from '@hcengineering/ui'
import { Breadcrumb, DropdownIntlItem, DropdownLabelsIntl, EditBox, Header, Scroller } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import setting from '../plugin'
export let visibleNav: boolean = true
@ -73,7 +73,7 @@
</div>
<DropdownLabelsIntl
label={setting.string.Role}
disabled={roleOrder[account.role] > roleOrder[currentRole] ||
disabled={!hasAccountRole(account, currentRole) ||
(account.role === AccountRole.Owner && owners.length === 1)}
kind={'primary'}
size={'medium'}

View File

@ -14,31 +14,31 @@
-->
<script lang="ts">
import { PersonAccount } from '@hcengineering/contact'
import { AccountRole, getCurrentAccount, roleOrder } from '@hcengineering/core'
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import login, { loginId } from '@hcengineering/login'
import { setMetadata } from '@hcengineering/platform'
import presentation, { closeClient, createQuery } from '@hcengineering/presentation'
import setting, { SettingsCategory } from '@hcengineering/setting'
import {
Component,
Label,
NavGroup,
NavItem,
Scroller,
Separator,
defineSeparators,
settingsSeparators,
fetchMetadataLocalStorage,
getCurrentResolvedLocation,
navigate,
resolvedLocationStore,
setMetadataLocalStorage,
settingsSeparators,
showPopup,
Label,
NavItem,
NavGroup,
type AnyComponent
} from '@hcengineering/ui'
import { NavFooter } from '@hcengineering/workbench-resources'
import { ComponentType, onDestroy } from 'svelte'
import { settingsStore, clearSettingsStore, type SettingsStore } from '../store'
import { clearSettingsStore, settingsStore, type SettingsStore } from '../store'
export let visibleNav: boolean = true
export let navFloat: boolean = false
@ -57,7 +57,7 @@
setting.class.SettingsCategory,
{},
(res) => {
categories = roleOrder[account.role] > roleOrder[AccountRole.User] ? res : res.filter((p) => !p.secured)
categories = hasAccountRole(account, AccountRole.Maintainer) ? res : res.filter((p) => !p.secured)
category = findCategory(categoryId)
},
{ sort: { order: 1 } }

View File

@ -14,16 +14,16 @@
-->
<script lang="ts">
import { PersonAccount } from '@hcengineering/contact'
import { AccountRole, getCurrentAccount, roleOrder } from '@hcengineering/core'
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { createQuery, isAdminUser } from '@hcengineering/presentation'
import setting, { SettingsCategory } from '@hcengineering/setting'
import {
Component,
Location,
NavItem,
getCurrentResolvedLocation,
navigate,
resolvedLocationStore,
NavItem
resolvedLocationStore
} from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import { clearSettingsStore } from '../store'
@ -45,7 +45,7 @@
setting.class.WorkspaceSettingCategory,
{},
(res) => {
categories = roleOrder[account.role] > roleOrder[AccountRole.User] ? res : res.filter((p) => !p.secured)
categories = hasAccountRole(account, AccountRole.Maintainer) ? res : res.filter((p) => !p.secured)
if (!admin) {
categories = categories.filter((p) => !(p.adminOnly ?? false))
}

View File

@ -184,15 +184,6 @@ export interface ProjectTypeDescriptor extends SpaceTypeDescriptor {
editor?: AnyComponent
}
/**
* @public
*/
export enum TaskGrouping {
State = 'state',
Assignee = 'assignee',
NoGrouping = '#no_category'
}
/**
* @public
*/

View File

@ -87,6 +87,7 @@ export default plugin(telegramId, {
SharedMessages: '' as Ref<Class<SharedTelegramMessages>>
},
space: {
// todo should be removed
Telegram: '' as Ref<Space>
},
templateField: {

View File

@ -16,7 +16,6 @@
import { Ref, getCurrentAccount } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import task, { Project } from '@hcengineering/task'
import tracker, { Project as TrackerProject } from '@hcengineering/tracker'
import { Label, Separator } from '@hcengineering/ui'
import { ObjectPresenter, TreeNode } from '@hcengineering/view-resources'
import time from '../../plugin'
@ -25,36 +24,19 @@
export let appsDirection: 'vertical' | 'horizontal' = 'horizontal'
export let selected: Ref<Project> | undefined = (localStorage.getItem('team_last_mode') as Ref<Project>) ?? undefined
let memberProjects: Project[] = []
let projectsPublic: Project[] = []
let projects: Project[] = []
const projectsQuery = createQuery()
const publicQuery = createQuery()
$: projectsQuery.query(
projectsQuery.query(
task.class.Project,
{
archived: false,
members: getCurrentAccount()._id
},
(result) => {
memberProjects = result
projects = result
}
)
$: publicQuery.query(
tracker.class.Project,
{
_id: { $nin: memberProjects.map((it) => it._id as Ref<TrackerProject>) },
archived: false,
private: { $ne: true }
},
(result) => {
projectsPublic = result
}
)
$: finalProjects = memberProjects.concat(projectsPublic)
</script>
<div class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'}">
@ -64,7 +46,7 @@
<Label label={time.string.Planner} />
</div>
<TreeNode _id={'projects-planning'} label={time.string.Team} node>
{#each finalProjects as _project}
{#each projects as _project}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="antiNav-element parent"

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref, Space } from '@hcengineering/core'
import { AccountRole, Ref, Space, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { MultipleDraftController, getClient } from '@hcengineering/presentation'
import { ButtonWithDropdown, IconAdd, IconDropdown, SelectPopupValueType, showPopup } from '@hcengineering/ui'
import view from '@hcengineering/view'
@ -41,16 +41,23 @@
}
$: label = draftExists || !closed ? tracker.string.ResumeDraft : tracker.string.NewIssue
$: dropdownItems = [
{
id: tracker.string.CreateProject,
label: tracker.string.CreateProject
},
{
id: tracker.string.NewIssue,
label
}
]
$: dropdownItems = hasAccountRole(getCurrentAccount(), AccountRole.User)
? [
{
id: tracker.string.CreateProject,
label: tracker.string.CreateProject
},
{
id: tracker.string.NewIssue,
label
}
]
: [
{
id: tracker.string.NewIssue,
label
}
]
const client = getClient()
let keys: string[] | undefined = undefined

View File

@ -13,18 +13,21 @@
// limitations under the License.
-->
<script lang="ts">
import presentation from '@hcengineering/presentation'
import { Project } from '@hcengineering/tracker'
import {
Icon,
IconWithEmoji,
Label,
getPlatformColorDef,
getPlatformColorForTextDef,
themeStore,
Label
showPopup,
themeStore
} from '@hcengineering/ui'
import tracker from '../../plugin'
import view from '@hcengineering/view'
import presentation from '@hcengineering/presentation'
import { canEditSpace } from '@hcengineering/view-resources'
import { CreateProject } from '../..'
import tracker from '../../plugin'
export let value: Project | undefined
export let inline: boolean = false

View File

@ -18,13 +18,13 @@ import { Analytics } from '@hcengineering/analytics'
import core, {
AccountRole,
getCurrentAccount,
hasAccountRole,
matchQuery,
type Class,
type Client,
type Doc,
type Ref,
type WithLookup,
roleOrder
type WithLookup
} from '@hcengineering/core'
import { getResource } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
@ -172,7 +172,7 @@ export function filterActions (
): Array<WithLookup<Action>> {
let result: Array<WithLookup<Action>> = []
const hierarchy = client.getHierarchy()
const role = getCurrentAccount().role
const me = getCurrentAccount()
const clazz = hierarchy.getClass(doc._class)
const ignoreActions = hierarchy.as(clazz, view.mixin.IgnoreActions)
const ignore: Array<Ref<Action>> = getIgnoreActions(ignoreActions?.actions ?? [], doc)
@ -199,7 +199,7 @@ export function filterActions (
if (ignore.includes(action._id)) {
continue
}
if (roleOrder[role] < roleOrder[AccountRole.Maintainer] && action.secured === true) {
if (!hasAccountRole(me, AccountRole.Maintainer) && action.secured === true) {
continue
}
if (

View File

@ -33,11 +33,44 @@ import DateEditor from './components/DateEditor.svelte'
import DatePresenter from './components/DatePresenter.svelte'
import DocAttributeBar from './components/DocAttributeBar.svelte'
import DocNavLink from './components/DocNavLink.svelte'
import DocReferencePresenter from './components/DocReferencePresenter.svelte'
import EditBoxPopup from './components/EditBoxPopup.svelte'
import EditDoc from './components/EditDoc.svelte'
import EnumArrayEditor from './components/EnumArrayEditor.svelte'
import EnumEditor from './components/EnumEditor.svelte'
import EnumPresenter from './components/EnumPresenter.svelte'
import HTMLEditor from './components/HTMLEditor.svelte'
import HTMLPresenter from './components/HTMLPresenter.svelte'
import HyperlinkEditor from './components/HyperlinkEditor.svelte'
import HyperlinkEditorPopup from './components/HyperlinkEditorPopup.svelte'
import HyperlinkPresenter from './components/HyperlinkPresenter.svelte'
import IconPicker from './components/IconPicker.svelte'
import IntlStringPresenter from './components/IntlStringPresenter.svelte'
import MarkupDiffPresenter from './components/MarkupDiffPresenter.svelte'
import MarkupEditor from './components/MarkupEditor.svelte'
import MarkupEditorPopup from './components/MarkupEditorPopup.svelte'
import MarkupPresenter from './components/MarkupPresenter.svelte'
import Menu from './components/Menu.svelte'
import NumberEditor from './components/NumberEditor.svelte'
import NumberPresenter from './components/NumberPresenter.svelte'
import ObjectIcon from './components/ObjectIcon.svelte'
import ObjectMention from './components/ObjectMention.svelte'
import ObjectPresenter from './components/ObjectPresenter.svelte'
import RolePresenter from './components/RolePresenter.svelte'
import SearchSelector from './components/SearchSelector.svelte'
import SpaceHeader from './components/SpaceHeader.svelte'
import SpacePresenter from './components/SpacePresenter.svelte'
import SpaceRefPresenter from './components/SpaceRefPresenter.svelte'
import SpaceTypeSelector from './components/SpaceTypeSelector.svelte'
import StringEditor from './components/StringEditor.svelte'
import StringPresenter from './components/StringPresenter.svelte'
import Table from './components/Table.svelte'
import TableBrowser from './components/TableBrowser.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte'
import UpDownNavigator from './components/UpDownNavigator.svelte'
import ValueSelector from './components/ValueSelector.svelte'
import ViewletContentView from './components/ViewletContentView.svelte'
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
import ArrayFilter from './components/filter/ArrayFilter.svelte'
import DateFilter from './components/filter/DateFilter.svelte'
import DateFilterPresenter from './components/filter/DateFilterPresenter.svelte'
@ -48,13 +81,6 @@ import StringFilter from './components/filter/StringFilter.svelte'
import StringFilterPresenter from './components/filter/StringFilterPresenter.svelte'
import TimestampFilter from './components/filter/TimestampFilter.svelte'
import ValueFilter from './components/filter/ValueFilter.svelte'
import HTMLEditor from './components/HTMLEditor.svelte'
import HTMLPresenter from './components/HTMLPresenter.svelte'
import HyperlinkEditor from './components/HyperlinkEditor.svelte'
import HyperlinkEditorPopup from './components/HyperlinkEditorPopup.svelte'
import HyperlinkPresenter from './components/HyperlinkPresenter.svelte'
import IconPicker from './components/IconPicker.svelte'
import IntlStringPresenter from './components/IntlStringPresenter.svelte'
import GithubPresenter from './components/linkPresenters/GithubPresenter.svelte'
import YoutubePresenter from './components/linkPresenters/YoutubePresenter.svelte'
import DividerPresenter from './components/list/DividerPresenter.svelte'
@ -62,37 +88,11 @@ import GrowPresenter from './components/list/GrowPresenter.svelte'
import ListView from './components/list/ListView.svelte'
import SortableList from './components/list/SortableList.svelte'
import SortableListItem from './components/list/SortableListItem.svelte'
import MarkupDiffPresenter from './components/MarkupDiffPresenter.svelte'
import MarkupEditor from './components/MarkupEditor.svelte'
import MarkupEditorPopup from './components/MarkupEditorPopup.svelte'
import MarkupPresenter from './components/MarkupPresenter.svelte'
import Menu from './components/Menu.svelte'
import TreeElement from './components/navigator/TreeElement.svelte'
import TreeItem from './components/navigator/TreeItem.svelte'
import TreeNode from './components/navigator/TreeNode.svelte'
import NumberEditor from './components/NumberEditor.svelte'
import NumberPresenter from './components/NumberPresenter.svelte'
import ObjectMention from './components/ObjectMention.svelte'
import ObjectPresenter from './components/ObjectPresenter.svelte'
import RolePresenter from './components/RolePresenter.svelte'
import SearchSelector from './components/SearchSelector.svelte'
import SpaceHeader from './components/SpaceHeader.svelte'
import SpacePresenter from './components/SpacePresenter.svelte'
import SpaceRefPresenter from './components/SpaceRefPresenter.svelte'
import SpaceTypeSelector from './components/SpaceTypeSelector.svelte'
import StatusPresenter from './components/status/StatusPresenter.svelte'
import StatusRefPresenter from './components/status/StatusRefPresenter.svelte'
import StringEditor from './components/StringEditor.svelte'
import StringPresenter from './components/StringPresenter.svelte'
import Table from './components/Table.svelte'
import TableBrowser from './components/TableBrowser.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte'
import UpDownNavigator from './components/UpDownNavigator.svelte'
import ValueSelector from './components/ValueSelector.svelte'
import ViewletContentView from './components/ViewletContentView.svelte'
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
import DocReferencePresenter from './components/DocReferencePresenter.svelte'
import ObjectIcon from './components/ObjectIcon.svelte'
import {
afterResult,
@ -121,33 +121,34 @@ import { IndexedDocumentPreview } from '@hcengineering/presentation'
import { AggregationMiddleware, AnalyticsMiddleware } from './middleware'
import { showEmptyGroups } from './viewOptions'
import { canArchiveSpace, canDeleteObject, canDeleteSpace, canEditSpace } from './visibilityTester'
export { canArchiveSpace, canDeleteObject, canDeleteSpace, canEditSpace } from './visibilityTester'
export { getActions, getContextActions, invokeAction, showMenu } from './actions'
export { default as ActionButton } from './components/ActionButton.svelte'
export { default as ActionHandler } from './components/ActionHandler.svelte'
export { default as BaseDocPresenter } from './components/BaseDocPresenter.svelte'
export { default as FilterButton } from './components/filter/FilterButton.svelte'
export { default as FilterRemovedNotification } from './components/filter/FilterRemovedNotification.svelte'
export { default as FixedColumn } from './components/FixedColumn.svelte'
export { default as SourcePresenter } from './components/inference/SourcePresenter.svelte'
export { default as LinkPresenter } from './components/LinkPresenter.svelte'
export { default as List } from './components/list/List.svelte'
export { default as MarkupDiffPresenter } from './components/MarkupDiffPresenter.svelte'
export { default as MarkupPresenter } from './components/MarkupPresenter.svelte'
export { default as MarkupPreviewPopup } from './components/MarkupPreviewPopup.svelte'
export { default as ContextMenu } from './components/Menu.svelte'
export { default as NavLink } from './components/navigator/NavLink.svelte'
export { default as ObjectBox } from './components/ObjectBox.svelte'
export { default as ObjectBoxPopup } from './components/ObjectBoxPopup.svelte'
export { default as ObjectPresenter } from './components/ObjectPresenter.svelte'
export { default as ObjectSearchBox } from './components/ObjectSearchBox.svelte'
export { default as ParentsNavigator } from './components/ParentsNavigator.svelte'
export { default as StatusPresenter } from './components/status/StatusPresenter.svelte'
export { default as StatusRefPresenter } from './components/status/StatusRefPresenter.svelte'
export { default as SpaceTypeSelector } from './components/SpaceTypeSelector.svelte'
export { default as TableBrowser } from './components/TableBrowser.svelte'
export { default as ValueSelector } from './components/ValueSelector.svelte'
export { default as ViewletSelector } from './components/ViewletSelector.svelte'
export { default as ViewletsSettingButton } from './components/ViewletsSettingButton.svelte'
export { default as FilterButton } from './components/filter/FilterButton.svelte'
export { default as FilterRemovedNotification } from './components/filter/FilterRemovedNotification.svelte'
export { default as SourcePresenter } from './components/inference/SourcePresenter.svelte'
export { default as List } from './components/list/List.svelte'
export { default as NavLink } from './components/navigator/NavLink.svelte'
export { default as StatusPresenter } from './components/status/StatusPresenter.svelte'
export { default as StatusRefPresenter } from './components/status/StatusRefPresenter.svelte'
export * from './filter'
export * from './middleware'
@ -178,6 +179,7 @@ export {
DateEditor,
DocAttributeBar,
DocNavLink,
DocReferencePresenter,
EditBoxPopup,
EditDoc,
EnumEditor,
@ -189,6 +191,7 @@ export {
Menu,
NumberEditor,
NumberPresenter,
ObjectIcon,
ObjectMention,
SortableList,
SortableListItem,
@ -203,9 +206,7 @@ export {
TreeNode,
UpDownNavigator,
ViewletContentView,
ViewletSettingButton,
DocReferencePresenter,
ObjectIcon
ViewletSettingButton
}
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {

View File

@ -81,6 +81,7 @@ import {
type Location
} from '@hcengineering/ui'
import view, {
type AttributePresenter,
type AttributeModel,
type BuildModelKey,
type BuildModelOptions,
@ -189,7 +190,10 @@ export async function getAttributePresenter (
const isCollectionAttr = presenterClass.category === 'collection'
const mixin = isCollectionAttr ? view.mixin.CollectionPresenter : actualMixinClass
let presenterMixin = hierarchy.classHierarchyMixin(presenterClass.attrClass, mixin)
let presenterMixin: AttributePresenter | CollectionPresenter | undefined = hierarchy.classHierarchyMixin(
presenterClass.attrClass,
mixin
)
if (presenterMixin?.presenter === undefined && mixinClass != null && mixin === mixinClass) {
presenterMixin = hierarchy.classHierarchyMixin(presenterClass.attrClass, view.mixin.AttributePresenter)
@ -197,7 +201,10 @@ export async function getAttributePresenter (
let presenter: AnySvelteComponent
if (presenterMixin?.presenter !== undefined) {
const attributePresenter = presenterMixin as AttributePresenter
if (presenterClass.category === 'array' && attributePresenter.arrayPresenter !== undefined) {
presenter = await getResource(attributePresenter.arrayPresenter)
} else if (presenterMixin?.presenter !== undefined) {
presenter = await getResource(presenterMixin.presenter)
} else if (presenterClass.attrClass === core.class.TypeAny) {
const typeAny = attribute.type as TypeAny

View File

@ -181,6 +181,7 @@ export interface CollectionPresenter extends Class<Doc> {
*/
export interface AttributePresenter extends Class<Doc> {
presenter: AnyComponent
arrayPresenter?: AnyComponent
}
/**

View File

@ -10,10 +10,6 @@
"General": "General",
"Members": "Members",
"Application": "Application",
"View": "View",
"Leave": "Leave",
"Joined": "Joined",
"Join": "Join",
"BrowseSpaces": "Browse spaces",
"AccountDisabled": "Account is disabled",
"AccountDisabledDescr": "Please contact the workspace administrator",

View File

@ -10,10 +10,6 @@
"General": "General",
"Members": "Miembros",
"Application": "Aplicación",
"View": "Ver",
"Leave": "Salir",
"Joined": "Unido",
"Join": "Unirse",
"BrowseSpaces": "Explorar Espacios",
"AccountDisabled": "La cuenta está deshabilitada",
"AccountDisabledDescr": "Por favor, contacte con el administrador del espacio de trabajo",

View File

@ -10,10 +10,6 @@
"General": "Geral",
"Members": "Membros",
"Application": "Aplicação",
"View": "Visualizar",
"Leave": "Sair",
"Joined": "Ingressou",
"Join": "Ingressar",
"BrowseSpaces": "Explorar Espaços",
"AccountDisabled": "A conta está desabilitada",
"AccountDisabledDescr": "Entre em contato com o administrador do espaço de trabalho",

View File

@ -10,10 +10,6 @@
"General": "Общее",
"Members": "Участники",
"Application": "Приложение",
"View": "Посмотреть",
"Leave": "Покинуть",
"Joined": "Вы присоединились",
"Join": "Присоединиться",
"BrowseSpaces": "Обзор пространств",
"AccountDisabled": "Аккаунт отключен",
"AccountDisabledDescr": "Пожалуйста, свяжитесь с администратором",

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import contact, { Employee, PersonAccount, formatName } from '@hcengineering/contact'
import { AccountRole, Ref, getCurrentAccount, roleOrder } from '@hcengineering/core'
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import login from '@hcengineering/login'
import { createQuery } from '@hcengineering/presentation'
import setting, { SettingsCategory, settingId } from '@hcengineering/setting'
@ -41,7 +41,7 @@
setting.class.SettingsCategory,
{},
(res) => {
items = roleOrder[account.role] > roleOrder[AccountRole.User] ? res : res.filter((p) => !p.secured)
items = hasAccountRole(getCurrentAccount(), AccountRole.Maintainer) ? res : res.filter((p) => !p.secured)
},
{ sort: { order: 1 } }
)

Some files were not shown because too many files have changed in this diff Show More