mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
parent
f1d0aa54f9
commit
5e3bfacf8a
@ -47,7 +47,7 @@
|
||||
"@hcengineering/client": "^0.6.13",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model": "^0.6.6",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"faker": "~5.5.3",
|
||||
"@hcengineering/model-recruit": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
|
@ -66,8 +66,8 @@
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"@hcengineering/chunter-assets": "^0.6.7",
|
||||
"@hcengineering/chunter-resources": "^0.6.0",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit-assets": "^0.6.9",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"@hcengineering/recruit-assets": "^0.6.10",
|
||||
"@hcengineering/recruit-resources": "^0.6.0",
|
||||
"@hcengineering/setting": "^0.6.9",
|
||||
"@hcengineering/setting-assets": "^0.6.7",
|
||||
@ -133,8 +133,8 @@
|
||||
"@hcengineering/server-tags-resources": "^0.6.0",
|
||||
"@hcengineering/server-task": "^0.6.0",
|
||||
"@hcengineering/server-task-resources": "^0.6.0",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar-assets": "^0.6.9",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/calendar-assets": "^0.6.10",
|
||||
"@hcengineering/calendar-resources": "^0.6.0",
|
||||
"@hcengineering/server-calendar": "^0.6.0",
|
||||
"@hcengineering/server-calendar-resources": "^0.6.0",
|
||||
@ -160,7 +160,7 @@
|
||||
"@hcengineering/document": "^0.6.0",
|
||||
"@hcengineering/document-assets": "^0.6.0",
|
||||
"@hcengineering/document-resources": "^0.6.0",
|
||||
"@hcengineering/bitrix": "^0.6.38",
|
||||
"@hcengineering/bitrix": "^0.6.39",
|
||||
"@hcengineering/bitrix-assets": "^0.6.0",
|
||||
"@hcengineering/bitrix-resources": "^0.6.0",
|
||||
"@hcengineering/request": "^0.6.3",
|
||||
|
@ -19,7 +19,7 @@ import { activityId } from '@hcengineering/activity'
|
||||
import { attachmentId } from '@hcengineering/attachment'
|
||||
import { automationId } from '@hcengineering/automation'
|
||||
import { boardId } from '@hcengineering/board'
|
||||
import { calendarId } from '@hcengineering/calendar'
|
||||
import calendar, { calendarId } from '@hcengineering/calendar'
|
||||
import { chunterId } from '@hcengineering/chunter'
|
||||
import client, { clientId } from '@hcengineering/client'
|
||||
import { contactId } from '@hcengineering/contact'
|
||||
@ -88,6 +88,7 @@ interface Config {
|
||||
REKONI_URL: string
|
||||
TELEGRAM_URL: string
|
||||
GMAIL_URL: string
|
||||
CALENDAR_URL: string
|
||||
TITLE?: string
|
||||
DEFAULT_LANGUAGE?: string
|
||||
}
|
||||
@ -108,6 +109,7 @@ export async function configurePlatform() {
|
||||
}
|
||||
setMetadata(telegram.metadata.TelegramURL, config.TELEGRAM_URL ?? 'http://localhost:8086')
|
||||
setMetadata(gmail.metadata.GmailURL, config.GMAIL_URL ?? 'http://localhost:8087')
|
||||
setMetadata(calendar.metadata.CalendarServiceURL, config.CALENDAR_URL ?? 'http://localhost:8095')
|
||||
|
||||
setMetadata(login.metadata.OverrideEndpoint, process.env.LOGIN_ENDPOINT)
|
||||
|
||||
|
@ -69,7 +69,7 @@
|
||||
"@hcengineering/server-attachment-resources": "^0.6.0",
|
||||
"xml2js": "~0.4.23",
|
||||
"@hcengineering/model-recruit": "^0.6.0",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"@hcengineering/task": "^0.6.10",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"mime-types": "~2.1.34",
|
||||
|
@ -35,7 +35,7 @@
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/bitrix": "^0.6.38",
|
||||
"@hcengineering/bitrix": "^0.6.39",
|
||||
"@hcengineering/bitrix-resources": "^0.6.0",
|
||||
"@hcengineering/preference": "^0.6.8",
|
||||
"@hcengineering/model-preference": "^0.6.0",
|
||||
|
@ -30,8 +30,9 @@
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/setting": "^0.6.9",
|
||||
"@hcengineering/model-task": "^0.6.0",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/calendar-resources": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
|
@ -14,32 +14,33 @@
|
||||
//
|
||||
|
||||
import activity from '@hcengineering/activity'
|
||||
import { Calendar, Event, Reminder, calendarId } from '@hcengineering/calendar'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import { Calendar, Event, ReccuringEvent, ReccuringInstance, RecurringRule, calendarId } from '@hcengineering/calendar'
|
||||
import { Contact } from '@hcengineering/contact'
|
||||
import { DateRangeMode, Domain, IndexKind, Markup, Ref, Timestamp } from '@hcengineering/core'
|
||||
import {
|
||||
ArrOf,
|
||||
Builder,
|
||||
Collection,
|
||||
Hidden,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
ReadOnly,
|
||||
TypeBoolean,
|
||||
TypeDate,
|
||||
TypeMarkup,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
TypeTimestamp,
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import contact from '@hcengineering/model-contact'
|
||||
import core, { TAttachedDoc } from '@hcengineering/model-core'
|
||||
import { TSpaceWithStates } from '@hcengineering/model-task'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import notification from '@hcengineering/notification'
|
||||
import setting from '@hcengineering/setting'
|
||||
import calendar from './plugin'
|
||||
|
||||
export * from '@hcengineering/calendar'
|
||||
@ -50,11 +51,16 @@ export const DOMAIN_CALENDAR = 'calendar' as Domain
|
||||
|
||||
@Model(calendar.class.Calendar, core.class.Space)
|
||||
@UX(calendar.string.Calendar, calendar.icon.Calendar)
|
||||
export class TCalendar extends TSpaceWithStates implements Calendar {}
|
||||
export class TCalendar extends TSpaceWithStates implements Calendar {
|
||||
@Prop(TypeString(), calendar.string.HideDetails)
|
||||
hideDetails?: boolean
|
||||
}
|
||||
|
||||
@Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_CALENDAR)
|
||||
@UX(calendar.string.Event, calendar.icon.Calendar)
|
||||
export class TEvent extends TAttachedDoc implements Event {
|
||||
eventId!: string
|
||||
|
||||
@Prop(TypeString(), calendar.string.Title)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
@ -67,6 +73,10 @@ export class TEvent extends TAttachedDoc implements Event {
|
||||
@Index(IndexKind.FullText)
|
||||
location?: string
|
||||
|
||||
@Prop(TypeBoolean(), calendar.string.AllDay)
|
||||
@ReadOnly()
|
||||
allDay!: boolean
|
||||
|
||||
@Prop(TypeDate(DateRangeMode.DATETIME), calendar.string.Date)
|
||||
date!: Timestamp
|
||||
|
||||
@ -76,28 +86,37 @@ export class TEvent extends TAttachedDoc implements Event {
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
comments?: number
|
||||
@Prop(ArrOf(TypeRef(contact.class.Contact)), calendar.string.Participants)
|
||||
participants!: Ref<Contact>[]
|
||||
|
||||
@Prop(ArrOf(TypeRef(contact.class.Employee)), calendar.string.Participants)
|
||||
participants!: Ref<Employee>[]
|
||||
@Prop(ArrOf(TypeTimestamp()), calendar.string.Reminders)
|
||||
reminders?: number[]
|
||||
|
||||
@Prop(ArrOf(TypeString()), calendar.string.ExternalParticipants)
|
||||
externalParticipants?: string[]
|
||||
|
||||
access!: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
|
||||
}
|
||||
|
||||
@Mixin(calendar.mixin.Reminder, calendar.class.Event)
|
||||
@UX(calendar.string.Reminder, calendar.icon.Calendar)
|
||||
export class TReminder extends TEvent implements Reminder {
|
||||
@Prop(TypeDate(DateRangeMode.DATETIME), calendar.string.Shift)
|
||||
@Hidden()
|
||||
shift!: Timestamp
|
||||
@Model(calendar.class.ReccuringEvent, calendar.class.Event)
|
||||
@UX(calendar.string.ReccuringEvent, calendar.icon.Calendar)
|
||||
export class TReccuringEvent extends TEvent implements ReccuringEvent {
|
||||
rules!: RecurringRule[]
|
||||
exdate!: Timestamp[]
|
||||
rdate!: Timestamp[]
|
||||
}
|
||||
|
||||
@Prop(TypeString(), calendar.string.State)
|
||||
@Index(IndexKind.Indexed)
|
||||
@Hidden()
|
||||
state!: 'active' | 'done'
|
||||
@Model(calendar.class.ReccuringInstance, calendar.class.Event)
|
||||
@UX(calendar.string.Event, calendar.icon.Calendar)
|
||||
export class TReccuringInstance extends TEvent implements ReccuringInstance {
|
||||
recurringEventId!: Ref<ReccuringEvent>
|
||||
originalStartTime!: number
|
||||
isCancelled?: boolean
|
||||
virtual?: boolean
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TCalendar, TEvent, TReminder)
|
||||
builder.createModel(TCalendar, TReccuringEvent, TReccuringInstance, TEvent)
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
@ -127,6 +146,20 @@ export function createModel (builder: Builder): void {
|
||||
calendar.viewlet.CalendarEvent
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.IntegrationType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: calendar.string.Calendar,
|
||||
description: calendar.string.IntegrationDescr,
|
||||
icon: calendar.component.CalendarIntegrationIcon,
|
||||
createComponent: calendar.component.IntegrationConnect,
|
||||
onDisconnect: calendar.handler.DisconnectHandler,
|
||||
reconnectComponent: calendar.component.IntegrationConnect
|
||||
},
|
||||
calendar.integrationType.Calendar
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationGroup,
|
||||
core.space.Model,
|
||||
@ -149,10 +182,8 @@ export function createModel (builder: Builder): void {
|
||||
generated: false,
|
||||
label: calendar.string.Reminder,
|
||||
group: calendar.ids.CalendarNotificationGroup,
|
||||
txClasses: [core.class.TxMixin],
|
||||
field: 'state',
|
||||
txMatch: { 'attributes.state': 'done' },
|
||||
objectClass: calendar.mixin.Reminder,
|
||||
txClasses: [],
|
||||
objectClass: calendar.class.Event,
|
||||
allowedForAuthor: true,
|
||||
templates: {
|
||||
textTemplate: 'Reminder: {doc}',
|
||||
@ -171,9 +202,9 @@ export function createModel (builder: Builder): void {
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: calendar.mixin.Reminder,
|
||||
objectClass: calendar.class.Event,
|
||||
icon: calendar.icon.Reminder,
|
||||
txClass: core.class.TxMixin,
|
||||
txClass: core.class.TxUpdateDoc,
|
||||
label: calendar.string.Reminder,
|
||||
component: calendar.activity.ReminderViewlet,
|
||||
display: 'emphasized',
|
||||
@ -218,14 +249,30 @@ export function createModel (builder: Builder): void {
|
||||
calendar.action.SaveEventReminder
|
||||
)
|
||||
|
||||
builder.mixin(calendar.mixin.Reminder, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: calendar.component.ReminderPresenter
|
||||
})
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: calendar.actionImpl.DeleteRecEvent,
|
||||
override: [view.action.Delete],
|
||||
label: view.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
keyBinding: ['Meta + Backspace'],
|
||||
category: view.category.General,
|
||||
input: 'any',
|
||||
target: calendar.class.ReccuringInstance,
|
||||
context: { mode: ['context', 'browser'], group: 'tools' }
|
||||
},
|
||||
calendar.action.DeleteRecEvent
|
||||
)
|
||||
|
||||
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: calendar.component.EditEvent
|
||||
})
|
||||
|
||||
builder.mixin(calendar.class.ReccuringInstance, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: calendar.component.EditRecEvent
|
||||
})
|
||||
|
||||
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: calendar.component.EventPresenter
|
||||
})
|
||||
|
@ -13,34 +13,70 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { TxOperations } from '@hcengineering/core'
|
||||
import { Calendar, Event } from '@hcengineering/calendar'
|
||||
import core, { Ref, TxOperations } from '@hcengineering/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
|
||||
import calendar from './plugin'
|
||||
import { DOMAIN_CALENDAR } from '.'
|
||||
import contact from '@hcengineering/contact'
|
||||
|
||||
async function createSpace (tx: TxOperations): Promise<void> {
|
||||
const current = await tx.findOne(core.class.Space, {
|
||||
_id: calendar.space.PersonalEvents
|
||||
async function migrateCalendars (tx: TxOperations): Promise<void> {
|
||||
const existCalendars = new Set((await tx.findAll(calendar.class.Calendar, {})).map((p) => p._id))
|
||||
const users = await tx.findAll(contact.class.EmployeeAccount, {})
|
||||
for (const user of users) {
|
||||
if (!existCalendars.has(`${user._id}_calendar` as Ref<Calendar>)) {
|
||||
await tx.createDoc(
|
||||
calendar.class.Calendar,
|
||||
core.space.Space,
|
||||
{
|
||||
name: user.email,
|
||||
description: '',
|
||||
archived: false,
|
||||
private: false,
|
||||
members: [user._id]
|
||||
},
|
||||
`${user._id}_calendar` as Ref<Calendar>,
|
||||
undefined,
|
||||
user._id
|
||||
)
|
||||
}
|
||||
}
|
||||
const events = await tx.findAll(calendar.class.Event, { space: calendar.space.PersonalEvents })
|
||||
for (const event of events) {
|
||||
await tx.update(event, { space: (event.createdBy ?? event.modifiedBy) as string as Ref<Calendar> })
|
||||
}
|
||||
const space = await tx.findOne(calendar.class.Calendar, { _id: calendar.space.PersonalEvents })
|
||||
if (space !== undefined) {
|
||||
await tx.remove(space)
|
||||
}
|
||||
}
|
||||
|
||||
async function fixEventDueDate (client: MigrationClient): Promise<void> {
|
||||
const events = await client.find<Event>(DOMAIN_CALENDAR, {
|
||||
_class: calendar.class.Event,
|
||||
dueDate: { $exists: false }
|
||||
})
|
||||
if (current === undefined) {
|
||||
await tx.createDoc(
|
||||
core.class.Space,
|
||||
core.space.Space,
|
||||
{
|
||||
name: 'Personal Events',
|
||||
description: 'Personal Events',
|
||||
private: false,
|
||||
archived: false,
|
||||
members: []
|
||||
},
|
||||
calendar.space.PersonalEvents
|
||||
)
|
||||
for (const event of events) {
|
||||
await client.update(DOMAIN_CALENDAR, { _id: event._id }, { dueDate: event.date })
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateReminders (client: MigrationClient): Promise<void> {
|
||||
const events = await client.find(DOMAIN_CALENDAR, { 'calendar:mixin:Reminder': { $exists: true } })
|
||||
for (const event of events) {
|
||||
const shift = (event as any)['calendar:mixin:Reminder'].shift
|
||||
await client.update(DOMAIN_CALENDAR, { _id: event._id }, { reminders: [shift] })
|
||||
await client.update(DOMAIN_CALENDAR, { _id: event._id }, { $unset: { 'calendar:mixin:Reminder': true } })
|
||||
}
|
||||
}
|
||||
|
||||
export const calendarOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await fixEventDueDate(client)
|
||||
await migrateReminders(client)
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
await createSpace(tx)
|
||||
await migrateCalendars(tx)
|
||||
}
|
||||
}
|
||||
|
@ -25,20 +25,22 @@ import { Action, ActionCategory, ViewAction, Viewlet, ViewletDescriptor } from '
|
||||
|
||||
export default mergeIds(calendarId, calendar, {
|
||||
component: {
|
||||
IntegrationConnect: '' as AnyComponent,
|
||||
CreateCalendar: '' as AnyComponent,
|
||||
CalendarView: '' as AnyComponent,
|
||||
EditEvent: '' as AnyComponent,
|
||||
ReminderPresenter: '' as AnyComponent,
|
||||
EventPresenter: '' as AnyComponent
|
||||
EventPresenter: '' as AnyComponent,
|
||||
CalendarIntegrationIcon: '' as AnyComponent
|
||||
},
|
||||
action: {
|
||||
SaveEventReminder: '' as Ref<Action>
|
||||
SaveEventReminder: '' as Ref<Action>,
|
||||
DeleteRecEvent: '' as Ref<Action>
|
||||
},
|
||||
category: {
|
||||
Calendar: '' as Ref<ActionCategory>
|
||||
},
|
||||
actionImpl: {
|
||||
SaveEventReminder: '' as ViewAction
|
||||
SaveEventReminder: '' as ViewAction,
|
||||
DeleteRecEvent: '' as ViewAction
|
||||
},
|
||||
string: {
|
||||
ApplicationLabelCalendar: '' as IntlString,
|
||||
@ -48,7 +50,8 @@ export default mergeIds(calendarId, calendar, {
|
||||
State: '' as IntlString,
|
||||
CreatedReminder: '' as IntlString,
|
||||
ConfigLabel: '' as IntlString,
|
||||
ConfigDescription: '' as IntlString
|
||||
ConfigDescription: '' as IntlString,
|
||||
IntegrationDescr: '' as IntlString
|
||||
},
|
||||
viewlet: {
|
||||
Calendar: '' as Ref<ViewletDescriptor>,
|
||||
|
@ -34,7 +34,7 @@
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"@hcengineering/recruit-resources": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
|
@ -28,9 +28,10 @@
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/model": "^0.6.6",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/server-calendar": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
}
|
||||
}
|
||||
|
@ -20,16 +20,33 @@ import core, { Class, Doc } from '@hcengineering/core'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import serverCalendar from '@hcengineering/server-calendar'
|
||||
import serverCore, { ObjectDDParticipant } from '@hcengineering/server-core'
|
||||
import contact from '@hcengineering/contact'
|
||||
|
||||
export { serverCalendarId } from '@hcengineering/server-calendar'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.mixin(calendar.class.Event, core.class.Class, serverNotification.mixin.HTMLPresenter, {
|
||||
presenter: serverCalendar.function.EventHTMLPresenter
|
||||
presenter: serverCalendar.function.ReminderHTMLPresenter
|
||||
})
|
||||
|
||||
builder.mixin(calendar.class.Event, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
presenter: serverCalendar.function.EventTextPresenter
|
||||
presenter: serverCalendar.function.ReminderTextPresenter
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCalendar.trigger.OnEmployeeAccountCreate,
|
||||
txMatch: {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: contact.class.EmployeeAccount
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCalendar.trigger.OnEvent,
|
||||
txMatch: {
|
||||
_class: core.class.TxCollectionCUD,
|
||||
objectClass: calendar.class.Event
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/panel",
|
||||
"version": "0.6.9",
|
||||
"version": "0.6.10",
|
||||
"main": "src/index.ts",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
@ -40,7 +40,7 @@
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/calendar": "^0.6.11"
|
||||
"@hcengineering/calendar": "^0.6.12"
|
||||
},
|
||||
"repository": "https://github.com/hcenginneing/anticrm",
|
||||
"publishConfig": {
|
||||
|
@ -36,7 +36,7 @@
|
||||
: new Date(new Date(currentDate).setHours(0, 0, 0, 0))
|
||||
</script>
|
||||
|
||||
<table class="antiTable">
|
||||
<table>
|
||||
<thead class="scroller-thead">
|
||||
<tr class="scroller-thead__tr">
|
||||
<th><Label label={ui.string.HoursLabel} /></th>
|
||||
@ -45,7 +45,7 @@
|
||||
<th>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="antiTable-cells cursor-pointer uppercase flex-col-center"
|
||||
class="cursor-pointer uppercase flex-col-center"
|
||||
class:today={areDatesEqual(todayDate, day)}
|
||||
on:click={() => {
|
||||
dispatch('select', day)
|
||||
@ -59,18 +59,15 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if $$slots.cell}
|
||||
<slot name="header" date={getDay(weekMonday, 0)} days={displayedDaysCount} />
|
||||
{/if}
|
||||
{#each [...Array(displayedHours).keys()] as hourOfDay}
|
||||
<tr class="antiTable-body__row">
|
||||
<td style="width: 50px;" class="calendar-td">
|
||||
<div class="antiTable-cells__firstCell">
|
||||
<tr>
|
||||
<td style="width: 50px;" class="calendar-td first">
|
||||
{#if hourOfDay !== 0}
|
||||
{addZero(hourOfDay)}:00
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
{#each [...Array(displayedDaysCount).keys()] as dayIndex}
|
||||
<td class="antiTable-body__border calendar-td cell" style={`height: ${cellHeight};`}>
|
||||
<td class="calendar-td cell" style={`height: ${cellHeight};`}>
|
||||
{#if $$slots.cell}
|
||||
<slot name="cell" date={getDay(weekMonday, dayIndex, hourOfDay * 60)} />
|
||||
{/if}
|
||||
@ -92,14 +89,21 @@
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
.calendar-td {
|
||||
&:not(.first) {
|
||||
border: 1px solid var(--theme-table-border-color);
|
||||
}
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
&.first {
|
||||
display: flex;
|
||||
margin-top: -0.5rem;
|
||||
}
|
||||
}
|
||||
.cell {
|
||||
overflow: hidden;
|
||||
padding: 2px;
|
||||
width: calc(calc(100% - 50px) / 7);
|
||||
}
|
||||
.cell:hover:not(.wrongMonth) {
|
||||
.cell:hover {
|
||||
color: var(--accented-button-color);
|
||||
background-color: var(--highlight-hover);
|
||||
}
|
||||
|
@ -41,7 +41,7 @@
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
"filesize": "^8.0.3",
|
||||
|
@ -30,6 +30,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/bitrix": "^0.6.38"
|
||||
"@hcengineering/bitrix": "^0.6.39"
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"svelte": "3.55.1",
|
||||
"@hcengineering/bitrix": "^0.6.38",
|
||||
"@hcengineering/bitrix": "^0.6.39",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
@ -54,7 +54,7 @@
|
||||
"@hcengineering/tags": "^0.6.11",
|
||||
"@hcengineering/tags-resources": "^0.6.0",
|
||||
"fast-equals": "^2.0.3",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"@hcengineering/task": "^0.6.10"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/anticrm",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/bitrix",
|
||||
"version": "0.6.38",
|
||||
"version": "0.6.39",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
@ -37,7 +37,7 @@
|
||||
"fast-equals": "^2.0.3",
|
||||
"qs": "~6.11.0",
|
||||
"@hcengineering/gmail": "^0.6.12",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"@hcengineering/task": "^0.6.10"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/anticrm",
|
||||
|
@ -35,7 +35,7 @@
|
||||
"@hcengineering/attachment": "^0.6.8",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/board": "^0.6.9",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"@hcengineering/chunter-resources": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
@ -44,7 +44,7 @@
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
"@hcengineering/notification-resources": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/task": "^0.6.10",
|
||||
|
@ -39,6 +39,18 @@
|
||||
"CreateEvent": "Create event",
|
||||
"EventFor": "Event for: ",
|
||||
"ConfigLabel": "Calendar",
|
||||
"ConfigDescription": "Extension to see calendar with Events"
|
||||
"ConfigDescription": "Extension to see calendar with Events",
|
||||
"HideDetails": "Hide details",
|
||||
"ReccuringEvent": "Reccuring event",
|
||||
"ExternalParticipants": "External participants",
|
||||
"IntegrationDescr": "Use Google Calendar integration",
|
||||
"Connect": "Connect",
|
||||
"RedirectGoogle": "You will be redirect to google auth page",
|
||||
"ConnectCalendar": "Connect Google Calendar account",
|
||||
"EditRecEvent": "Edit recurring event",
|
||||
"RemoveRecEvent": "Remove recurring event",
|
||||
"ThisEvent": "This event",
|
||||
"ThisAndNext": "This and following events",
|
||||
"AllEvents": "All events"
|
||||
}
|
||||
}
|
@ -39,6 +39,18 @@
|
||||
"CreateEvent": "Создать событие",
|
||||
"EventFor": "Событие для: ",
|
||||
"ConfigLabel": "Календарь",
|
||||
"ConfigDescription": "Расширение для календаря с событиями"
|
||||
"ConfigDescription": "Расширение для календаря с событиями",
|
||||
"ReccuringEvent": "Повторяющиеся событие",
|
||||
"HideDetails": "Скрыть детали",
|
||||
"ExternalParticipants": "Внешние участники",
|
||||
"IntegrationDescr": "Подключить Google Каледнарь",
|
||||
"Connect": "Подключить",
|
||||
"RedirectGoogle": "Вы будете перенаправлены на страницу авторизации Google",
|
||||
"ConnectCalendar": "Подключить Google Каледнарь",
|
||||
"EditRecEvent": "Изменение повторяющегося события",
|
||||
"RemoveRecEvent": "Удаление повторяющегося события",
|
||||
"ThisEvent": "Только это событие",
|
||||
"ThisAndNext": "Это и последующие события",
|
||||
"AllEvents": "Все события"
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/calendar-assets",
|
||||
"version": "0.6.9",
|
||||
"version": "0.6.10",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"template": "@hcengineering/assets-package",
|
||||
@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/calendar": "^0.6.11"
|
||||
"@hcengineering/calendar": "^0.6.12"
|
||||
},
|
||||
"repository": "https://github.com/hcenginneing/anticrm",
|
||||
"publishConfig": {
|
||||
|
@ -36,10 +36,11 @@
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"svelte": "3.55.1",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
|
@ -13,21 +13,32 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { Class, Doc, DocumentQuery, FindOptions, Ref, SortingOrder, Space } from '@hcengineering/core'
|
||||
import { Calendar, Event, getAllEvents } from '@hcengineering/calendar'
|
||||
import {
|
||||
Class,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Space,
|
||||
Timestamp,
|
||||
getCurrentAccount
|
||||
} from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import {
|
||||
AnyComponent,
|
||||
areDatesEqual,
|
||||
Button,
|
||||
IconBack,
|
||||
IconForward,
|
||||
MonthCalendar,
|
||||
Scroller,
|
||||
showPopup,
|
||||
WeekCalendar,
|
||||
YearCalendar,
|
||||
defaultSP
|
||||
areDatesEqual,
|
||||
defaultSP,
|
||||
getMonday,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { BuildModelKey } from '@hcengineering/view'
|
||||
import { CalendarMode } from '../index'
|
||||
@ -51,53 +62,98 @@
|
||||
|
||||
let objects: Event[] = []
|
||||
|
||||
function getFrom (date: Date, mode: CalendarMode): Timestamp {
|
||||
switch (mode) {
|
||||
case CalendarMode.Day: {
|
||||
return new Date(date).setHours(0, 0, 0, 0)
|
||||
}
|
||||
case CalendarMode.Week: {
|
||||
return getMonday(date, mondayStart).setHours(0, 0, 0, 0)
|
||||
}
|
||||
case CalendarMode.Month: {
|
||||
return new Date(new Date(date).setDate(1)).setHours(0, 0, 0, 0)
|
||||
}
|
||||
case CalendarMode.Year: {
|
||||
return new Date(new Date(date).setMonth(0, 1)).setHours(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTo (date: Date, mode: CalendarMode): Timestamp {
|
||||
switch (mode) {
|
||||
case CalendarMode.Day: {
|
||||
return new Date(date).setDate(date.getDate() + 1)
|
||||
}
|
||||
case CalendarMode.Week: {
|
||||
const monday = getMonday(date, mondayStart)
|
||||
return new Date(monday.setDate(monday.getDate() + 7)).setHours(0, 0, 0, 0)
|
||||
}
|
||||
case CalendarMode.Month: {
|
||||
return new Date(new Date(date).setMonth(date.getMonth() + 1, 1)).setHours(0, 0, 0, 0)
|
||||
}
|
||||
case CalendarMode.Year: {
|
||||
return new Date(new Date(date).setMonth(12, 1)).setHours(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: from = getFrom(currentDate, mode)
|
||||
$: to = getTo(currentDate, mode)
|
||||
|
||||
const calendarsQuery = createQuery()
|
||||
|
||||
let calendars: Calendar[] = []
|
||||
|
||||
calendarsQuery.query(calendar.class.Calendar, { createdBy: getCurrentAccount()._id }, (res) => {
|
||||
calendars = res
|
||||
})
|
||||
|
||||
const q = createQuery()
|
||||
|
||||
async function update (_class: Ref<Class<Event>>, query: DocumentQuery<Event>, options?: FindOptions<Event>) {
|
||||
async function update (
|
||||
_class: Ref<Class<Event>>,
|
||||
query: DocumentQuery<Event>,
|
||||
from: Timestamp,
|
||||
to: Timestamp,
|
||||
calendars: Calendar[],
|
||||
options?: FindOptions<Event>
|
||||
) {
|
||||
q.query<Event>(
|
||||
_class,
|
||||
query,
|
||||
{
|
||||
space: { $in: calendars.map((p) => p._id) },
|
||||
...query
|
||||
},
|
||||
(result) => {
|
||||
objects = result
|
||||
objects = getAllEvents(result, from, to)
|
||||
},
|
||||
{ sort: { date: SortingOrder.Ascending }, ...options }
|
||||
)
|
||||
}
|
||||
$: update(_class, query, options)
|
||||
$: update(_class, query, from, to, calendars, options)
|
||||
|
||||
function areDatesLess (firstDate: Date, secondDate: Date): boolean {
|
||||
return (
|
||||
firstDate.getFullYear() <= secondDate.getFullYear() &&
|
||||
firstDate.getMonth() <= secondDate.getMonth() &&
|
||||
firstDate.getDate() <= secondDate.getDate()
|
||||
)
|
||||
function inRange (start: Date, end: Date, startPeriod: Date, period: 'day' | 'hour'): boolean {
|
||||
const endPeriod =
|
||||
period === 'day'
|
||||
? new Date(startPeriod).setDate(startPeriod.getDate() + 1)
|
||||
: new Date(startPeriod).setHours(startPeriod.getHours() + 1)
|
||||
if (end.getTime() - 1 <= startPeriod.getTime()) return false
|
||||
if (start.getTime() >= endPeriod) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function findEvents (events: Event[], date: Date, minutes = false): Event[] {
|
||||
return events.filter((it) => {
|
||||
const d1 = new Date(it.date)
|
||||
const d2 = new Date(it.dueDate ?? it.date)
|
||||
const inDays = areDatesLess(d1, date) && areDatesLess(date, d2)
|
||||
|
||||
if (minutes) {
|
||||
if (areDatesEqual(d1, date)) {
|
||||
return (
|
||||
(date.getTime() <= d1.getTime() && d1.getTime() < date.getTime() + 60 * 60 * 1000) ||
|
||||
(d1.getTime() <= date.getTime() && date.getTime() < d2.getTime())
|
||||
)
|
||||
}
|
||||
|
||||
if (areDatesEqual(d2, date)) {
|
||||
return (
|
||||
(date.getTime() < d2.getTime() && d2.getTime() < date.getTime() + 60 * 60 * 1000) ||
|
||||
(d1.getTime() <= date.getTime() && date.getTime() < d2.getTime())
|
||||
)
|
||||
}
|
||||
|
||||
// Somethere in middle
|
||||
return inDays
|
||||
let d1 = new Date(it.date)
|
||||
let d2 = new Date(it.dueDate ?? it.date)
|
||||
if (it.allDay) {
|
||||
if (minutes) return false
|
||||
d1 = new Date(d1.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
d2 = new Date(d2.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
return inRange(d1, d2, date, minutes ? 'hour' : 'day')
|
||||
}
|
||||
return inDays
|
||||
return inRange(d1, d2, date, minutes ? 'hour' : 'day')
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,13 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Calendar, generateEventId } from '@hcengineering/calendar'
|
||||
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||
import { Class, DateRangeMode, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import { UserBoxList } from '@hcengineering/contact-resources'
|
||||
import ui, { EditBox, DateRangePresenter } from '@hcengineering/ui'
|
||||
import { tick } from 'svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Class, DateRangeMode, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import ui, { DateRangePresenter, EditBox, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let attachedTo: Ref<Doc> = calendar.ids.NoAttached
|
||||
@ -30,16 +30,17 @@
|
||||
|
||||
const now = new Date()
|
||||
const defaultDuration = 30 * 60 * 1000
|
||||
const allDayDuration = 24 * 60 * 60 * 1000
|
||||
|
||||
let startDate =
|
||||
date === undefined ? now.getTime() : withTime ? date.getTime() : date.setHours(now.getHours(), now.getMinutes())
|
||||
let duration = defaultDuration
|
||||
let dueDate = startDate + duration
|
||||
let dueDateRef: DateRangePresenter
|
||||
let allDay = false
|
||||
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
let participants: Ref<Employee>[] = [currentUser.employee]
|
||||
const space = calendar.space.PersonalEvents
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
@ -52,22 +53,23 @@
|
||||
let date: number | undefined
|
||||
if (startDate != null) date = startDate
|
||||
if (date === undefined) return
|
||||
await client.createDoc(calendar.class.Event, space, {
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
collection: 'events',
|
||||
date,
|
||||
dueDate,
|
||||
const space = `${getCurrentAccount()._id}_calendar` as Ref<Calendar>
|
||||
await client.addCollection(calendar.class.Event, space, attachedTo, attachedToClass, 'events', {
|
||||
eventId: generateEventId(),
|
||||
date: new Date(date).setUTCHours(0, 0, 0, 0),
|
||||
dueDate: new Date(dueDate).setUTCHours(0, 0, 0, 0),
|
||||
description: '',
|
||||
participants,
|
||||
title
|
||||
title,
|
||||
allDay,
|
||||
access: 'owner'
|
||||
})
|
||||
}
|
||||
|
||||
const handleNewStartDate = async (newStartDate: number | null) => {
|
||||
if (newStartDate !== null) {
|
||||
startDate = newStartDate
|
||||
dueDate = startDate + duration
|
||||
dueDate = startDate + (allDay ? allDayDuration : duration)
|
||||
await tick()
|
||||
dueDateRef.adaptValue()
|
||||
}
|
||||
@ -80,12 +82,14 @@
|
||||
dueDate = newDueDate
|
||||
duration = diff
|
||||
} else {
|
||||
dueDate = startDate + duration
|
||||
dueDate = startDate + (allDay ? allDayDuration : duration)
|
||||
}
|
||||
await tick()
|
||||
dueDateRef.adaptValue()
|
||||
}
|
||||
}
|
||||
|
||||
$: mode = allDay ? DateRangeMode.DATE : DateRangeMode.DATETIME
|
||||
</script>
|
||||
|
||||
<Card
|
||||
@ -99,12 +103,13 @@
|
||||
>
|
||||
<EditBox bind:value={title} placeholder={calendar.string.Title} kind={'large-style'} autoFocus />
|
||||
<svelte:fragment slot="pool">
|
||||
<ToggleWithLabel bind:on={allDay} label={calendar.string.AllDay} />
|
||||
<DateRangePresenter
|
||||
value={startDate}
|
||||
labelNull={ui.string.SelectDate}
|
||||
on:change={async (event) => await handleNewStartDate(event.detail)}
|
||||
mode={DateRangeMode.DATETIME}
|
||||
kind={'regular'}
|
||||
{mode}
|
||||
size={'large'}
|
||||
editable
|
||||
/>
|
||||
@ -113,8 +118,8 @@
|
||||
value={dueDate}
|
||||
labelNull={calendar.string.DueTo}
|
||||
on:change={async (event) => await handleNewDueDate(event.detail)}
|
||||
mode={DateRangeMode.DATETIME}
|
||||
kind={'regular'}
|
||||
{mode}
|
||||
size={'large'}
|
||||
editable
|
||||
/>
|
||||
|
@ -13,23 +13,25 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Calendar, Event, generateEventId } from '@hcengineering/calendar'
|
||||
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||
import { Class, DateRangeMode, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import { UserBoxList } from '@hcengineering/contact-resources'
|
||||
import ui, { EditBox, DateRangePresenter } from '@hcengineering/ui'
|
||||
import { Class, DateRangeMode, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import ui, { DateRangePresenter, EditBox } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let attachedTo: Ref<Doc>
|
||||
export let attachedToClass: Ref<Class<Doc>>
|
||||
export let event: Event | undefined = undefined
|
||||
export let title: string = ''
|
||||
let _title = title
|
||||
|
||||
let value: number | null | undefined = null
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
let participants: Ref<Employee>[] = [currentUser.employee]
|
||||
const space = calendar.space.PersonalEvents
|
||||
const defaultDuration = 30 * 60 * 1000
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
@ -41,22 +43,18 @@
|
||||
async function saveReminder () {
|
||||
let date: number | undefined
|
||||
if (value != null) date = value
|
||||
// if (value.date !== undefined) {
|
||||
// date = new Date(value.date).getTime()
|
||||
// } else if (value.shift !== undefined) {
|
||||
// date = new Date().getTime() + value.shift
|
||||
// }
|
||||
if (date === undefined) return
|
||||
const _id = await client.addCollection(calendar.class.Event, space, attachedTo, attachedToClass, 'reminders', {
|
||||
const space = `${getCurrentAccount()._id}_calendar` as Ref<Calendar>
|
||||
await client.addCollection(calendar.class.Event, space, attachedTo, attachedToClass, 'events', {
|
||||
eventId: generateEventId(),
|
||||
date,
|
||||
dueDate: date + defaultDuration,
|
||||
description: '',
|
||||
participants,
|
||||
title: _title
|
||||
})
|
||||
|
||||
await client.createMixin(_id, calendar.class.Event, space, calendar.mixin.Reminder, {
|
||||
shift: 0,
|
||||
state: 'active'
|
||||
title: _title,
|
||||
allDay: false,
|
||||
reminders: [0],
|
||||
access: 'owner'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@ -72,7 +70,6 @@
|
||||
>
|
||||
<EditBox bind:value={_title} placeholder={calendar.string.Title} kind={'large-style'} autoFocus />
|
||||
<svelte:fragment slot="pool">
|
||||
<!-- <TimeShiftPicker title={calendar.string.Date} bind:value direction="after" /> -->
|
||||
<DateRangePresenter
|
||||
bind:value
|
||||
mode={DateRangeMode.DATETIME}
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { Class, Doc, DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
|
||||
import { Label, addZero, getPlatformColorForTextDef, themeStore, tooltip } from '@hcengineering/ui'
|
||||
import { Label, addZero, getPlatformColorForTextDef, showPopup, themeStore, tooltip } from '@hcengineering/ui'
|
||||
import { BuildModelKey } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
@ -38,14 +38,11 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
const eventCount = 3
|
||||
|
||||
$: tip =
|
||||
events.length > 0
|
||||
? {
|
||||
label: calendar.string.Events,
|
||||
component: EventsPopup,
|
||||
props: { value: events, _class, query, options, baseMenuClass, config }
|
||||
}
|
||||
: undefined
|
||||
$: tip = {
|
||||
label: calendar.string.Events,
|
||||
component: EventsPopup,
|
||||
props: { value: events }
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if size === 'huge'}
|
||||
@ -73,13 +70,24 @@
|
||||
>
|
||||
{#each events.slice(0, eventCount) as e}
|
||||
<div
|
||||
class="overflow-label mt-1 py-1 flex flex-between event"
|
||||
style="background-color: {getPlatformColorForTextDef(e._class, $themeStore.dark).background};"
|
||||
class="overflow-label mt-1 py-1 flex flex-between event cursor-pointer"
|
||||
style="background-color: {getPlatformColorForTextDef(e.space, $themeStore.dark).color};"
|
||||
on:click|stopPropagation|preventDefault={() => {
|
||||
showPopup(
|
||||
e._class === calendar.class.ReccuringInstance
|
||||
? calendar.component.EditRecEvent
|
||||
: calendar.component.EditEvent,
|
||||
{ object: e },
|
||||
'content'
|
||||
)
|
||||
}}
|
||||
>
|
||||
{e.title}
|
||||
<div>
|
||||
{addZero(new Date(e.date).getHours())}:{addZero(new Date(e.date).getMinutes())}
|
||||
</div>
|
||||
{#if !e.allDay}
|
||||
<div>
|
||||
{addZero(new Date(e.date).getHours())}:{addZero(new Date(e.date).getMinutes())}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if events.length > eventCount}
|
||||
@ -92,15 +100,12 @@
|
||||
{:else}
|
||||
<div class="w-full h-full relative flex-center cell" class:today class:selected class:wrongMonth use:tooltip={tip}>
|
||||
{date.getDate()}
|
||||
{#if events.length > 0}
|
||||
<div class="marker" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.event {
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0rem 0.5rem;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
@ -111,7 +116,7 @@
|
||||
padding: 0.25rem;
|
||||
}
|
||||
.cell {
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.today {
|
||||
|
@ -35,9 +35,8 @@
|
||||
showPopup(SaveEventReminder, { objectId: value._id, objectClass: value._class }, ev.target as HTMLElement)
|
||||
} else {
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
const current = await client.findOne(calendar.mixin.Reminder, {
|
||||
const current = await client.findOne(calendar.class.Event, {
|
||||
attachedTo: value._id,
|
||||
state: 'active',
|
||||
participants: currentUser.employee
|
||||
})
|
||||
if (current === undefined) {
|
||||
|
@ -13,22 +13,37 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { EmployeeAccount } from '@hcengineering/contact'
|
||||
import { Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { Button, showPopup, Label, Scroller, IconAdd, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
import { Table } from '@hcengineering/view-resources'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Button, deviceOptionsStore as deviceInfo, IconAdd, Label, Scroller, showPopup } from '@hcengineering/ui'
|
||||
import calendar from '../plugin'
|
||||
import CreateReminder from './CreateReminder.svelte'
|
||||
import ReminderPresenter from './ReminderPresenter.svelte'
|
||||
|
||||
export let attachedTo: Ref<Doc>
|
||||
export let attachedToClass: Ref<Class<Doc>>
|
||||
export let title: string | undefined
|
||||
|
||||
function click (ev: Event): void {
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
let events: Event[] = []
|
||||
const query = createQuery()
|
||||
$: query.query(
|
||||
calendar.class.Event,
|
||||
{
|
||||
attachedTo,
|
||||
participants: currentUser.employee
|
||||
},
|
||||
(res) => {
|
||||
events = res.filter((p) => p.reminders !== undefined && p.reminders.length > 0)
|
||||
}
|
||||
)
|
||||
|
||||
function click (ev: MouseEvent): void {
|
||||
showPopup(CreateReminder, { attachedTo, attachedToClass, title }, ev.target as HTMLElement)
|
||||
}
|
||||
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
$: isMobile = $deviceInfo.isMobile
|
||||
</script>
|
||||
|
||||
@ -39,13 +54,9 @@
|
||||
</div>
|
||||
<Scroller>
|
||||
<div class="px-4 clear-mins">
|
||||
<Table
|
||||
_class={calendar.mixin.Reminder}
|
||||
config={['']}
|
||||
options={{}}
|
||||
query={{ attachedTo, state: 'active', participants: currentUser.employee }}
|
||||
hiddenHeader
|
||||
/>
|
||||
{#each events as event}
|
||||
<ReminderPresenter value={event} />
|
||||
{/each}
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
|
@ -14,32 +14,37 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { Doc, WithLookup } from '@hcengineering/core'
|
||||
import { DateRangeMode, Doc } from '@hcengineering/core'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import { AnyComponent, Component, Label, StylishEdit } from '@hcengineering/ui'
|
||||
import { getObjectPreview, ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import ui, {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DateRangePresenter,
|
||||
EditBox,
|
||||
IconMoreH,
|
||||
Label,
|
||||
ToggleWithLabel,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ContextMenu, ObjectPresenter, getObjectPreview } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let object: WithLookup<Event>
|
||||
export let object: Event
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', {
|
||||
ignoreKeys: ['comments', 'title', 'description'],
|
||||
ignoreMixins: [calendar.mixin.Reminder]
|
||||
})
|
||||
})
|
||||
|
||||
const query = createQuery()
|
||||
let doc: Doc | undefined
|
||||
|
||||
$: if (object.attachedTo !== undefined && object.attachedToClass !== undefined) {
|
||||
$: if (object.attachedTo !== calendar.ids.NoAttached) {
|
||||
query.query(object.attachedToClass, { _id: object.attachedTo }, (res) => {
|
||||
doc = res.shift()
|
||||
doc = res[0]
|
||||
})
|
||||
}
|
||||
|
||||
@ -52,10 +57,95 @@
|
||||
}
|
||||
|
||||
$: updatePreviewPresenter(doc)
|
||||
|
||||
$: mode = object.allDay ? DateRangeMode.DATE : DateRangeMode.DATETIME
|
||||
|
||||
const defaultDuration = 30 * 60 * 1000
|
||||
const allDayDuration = 24 * 60 * 60 * 1000
|
||||
let duration = object.dueDate - object.date
|
||||
|
||||
async function updateDate () {
|
||||
await client.update(object, {
|
||||
date: object.date,
|
||||
dueDate: object.dueDate,
|
||||
allDay: object.allDay
|
||||
})
|
||||
}
|
||||
|
||||
async function handleNewStartDate (newStartDate: number | null) {
|
||||
if (newStartDate !== null) {
|
||||
object.date = newStartDate
|
||||
object.dueDate = object.date + (object.allDay ? allDayDuration : duration)
|
||||
if (object.allDay) {
|
||||
object.date = new Date(object.date).setUTCHours(0, 0, 0, 0)
|
||||
object.dueDate = new Date(object.dueDate).setUTCHours(0, 0, 0, 0)
|
||||
}
|
||||
await tick()
|
||||
dueDateRef.adaptValue()
|
||||
await updateDate()
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNewDueDate (newDueDate: number | null) {
|
||||
if (newDueDate !== null) {
|
||||
const diff = newDueDate - object.date
|
||||
if (diff > 0) {
|
||||
object.dueDate = newDueDate
|
||||
duration = diff
|
||||
} else {
|
||||
object.dueDate = object.date + (object.allDay ? allDayDuration : duration)
|
||||
}
|
||||
if (object.allDay) {
|
||||
object.date = new Date(object.date).setUTCHours(0, 0, 0, 0)
|
||||
object.dueDate = new Date(object.dueDate).setUTCHours(0, 0, 0, 0)
|
||||
}
|
||||
await tick()
|
||||
dueDateRef.adaptValue()
|
||||
await updateDate()
|
||||
}
|
||||
}
|
||||
|
||||
async function allDayChangeHandler () {
|
||||
if (object.allDay) {
|
||||
object.date = new Date(object.date).setUTCHours(0, 0, 0, 0)
|
||||
object.dueDate = new Date(object.dueDate).setUTCHours(0, 0, 0, 0)
|
||||
} else {
|
||||
object.dueDate = object.date + defaultDuration
|
||||
}
|
||||
await tick()
|
||||
dueDateRef.adaptValue()
|
||||
await updateDate()
|
||||
}
|
||||
|
||||
let dueDateRef: DateRangePresenter
|
||||
|
||||
function showMenu (ev?: MouseEvent): void {
|
||||
if (object !== undefined) {
|
||||
showPopup(ContextMenu, { object, excludedActions: [view.action.Open] }, ev?.target as HTMLElement)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
{#if object.attachedTo && object.attachedToClass}
|
||||
<Panel
|
||||
icon={calendar.icon.Calendar}
|
||||
title={object.title}
|
||||
{object}
|
||||
isAside={false}
|
||||
isHeader={false}
|
||||
on:open
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
withoutActivity={true}
|
||||
withoutInput={true}
|
||||
>
|
||||
<svelte:fragment slot="utils">
|
||||
<div class="p-1">
|
||||
<Button icon={IconMoreH} kind={'ghost'} size={'medium'} on:click={showMenu} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if doc}
|
||||
<div class="mb-4">
|
||||
<div class="flex-row-center p-1">
|
||||
<Label label={calendar.string.EventFor} />
|
||||
@ -72,7 +162,7 @@
|
||||
{/if}
|
||||
<div class="mb-2">
|
||||
<div class="mb-4">
|
||||
<StylishEdit
|
||||
<EditBox
|
||||
label={calendar.string.Title}
|
||||
bind:value={object.title}
|
||||
on:change={() => client.update(object, { title: object.title })}
|
||||
@ -90,4 +180,32 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<div>
|
||||
<ToggleWithLabel bind:on={object.allDay} label={calendar.string.AllDay} on:change={allDayChangeHandler} />
|
||||
</div>
|
||||
<div>
|
||||
<DateRangePresenter
|
||||
value={object.date}
|
||||
labelNull={ui.string.SelectDate}
|
||||
on:change={async (event) => await handleNewStartDate(event.detail)}
|
||||
{mode}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
editable
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<DateRangePresenter
|
||||
bind:this={dueDateRef}
|
||||
value={object.dueDate}
|
||||
labelNull={calendar.string.DueTo}
|
||||
on:change={async (event) => await handleNewDueDate(event.detail)}
|
||||
{mode}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
editable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
|
295
plugins/calendar-resources/src/components/EditRecEvent.svelte
Normal file
295
plugins/calendar-resources/src/components/EditRecEvent.svelte
Normal file
@ -0,0 +1,295 @@
|
||||
<!--
|
||||
// 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 { Event, ReccuringInstance, generateEventId } from '@hcengineering/calendar'
|
||||
import { DateRangeMode, Doc, DocumentUpdate, WithLookup } from '@hcengineering/core'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import ui, {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DateRangePresenter,
|
||||
EditBox,
|
||||
IconMoreH,
|
||||
Label,
|
||||
ToggleWithLabel,
|
||||
closePopup,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ContextMenu, ObjectPresenter, getObjectPreview } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
import UpdateRecInstancePopup from './UpdateRecInstancePopup.svelte'
|
||||
|
||||
export let object: WithLookup<ReccuringInstance>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
const query = createQuery()
|
||||
let doc: Doc | undefined
|
||||
|
||||
$: if (object.attachedTo !== calendar.ids.NoAttached) {
|
||||
query.query(object.attachedToClass, { _id: object.attachedTo }, (res) => {
|
||||
doc = res[0]
|
||||
})
|
||||
}
|
||||
|
||||
let presenter: AnyComponent | undefined
|
||||
async function updatePreviewPresenter (doc?: Doc): Promise<void> {
|
||||
if (doc === undefined) {
|
||||
return
|
||||
}
|
||||
presenter = doc !== undefined ? await getObjectPreview(client, doc._class) : undefined
|
||||
}
|
||||
|
||||
$: updatePreviewPresenter(doc)
|
||||
|
||||
$: mode = object.allDay ? DateRangeMode.DATE : DateRangeMode.DATETIME
|
||||
|
||||
const defaultDuration = 30 * 60 * 1000
|
||||
const allDayDuration = 24 * 60 * 60 * 1000
|
||||
let duration = object.dueDate - object.date
|
||||
|
||||
async function updatePast (ops: DocumentUpdate<Event>) {
|
||||
const origin = await client.findOne(calendar.class.ReccuringEvent, {
|
||||
eventId: object.recurringEventId,
|
||||
space: object.space
|
||||
})
|
||||
if (origin !== undefined) {
|
||||
await client.addCollection(
|
||||
calendar.class.ReccuringEvent,
|
||||
origin.space,
|
||||
origin.attachedTo,
|
||||
origin.attachedToClass,
|
||||
origin.collection,
|
||||
{
|
||||
...origin,
|
||||
date: object.date,
|
||||
dueDate: object.dueDate,
|
||||
...ops,
|
||||
eventId: generateEventId()
|
||||
}
|
||||
)
|
||||
const targetDate = ops.date ?? object.date
|
||||
await client.update(origin, {
|
||||
rules: [{ ...origin.rules[0], endDate: targetDate - 1 }],
|
||||
rdate: origin.rdate.filter((p) => p < targetDate)
|
||||
})
|
||||
const instances = await client.findAll(calendar.class.ReccuringInstance, {
|
||||
recurringEventId: origin.eventId,
|
||||
date: { $gte: targetDate }
|
||||
})
|
||||
for (const instance of instances) {
|
||||
await client.remove(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateHandler (ops: DocumentUpdate<Event>) {
|
||||
if (object.virtual !== true) {
|
||||
await client.update(object, ops)
|
||||
} else {
|
||||
showPopup(UpdateRecInstancePopup, {}, undefined, async (res) => {
|
||||
if (res !== null) {
|
||||
if (res.mode === 'current') {
|
||||
await client.addCollection(
|
||||
object._class,
|
||||
object.space,
|
||||
object.attachedTo,
|
||||
object.attachedToClass,
|
||||
object.collection,
|
||||
{
|
||||
title: object.title,
|
||||
description: object.description,
|
||||
date: object.date,
|
||||
dueDate: object.dueDate,
|
||||
allDay: object.allDay,
|
||||
participants: object.participants,
|
||||
externalParticipants: object.externalParticipants,
|
||||
originalStartTime: object.originalStartTime,
|
||||
recurringEventId: object.recurringEventId,
|
||||
reminders: object.reminders,
|
||||
location: object.location,
|
||||
eventId: object.eventId,
|
||||
access: 'owner'
|
||||
},
|
||||
object._id
|
||||
)
|
||||
} else if (res.mode === 'all') {
|
||||
const base = await client.findOne(calendar.class.ReccuringEvent, {
|
||||
space: object.space,
|
||||
eventId: object.recurringEventId
|
||||
})
|
||||
if (base !== undefined) {
|
||||
await client.update(base, ops)
|
||||
}
|
||||
} else if (res.mode === 'next') {
|
||||
await updatePast(ops)
|
||||
}
|
||||
}
|
||||
closePopup()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function updateDate () {
|
||||
await updateHandler({
|
||||
date: object.date,
|
||||
dueDate: object.dueDate,
|
||||
allDay: object.allDay
|
||||
})
|
||||
}
|
||||
|
||||
async function handleNewStartDate (newStartDate: number | null) {
|
||||
if (newStartDate !== null) {
|
||||
object.date = newStartDate
|
||||
object.dueDate = object.date + (object.allDay ? allDayDuration : duration)
|
||||
if (object.allDay) {
|
||||
object.date = new Date(object.date).setUTCHours(0, 0, 0, 0)
|
||||
object.dueDate = new Date(object.dueDate).setUTCHours(0, 0, 0, 0)
|
||||
}
|
||||
await tick()
|
||||
dueDateRef.adaptValue()
|
||||
await updateDate()
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNewDueDate (newDueDate: number | null) {
|
||||
if (newDueDate !== null) {
|
||||
const diff = newDueDate - object.date
|
||||
if (diff > 0) {
|
||||
object.dueDate = newDueDate
|
||||
duration = diff
|
||||
} else {
|
||||
object.dueDate = object.date + (object.allDay ? allDayDuration : duration)
|
||||
}
|
||||
if (object.allDay) {
|
||||
object.date = new Date(object.date).setUTCHours(0, 0, 0, 0)
|
||||
object.dueDate = new Date(object.dueDate).setUTCHours(0, 0, 0, 0)
|
||||
}
|
||||
await tick()
|
||||
dueDateRef.adaptValue()
|
||||
await updateDate()
|
||||
}
|
||||
}
|
||||
|
||||
async function allDayChangeHandler () {
|
||||
if (object.allDay) {
|
||||
object.date = new Date(object.date).setUTCHours(0, 0, 0, 0)
|
||||
object.dueDate = new Date(object.dueDate).setUTCHours(0, 0, 0, 0)
|
||||
} else {
|
||||
object.dueDate = object.date + defaultDuration
|
||||
}
|
||||
await tick()
|
||||
dueDateRef.adaptValue()
|
||||
await updateDate()
|
||||
}
|
||||
|
||||
let dueDateRef: DateRangePresenter
|
||||
|
||||
function showMenu (ev?: MouseEvent): void {
|
||||
if (object !== undefined) {
|
||||
showPopup(ContextMenu, { object, excludedActions: [view.action.Open] }, ev?.target as HTMLElement)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Panel
|
||||
icon={calendar.icon.Calendar}
|
||||
title={object.title}
|
||||
{object}
|
||||
isAside={false}
|
||||
isHeader={false}
|
||||
on:open
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
withoutActivity={true}
|
||||
withoutInput={true}
|
||||
>
|
||||
<svelte:fragment slot="utils">
|
||||
<div class="p-1">
|
||||
<Button icon={IconMoreH} kind={'ghost'} size={'medium'} on:click={showMenu} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if doc}
|
||||
<div class="mb-4">
|
||||
<div class="flex-row-center p-1">
|
||||
<Label label={calendar.string.EventFor} />
|
||||
<div class="ml-2">
|
||||
<ObjectPresenter _class={object.attachedToClass} objectId={object.attachedTo} value={doc} />
|
||||
</div>
|
||||
</div>
|
||||
{#if presenter !== undefined && doc}
|
||||
<div class="antiPanel p-4">
|
||||
<Component is={presenter} props={{ object: doc }} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mb-2">
|
||||
<div class="mb-4">
|
||||
<EditBox
|
||||
label={calendar.string.Title}
|
||||
bind:value={object.title}
|
||||
on:change={() => updateHandler({ title: object.title })}
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<StyledTextBox
|
||||
kind={'emphasized'}
|
||||
content={object.description}
|
||||
on:value={(evt) => {
|
||||
updateHandler({ description: evt.detail })
|
||||
}}
|
||||
label={calendar.string.Description}
|
||||
placeholder={calendar.string.Description}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<div>
|
||||
<ToggleWithLabel bind:on={object.allDay} label={calendar.string.AllDay} on:change={allDayChangeHandler} />
|
||||
</div>
|
||||
<div>
|
||||
<DateRangePresenter
|
||||
value={object.date}
|
||||
labelNull={ui.string.SelectDate}
|
||||
on:change={async (event) => await handleNewStartDate(event.detail)}
|
||||
{mode}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
editable
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<DateRangePresenter
|
||||
bind:this={dueDateRef}
|
||||
value={object.dueDate}
|
||||
labelNull={calendar.string.DueTo}
|
||||
on:change={async (event) => await handleNewDueDate(event.detail)}
|
||||
{mode}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
editable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
@ -14,24 +14,35 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { DateTimeRangePresenter, showPanel } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { DatePresenter, DateTimeRangePresenter, showPopup } from '@hcengineering/ui'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let value: Event
|
||||
export let inline: boolean = false
|
||||
|
||||
function click (): void {
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'content')
|
||||
showPopup(
|
||||
value._class === calendar.class.ReccuringInstance
|
||||
? calendar.component.EditRecEvent
|
||||
: calendar.component.EditEvent,
|
||||
{ object: value },
|
||||
'content'
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="antiSelect w-full cursor-pointer flex-center flex-between" on:click={click}>
|
||||
{#if value}
|
||||
<div class="mr-4">
|
||||
{value.title}
|
||||
</div>
|
||||
{#if !inline}
|
||||
<DateTimeRangePresenter value={value.date} />
|
||||
{#if value.allDay}
|
||||
<DatePresenter value={value.date} />
|
||||
{:else}
|
||||
<DateTimeRangePresenter value={value.date} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -14,23 +14,11 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Event } from '@hcengineering/calendar'
|
||||
import { Class, Doc, DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
|
||||
import { Table } from '@hcengineering/view-resources'
|
||||
import EventPresenter from './EventPresenter.svelte'
|
||||
|
||||
export let value: Event[]
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let query: DocumentQuery<Event> = {}
|
||||
export let options: FindOptions<Event> | undefined = undefined
|
||||
export let baseMenuClass: Ref<Class<Event>> | undefined = undefined
|
||||
export let config: string[]
|
||||
</script>
|
||||
|
||||
<Table
|
||||
{_class}
|
||||
{config}
|
||||
{baseMenuClass}
|
||||
{options}
|
||||
query={{ ...query, _id: { $in: value.map((it) => it._id) } }}
|
||||
loadingProps={{ length: value.length ?? 0 }}
|
||||
/>
|
||||
{#each value as val}
|
||||
<EventPresenter value={val} />
|
||||
{/each}
|
||||
|
@ -47,7 +47,7 @@
|
||||
}
|
||||
|
||||
function getHeight (e: Event, date: Date): string {
|
||||
if (e.dueDate !== undefined && new Date(e.dueDate).getHours() === date.getHours()) {
|
||||
if (new Date(e.dueDate).getHours() === date.getHours()) {
|
||||
return `${(new Date(e.dueDate).getMinutes() / 60) * 100}%`
|
||||
}
|
||||
return '100%'
|
||||
@ -75,9 +75,10 @@
|
||||
return total
|
||||
}
|
||||
|
||||
function getStyle (events: Event[], i: number, date: Date): string {
|
||||
function getStyle (events: Event[], i: number, date: Date, dark: boolean): string {
|
||||
const e = events[i]
|
||||
let res = `background-color: ${getPlatformColorForTextDef(e._class, $themeStore.dark).background};`
|
||||
const color = getPlatformColorForTextDef(e.space, dark)
|
||||
let res = `background: ${color.color}33; border: 1px solid ${color.color}99; border-left: 0.25rem solid ${color.color}; `
|
||||
res += `left: ${getShift(events, i)}rem;`
|
||||
if (startCell(e.date, date)) {
|
||||
res += ` top: ${getTop(e)};`
|
||||
@ -110,7 +111,7 @@
|
||||
class:isStart={startCell(e.date, date)}
|
||||
class:isEnd={endCell(e.dueDate ?? e.date, date)}
|
||||
class:wide
|
||||
style={getStyle(events, i, date)}
|
||||
style={getStyle(events, i, date, $themeStore.dark)}
|
||||
on:click|stopPropagation={() => {
|
||||
showPanel(view.component.EditDoc, e._id, e._class, 'content')
|
||||
}}
|
||||
@ -130,13 +131,10 @@
|
||||
padding: 0 0.5rem;
|
||||
height: 100%;
|
||||
border: 1px solid #00000033;
|
||||
width: 1rem;
|
||||
&.wide {
|
||||
width: 5rem;
|
||||
&.isStart {
|
||||
.title {
|
||||
visibility: visible;
|
||||
}
|
||||
width: 100%;
|
||||
&.isStart {
|
||||
.title {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,12 +143,12 @@
|
||||
}
|
||||
|
||||
&.isStart {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-top-left-radius: 0.25rem;
|
||||
border-top-right-radius: 0.25rem;
|
||||
}
|
||||
&.isEnd {
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,111 @@
|
||||
<!--
|
||||
// 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 { getMetadata } from '@hcengineering/platform'
|
||||
import { Button, IconClose, Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
import { concatLink } from '@hcengineering/core'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let connecting = false
|
||||
const calendarUrl = getMetadata(calendar.metadata.CalendarServiceURL) ?? ''
|
||||
|
||||
async function sendRequest (): Promise<void> {
|
||||
connecting = true
|
||||
const link = concatLink(calendarUrl, '/signin')
|
||||
const url = new URL(link)
|
||||
url.search = new URLSearchParams({
|
||||
redirectURL: window.location.href
|
||||
}).toString()
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
const redirectTo = await res.text()
|
||||
window.open(redirectTo, '_self')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card">
|
||||
<div class="flex-between header">
|
||||
<div class="overflow-label fs-title"><Label label={calendar.string.ConnectCalendar} /></div>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="tool"
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<IconClose size={'small'} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<Label label={calendar.string.RedirectGoogle} />
|
||||
<div class="footer">
|
||||
<Button label={calendar.string.Connect} kind={'accented'} disabled={connecting} on:click={sendRequest} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 20rem;
|
||||
min-width: 20rem;
|
||||
max-width: 20rem;
|
||||
background-color: var(--popup-bg-hover);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: var(--popup-shadow);
|
||||
|
||||
.header {
|
||||
flex-shrink: 0;
|
||||
margin: 1.75rem 1.75rem 1.25rem;
|
||||
|
||||
.tool {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
&:active {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 1;
|
||||
height: fit-content;
|
||||
margin: 0 1.75rem 0.5rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,12 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Reminder } from '@hcengineering/calendar'
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { DateTimeRangePresenter, showPanel, tooltip } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
export let value: Reminder
|
||||
export let value: Event
|
||||
|
||||
function click (): void {
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'content')
|
||||
@ -34,6 +34,10 @@
|
||||
{value.title}
|
||||
</div>
|
||||
{/await}
|
||||
<DateTimeRangePresenter value={value.date + value.shift} />
|
||||
<div class="flex-row-center">
|
||||
{#each value.reminders ?? [] as reminder}
|
||||
<DateTimeRangePresenter value={value.date + reminder} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -44,9 +44,10 @@
|
||||
return
|
||||
}
|
||||
|
||||
await client.updateMixin(event._id, event._class, event.space, calendar.mixin.Reminder, {
|
||||
shift,
|
||||
state: 'active'
|
||||
await client.update(event, {
|
||||
$push: {
|
||||
reminders: shift
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
@ -0,0 +1,108 @@
|
||||
<!--
|
||||
// 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 presentation from '@hcengineering/presentation'
|
||||
import {
|
||||
Button,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
FocusHandler,
|
||||
Label,
|
||||
createFocusManager
|
||||
} from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
|
||||
export let label: IntlString = calendar.string.EditRecEvent
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const manager = createFocusManager()
|
||||
|
||||
const items: DropdownIntlItem[] = [
|
||||
{
|
||||
id: 'current',
|
||||
label: calendar.string.ThisEvent
|
||||
},
|
||||
{
|
||||
id: 'next',
|
||||
label: calendar.string.ThisAndNext
|
||||
},
|
||||
{
|
||||
id: 'all',
|
||||
label: calendar.string.AllEvents
|
||||
}
|
||||
]
|
||||
|
||||
let selected = 'current'
|
||||
</script>
|
||||
|
||||
<FocusHandler {manager} />
|
||||
|
||||
<div class="msgbox-container">
|
||||
<div class="overflow-label fs-title mb-4"><Label {label} /></div>
|
||||
<div>
|
||||
<DropdownLabelsIntl {items} bind:selected />
|
||||
</div>
|
||||
<div class="footer">
|
||||
<Button
|
||||
focus
|
||||
focusIndex={1}
|
||||
label={presentation.string.Ok}
|
||||
size={'large'}
|
||||
kind={'accented'}
|
||||
on:click={() => {
|
||||
dispatch('close', { mode: selected })
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
focusIndex={2}
|
||||
label={presentation.string.Cancel}
|
||||
size={'large'}
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.msgbox-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem 1.75rem 1.75rem;
|
||||
width: 30rem;
|
||||
max-width: 40rem;
|
||||
background: var(--theme-popup-color);
|
||||
border-radius: 0.5rem;
|
||||
user-select: none;
|
||||
box-shadow: var(--theme-popup-shadow);
|
||||
|
||||
.message {
|
||||
margin-bottom: 1.75rem;
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
direction: rtl;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,14 +13,14 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Event, Reminder } from '@hcengineering/calendar'
|
||||
import { Ref, TxMixin } from '@hcengineering/core'
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { Ref, TxUpdateDoc } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { DateTimePresenter, showPanel } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import calendar from '../../plugin'
|
||||
|
||||
export let tx: TxMixin<Event, Reminder>
|
||||
export let tx: TxUpdateDoc<Event>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
|
@ -0,0 +1,67 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 36 36"
|
||||
style="enable-background:new 0 0 36 36;"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
fill: #f1bf42;
|
||||
}
|
||||
.st1 {
|
||||
fill: #3265cb;
|
||||
}
|
||||
.st2 {
|
||||
fill: #5383ec;
|
||||
}
|
||||
.st3 {
|
||||
fill: #3c7e40;
|
||||
}
|
||||
.st4 {
|
||||
fill: #58a65c;
|
||||
}
|
||||
.st5 {
|
||||
fill: #e34134;
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<rect x="25.5" y="10.6" class="st0" width="6.6" height="14.8" />
|
||||
<path class="st1" d="M32.1,10.6v-4C32.1,5.1,31,4,29.5,4h-4v6.6H32.1z" />
|
||||
<path class="st2" d="M10.6,10.6h14.9V4H6.6C5.1,4,4,5.1,4,6.6v18.8h6.6V10.6z" />
|
||||
<path class="st3" d="M4,25.4v3.9C4,30.9,5.1,32,6.6,32h4v-6.6" />
|
||||
<rect x="10.6" y="25.4" class="st4" width="14.9" height="6.6" />
|
||||
<polygon class="st5" points="25.5,25.4 25.5,32 25.5,32 32.1,25.4 32.1,25.4 " />
|
||||
<g>
|
||||
<path
|
||||
class="st2"
|
||||
d="M12.5,20l1.6-0.2c0.1,0.4,0.2,0.7,0.4,0.9S15,21,15.3,21c0.3,0,0.6-0.1,0.9-0.4s0.3-0.6,0.3-1
|
||||
c0-0.4-0.1-0.7-0.3-1s-0.5-0.4-0.8-0.4c-0.2,0-0.5,0-0.8,0.1l0.2-1.3c0.4,0,0.8-0.1,1-0.3s0.4-0.5,0.4-0.8c0-0.3-0.1-0.5-0.3-0.7
|
||||
S15.5,15,15.3,15c-0.3,0-0.5,0.1-0.7,0.3s-0.3,0.5-0.4,0.9l-1.5-0.3c0.1-0.5,0.3-0.9,0.5-1.2s0.5-0.6,0.9-0.7s0.8-0.3,1.3-0.3
|
||||
c0.8,0,1.5,0.3,1.9,0.8c0.4,0.4,0.6,0.9,0.6,1.4c0,0.8-0.4,1.4-1.2,1.8c0.5,0.1,0.9,0.3,1.2,0.7s0.4,0.8,0.4,1.3
|
||||
c0,0.8-0.3,1.4-0.8,1.9s-1.2,0.8-2.1,0.8c-0.8,0-1.4-0.2-1.9-0.7S12.6,20.7,12.5,20z"
|
||||
/>
|
||||
<path
|
||||
class="st2"
|
||||
d="M23.5,22.2h-1.6V16c-0.6,0.6-1.3,1-2.1,1.2v-1.5c0.4-0.1,0.9-0.4,1.4-0.8s0.8-0.9,1-1.4h1.3V22.2z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -13,25 +13,118 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import { Resources } from '@hcengineering/platform'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import { ReccuringInstance } from '@hcengineering/calendar'
|
||||
import { Doc, TxOperations, concatLink } from '@hcengineering/core'
|
||||
import { Resources, getMetadata } from '@hcengineering/platform'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { closePopup, showPopup } from '@hcengineering/ui'
|
||||
import CalendarView from './components/CalendarView.svelte'
|
||||
import SaveEventReminder from './components/SaveEventReminder.svelte'
|
||||
import CreateEvent from './components/CreateEvent.svelte'
|
||||
import DateTimePresenter from './components/DateTimePresenter.svelte'
|
||||
import DocReminder from './components/DocReminder.svelte'
|
||||
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
||||
import Events from './components/Events.svelte'
|
||||
import ReminderPresenter from './components/ReminderPresenter.svelte'
|
||||
import ReminderViewlet from './components/activity/ReminderViewlet.svelte'
|
||||
import EditEvent from './components/EditEvent.svelte'
|
||||
import EditRecEvent from './components/EditRecEvent.svelte'
|
||||
import EventPresenter from './components/EventPresenter.svelte'
|
||||
import CreateEvent from './components/CreateEvent.svelte'
|
||||
import Events from './components/Events.svelte'
|
||||
import IntegrationConnect from './components/IntegrationConnect.svelte'
|
||||
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
||||
import SaveEventReminder from './components/SaveEventReminder.svelte'
|
||||
import UpdateRecInstancePopup from './components/UpdateRecInstancePopup.svelte'
|
||||
import ReminderViewlet from './components/activity/ReminderViewlet.svelte'
|
||||
import CalendarIntegrationIcon from './components/icons/Calendar.svelte'
|
||||
import calendar from './plugin'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { deleteObjects } from '@hcengineering/view-resources'
|
||||
|
||||
async function saveEventReminder (object: Doc): Promise<void> {
|
||||
showPopup(SaveEventReminder, { objectId: object._id, objectClass: object._class })
|
||||
}
|
||||
|
||||
async function deleteRecHandler (res: any, object: ReccuringInstance): Promise<void> {
|
||||
const client = getClient()
|
||||
if (res.mode === 'current') {
|
||||
await client.addCollection(
|
||||
object._class,
|
||||
object.space,
|
||||
object.attachedTo,
|
||||
object.attachedToClass,
|
||||
object.collection,
|
||||
{
|
||||
eventId: object.eventId,
|
||||
title: object.title,
|
||||
description: object.description,
|
||||
date: object.date,
|
||||
dueDate: object.dueDate,
|
||||
allDay: object.allDay,
|
||||
participants: object.participants,
|
||||
externalParticipants: object.externalParticipants,
|
||||
originalStartTime: object.originalStartTime,
|
||||
recurringEventId: object.recurringEventId,
|
||||
reminders: object.reminders,
|
||||
location: object.location,
|
||||
isCancelled: true,
|
||||
access: 'owner'
|
||||
},
|
||||
object._id
|
||||
)
|
||||
} else if (res.mode === 'all') {
|
||||
const base = await client.findOne(calendar.class.ReccuringEvent, {
|
||||
space: object.space,
|
||||
eventId: object.recurringEventId
|
||||
})
|
||||
if (base !== undefined) {
|
||||
await client.remove(base)
|
||||
}
|
||||
} else if (res.mode === 'next') {
|
||||
await removePast(client, object)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRecEvent (object: ReccuringInstance): Promise<void> {
|
||||
if (object.virtual === true) {
|
||||
showPopup(UpdateRecInstancePopup, { label: calendar.string.RemoveRecEvent }, undefined, async (res) => {
|
||||
if (res !== null) {
|
||||
await deleteRecHandler(res, object)
|
||||
closePopup()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
showPopup(
|
||||
contact.component.DeleteConfirmationPopup,
|
||||
{
|
||||
object,
|
||||
deleteAction: async () => {
|
||||
const objs = Array.isArray(object) ? object : [object]
|
||||
await deleteObjects(getClient(), objs).catch((err) => console.error(err))
|
||||
closePopup()
|
||||
}
|
||||
},
|
||||
undefined
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function removePast (client: TxOperations, object: ReccuringInstance): Promise<void> {
|
||||
const origin = await client.findOne(calendar.class.ReccuringEvent, {
|
||||
eventId: object.recurringEventId,
|
||||
space: object.space
|
||||
})
|
||||
if (origin !== undefined) {
|
||||
const target = object.date
|
||||
await client.update(origin, {
|
||||
rules: [{ ...origin.rules[0], endDate: target - 1 }],
|
||||
rdate: origin.rdate.filter((p) => p < target)
|
||||
})
|
||||
const instances = await client.findAll(calendar.class.ReccuringInstance, {
|
||||
eventId: origin.eventId,
|
||||
date: { $gte: target }
|
||||
})
|
||||
for (const instance of instances) {
|
||||
await client.remove(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export enum CalendarMode {
|
||||
Day,
|
||||
Week,
|
||||
@ -42,19 +135,36 @@ export enum CalendarMode {
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
EditEvent,
|
||||
ReminderPresenter,
|
||||
EditRecEvent,
|
||||
PersonsPresenter,
|
||||
CalendarView,
|
||||
Events,
|
||||
DateTimePresenter,
|
||||
DocReminder,
|
||||
EventPresenter,
|
||||
CreateEvent
|
||||
CreateEvent,
|
||||
IntegrationConnect,
|
||||
CalendarIntegrationIcon
|
||||
},
|
||||
activity: {
|
||||
ReminderViewlet
|
||||
},
|
||||
actionImpl: {
|
||||
SaveEventReminder: saveEventReminder
|
||||
SaveEventReminder: saveEventReminder,
|
||||
DeleteRecEvent: deleteRecEvent
|
||||
},
|
||||
handler: {
|
||||
DisconnectHandler: async () => {
|
||||
const url = getMetadata(calendar.metadata.CalendarServiceURL)
|
||||
const token = getMetadata(presentation.metadata.Token)
|
||||
if (url === undefined || token === undefined) return
|
||||
await fetch(concatLink(url, '/signout'), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -19,7 +19,9 @@ import { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
export default mergeIds(calendarId, calendar, {
|
||||
component: {
|
||||
CreateEvent: '' as AnyComponent
|
||||
CreateEvent: '' as AnyComponent,
|
||||
EditEvent: '' as AnyComponent,
|
||||
EditRecEvent: '' as AnyComponent
|
||||
},
|
||||
activity: {
|
||||
ReminderViewlet: '' as AnyComponent
|
||||
@ -42,6 +44,17 @@ export default mergeIds(calendarId, calendar, {
|
||||
AllDay: '' as IntlString,
|
||||
AndMore: '' as IntlString,
|
||||
CreateEvent: '' as IntlString,
|
||||
EventFor: '' as IntlString
|
||||
EventFor: '' as IntlString,
|
||||
ReccuringEvent: '' as IntlString,
|
||||
HideDetails: '' as IntlString,
|
||||
ExternalParticipants: '' as IntlString,
|
||||
RedirectGoogle: '' as IntlString,
|
||||
Connect: '' as IntlString,
|
||||
ConnectCalendar: '' as IntlString,
|
||||
EditRecEvent: '' as IntlString,
|
||||
RemoveRecEvent: '' as IntlString,
|
||||
ThisEvent: '' as IntlString,
|
||||
ThisAndNext: '' as IntlString,
|
||||
AllEvents: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/calendar",
|
||||
"version": "0.6.11",
|
||||
"version": "0.6.12",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
@ -30,7 +30,8 @@
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/contact": "^0.6.18"
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/setting": "^0.6.9"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/anticrm",
|
||||
"publishConfig": {
|
||||
|
@ -11,12 +11,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import type { AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@hcengineering/core'
|
||||
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Contact } from '@hcengineering/contact'
|
||||
import type { AttachedDoc, Class, Doc, Markup, Ref, Space, Timestamp } from '@hcengineering/core'
|
||||
import { NotificationType } from '@hcengineering/notification'
|
||||
import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { Handler, IntegrationType } from '@hcengineering/setting'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -25,31 +26,72 @@ export interface Calendar extends Space {}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* RFC5545
|
||||
*/
|
||||
export interface Event extends AttachedDoc {
|
||||
title: string
|
||||
description: Markup
|
||||
|
||||
location?: string
|
||||
|
||||
// Event scheduled date
|
||||
date: Timestamp
|
||||
|
||||
// Event due date for long events.
|
||||
dueDate?: Timestamp
|
||||
|
||||
attachments?: number
|
||||
comments?: number
|
||||
|
||||
participants?: Ref<Employee>[]
|
||||
export interface RecurringRule {
|
||||
freq: 'SECONDLY' | 'MINUTELY' | 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'
|
||||
endDate?: Timestamp
|
||||
count?: number
|
||||
interval?: number
|
||||
bySecond?: number[]
|
||||
byMinute?: number[]
|
||||
byHour?: number[]
|
||||
byDay?: string[]
|
||||
byMonthDay?: number[]
|
||||
byYearDay?: number[]
|
||||
byWeekNo?: number[]
|
||||
byMonth?: number[]
|
||||
bySetPos?: number[]
|
||||
wkst?: 'SU' | 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA'
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Reminder extends Event {
|
||||
shift: Timestamp
|
||||
state: 'active' | 'done'
|
||||
export interface ReccuringEvent extends Event {
|
||||
rules: RecurringRule[]
|
||||
exdate: Timestamp[]
|
||||
rdate: Timestamp[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Event extends AttachedDoc {
|
||||
eventId: string
|
||||
title: string
|
||||
description: Markup
|
||||
|
||||
location?: string
|
||||
|
||||
allDay: boolean
|
||||
|
||||
// Event scheduled date
|
||||
date: Timestamp
|
||||
|
||||
// Event due date for long events.
|
||||
dueDate: Timestamp
|
||||
|
||||
attachments?: number
|
||||
|
||||
participants: Ref<Contact>[]
|
||||
|
||||
externalParticipants?: string[]
|
||||
|
||||
reminders?: Timestamp[]
|
||||
|
||||
access: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* use for an instance of a recurring event
|
||||
*/
|
||||
export interface ReccuringInstance extends Event {
|
||||
recurringEventId: string
|
||||
originalStartTime: number
|
||||
isCancelled?: boolean
|
||||
virtual?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,10 +105,9 @@ export const calendarId = 'calendar' as Plugin
|
||||
const calendarPlugin = plugin(calendarId, {
|
||||
class: {
|
||||
Calendar: '' as Ref<Class<Calendar>>,
|
||||
Event: '' as Ref<Class<Event>>
|
||||
},
|
||||
mixin: {
|
||||
Reminder: '' as Ref<Mixin<Reminder>>
|
||||
Event: '' as Ref<Class<Event>>,
|
||||
ReccuringEvent: '' as Ref<Class<ReccuringEvent>>,
|
||||
ReccuringInstance: '' as Ref<Class<ReccuringInstance>>
|
||||
},
|
||||
icon: {
|
||||
Calendar: '' as Asset,
|
||||
@ -75,7 +116,7 @@ const calendarPlugin = plugin(calendarId, {
|
||||
Notifications: '' as Asset
|
||||
},
|
||||
space: {
|
||||
// Space for all personal events.
|
||||
// deprecated
|
||||
PersonalEvents: '' as Ref<Space>
|
||||
},
|
||||
app: {
|
||||
@ -102,6 +143,15 @@ const calendarPlugin = plugin(calendarId, {
|
||||
EventNumber: '' as IntlString,
|
||||
Reminders: '' as IntlString
|
||||
},
|
||||
handler: {
|
||||
DisconnectHandler: '' as Handler
|
||||
},
|
||||
integrationType: {
|
||||
Calendar: '' as Ref<IntegrationType>
|
||||
},
|
||||
metadata: {
|
||||
CalendarServiceURL: '' as Metadata<string>
|
||||
},
|
||||
ids: {
|
||||
ReminderNotification: '' as Ref<NotificationType>,
|
||||
NoAttached: '' as Ref<Event>
|
||||
@ -109,3 +159,4 @@ const calendarPlugin = plugin(calendarId, {
|
||||
})
|
||||
|
||||
export default calendarPlugin
|
||||
export * from './utils'
|
||||
|
316
plugins/calendar/src/utils.ts
Normal file
316
plugins/calendar/src/utils.ts
Normal file
@ -0,0 +1,316 @@
|
||||
import { Timestamp, generateId } from '@hcengineering/core'
|
||||
import calendar, { Event, ReccuringEvent, ReccuringInstance, RecurringRule } from '.'
|
||||
|
||||
function getInstance (event: ReccuringEvent, date: Timestamp): ReccuringInstance {
|
||||
const diff = event.dueDate - event.date
|
||||
return {
|
||||
...event,
|
||||
recurringEventId: event.eventId,
|
||||
date,
|
||||
dueDate: date + diff,
|
||||
originalStartTime: date,
|
||||
_class: calendar.class.ReccuringInstance,
|
||||
eventId: generateEventId(),
|
||||
_id: generateId(),
|
||||
virtual: true
|
||||
}
|
||||
}
|
||||
|
||||
function generateRecurringValues (
|
||||
rule: RecurringRule,
|
||||
startDate: Timestamp,
|
||||
from: Timestamp,
|
||||
to: Timestamp
|
||||
): Timestamp[] {
|
||||
const values: Timestamp[] = []
|
||||
const currentDate = new Date(startDate)
|
||||
|
||||
switch (rule.freq) {
|
||||
case 'DAILY':
|
||||
generateDailyValues(rule, currentDate, values, from, to)
|
||||
break
|
||||
case 'WEEKLY':
|
||||
generateWeeklyValues(rule, currentDate, values, from, to)
|
||||
break
|
||||
case 'MONTHLY':
|
||||
generateMonthlyValues(rule, currentDate, values, from, to)
|
||||
break
|
||||
case 'YEARLY':
|
||||
generateYearlyValues(rule, currentDate, values, from, to)
|
||||
break
|
||||
default:
|
||||
throw new Error('Invalid recurring rule frequency')
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
function generateDailyValues (
|
||||
rule: RecurringRule,
|
||||
currentDate: Date,
|
||||
values: Timestamp[],
|
||||
from: Timestamp,
|
||||
to: Timestamp
|
||||
): void {
|
||||
const { count, endDate, interval } = rule
|
||||
const { bySecond, byMinute, byHour, bySetPos } = rule
|
||||
let i = 0
|
||||
|
||||
while (true) {
|
||||
if (
|
||||
(bySecond == null || bySecond.includes(currentDate.getSeconds())) &&
|
||||
(byMinute == null || byMinute.includes(currentDate.getMinutes())) &&
|
||||
(byHour == null || byHour.includes(currentDate.getHours())) &&
|
||||
(bySetPos == null || bySetPos.includes(getSetPos(currentDate)))
|
||||
) {
|
||||
const res = currentDate.getTime()
|
||||
if (res > from) {
|
||||
values.push(res)
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
currentDate.setDate(currentDate.getDate() + (interval ?? 1))
|
||||
if (count !== undefined && i === count) break
|
||||
if (endDate !== undefined && currentDate.getTime() > endDate) break
|
||||
if (currentDate.getTime() > to) break
|
||||
}
|
||||
}
|
||||
|
||||
function generateWeeklyValues (
|
||||
rule: RecurringRule,
|
||||
currentDate: Date,
|
||||
values: Timestamp[],
|
||||
from: Timestamp,
|
||||
to: Timestamp
|
||||
): void {
|
||||
const { count, endDate, interval } = rule
|
||||
const { bySecond, byMinute, byHour, byDay, wkst, bySetPos } = rule
|
||||
let i = 0
|
||||
|
||||
while (true) {
|
||||
if (
|
||||
(bySecond == null || bySecond.includes(currentDate.getSeconds())) &&
|
||||
(byMinute == null || byMinute.includes(currentDate.getMinutes())) &&
|
||||
(byHour == null || byHour.includes(currentDate.getHours())) &&
|
||||
(byDay == null || byDay.includes(getWeekday(currentDate, wkst))) &&
|
||||
(bySetPos == null || bySetPos.includes(getSetPos(currentDate)))
|
||||
) {
|
||||
const res = currentDate.getTime()
|
||||
if (res > from) {
|
||||
values.push(res)
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
currentDate.setDate(currentDate.getDate() + (interval ?? 1) * 7)
|
||||
if (count !== undefined && i === count) break
|
||||
if (endDate !== undefined && currentDate.getTime() > endDate) break
|
||||
if (currentDate.getTime() > to) break
|
||||
}
|
||||
}
|
||||
|
||||
function generateMonthlyValues (
|
||||
rule: RecurringRule,
|
||||
currentDate: Date,
|
||||
values: Timestamp[],
|
||||
from: Timestamp,
|
||||
to: Timestamp
|
||||
): void {
|
||||
const { count, endDate, interval } = rule
|
||||
const { bySecond, byMinute, byHour, byDay, byMonthDay, bySetPos, wkst } = rule
|
||||
let i = 0
|
||||
|
||||
while (true) {
|
||||
if (
|
||||
(bySecond == null || bySecond.includes(currentDate.getSeconds())) &&
|
||||
(byMinute == null || byMinute.includes(currentDate.getMinutes())) &&
|
||||
(byHour == null || byHour.includes(currentDate.getHours())) &&
|
||||
(byDay == null || byDay.includes(getWeekday(currentDate, wkst))) &&
|
||||
(byMonthDay == null || byMonthDay.includes(new Date(currentDate).getDate())) &&
|
||||
(bySetPos == null || bySetPos.includes(getSetPos(currentDate)))
|
||||
) {
|
||||
const res = currentDate.getTime()
|
||||
if (res > from) {
|
||||
values.push(res)
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
currentDate.setMonth(currentDate.getMonth() + (interval ?? 1))
|
||||
if (count !== undefined && i === count) break
|
||||
if (endDate !== undefined && currentDate.getTime() > endDate) break
|
||||
if (currentDate.getTime() > to) break
|
||||
}
|
||||
}
|
||||
|
||||
function generateYearlyValues (
|
||||
rule: RecurringRule,
|
||||
currentDate: Date,
|
||||
values: Timestamp[],
|
||||
from: Timestamp,
|
||||
to: Timestamp
|
||||
): void {
|
||||
const { count, endDate, interval } = rule
|
||||
const { bySecond, byMinute, byHour, byDay, byMonthDay, byYearDay, byWeekNo, byMonth, bySetPos, wkst } = rule
|
||||
let i = 0
|
||||
|
||||
while (true) {
|
||||
if (
|
||||
(bySecond == null || bySecond.includes(currentDate.getSeconds())) &&
|
||||
(byMinute == null || byMinute.includes(currentDate.getMinutes())) &&
|
||||
(byHour == null || byHour.includes(currentDate.getHours())) &&
|
||||
(byDay == null || byDay.includes(getWeekday(currentDate, wkst))) &&
|
||||
(byMonthDay == null || byMonthDay.includes(currentDate.getDate())) &&
|
||||
(byYearDay == null || byYearDay.includes(getYearDay(currentDate))) &&
|
||||
(byWeekNo == null || byWeekNo.includes(getWeekNumber(currentDate))) &&
|
||||
(byMonth == null || byMonth.includes(currentDate.getMonth())) &&
|
||||
(bySetPos == null || bySetPos.includes(getSetPos(currentDate)))
|
||||
) {
|
||||
const res = currentDate.getTime()
|
||||
if (res > from) {
|
||||
values.push(res)
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
currentDate.setFullYear(currentDate.getFullYear() + (interval ?? 1))
|
||||
if (count !== undefined && i === count) break
|
||||
if (endDate !== undefined && currentDate.getTime() > endDate) break
|
||||
if (currentDate.getTime() > to) break
|
||||
}
|
||||
}
|
||||
|
||||
function getSetPos (date: Date): number {
|
||||
const month = date.getMonth()
|
||||
const year = date.getFullYear()
|
||||
const firstOfMonth = new Date(year, month, 1)
|
||||
const daysOffset = firstOfMonth.getDay()
|
||||
const day = date.getDate()
|
||||
|
||||
return Math.ceil((day + daysOffset) / 7)
|
||||
}
|
||||
|
||||
function getWeekday (date: Date, wkst?: string): string {
|
||||
const weekdays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']
|
||||
const weekday = weekdays[date.getDay()]
|
||||
|
||||
if (wkst !== undefined && wkst !== 'MO') {
|
||||
const wkstIndex = weekdays.indexOf(wkst)
|
||||
const offset = wkstIndex > 0 ? wkstIndex - 1 : 6
|
||||
|
||||
return weekdays[(date.getDay() + offset) % 7]
|
||||
}
|
||||
|
||||
return weekday
|
||||
}
|
||||
|
||||
function getWeekNumber (date: Date): number {
|
||||
const firstDayOfYear = new Date(date.getFullYear(), 0, 1)
|
||||
const daysOffset = firstDayOfYear.getDay()
|
||||
const diff = (date.getTime() - firstDayOfYear.getTime()) / 86400000
|
||||
|
||||
return Math.floor((diff + daysOffset + 1) / 7)
|
||||
}
|
||||
|
||||
function getYearDay (date: Date): number {
|
||||
const startOfYear = new Date(date.getFullYear(), 0, 0)
|
||||
const diff = date.getTime() - startOfYear.getTime()
|
||||
|
||||
return Math.floor(diff / 86400000)
|
||||
}
|
||||
|
||||
function getReccuringEventInstances (
|
||||
event: ReccuringEvent,
|
||||
instances: ReccuringInstance[],
|
||||
from: Timestamp,
|
||||
to: Timestamp
|
||||
): ReccuringInstance[] {
|
||||
let res: ReccuringInstance[] = []
|
||||
for (const rule of event.rules) {
|
||||
const values = generateRecurringValues(rule, event.date, from, to)
|
||||
for (const val of values) {
|
||||
const instance = getInstance(event, val)
|
||||
res.push(instance)
|
||||
}
|
||||
}
|
||||
for (const date of event.rdate) {
|
||||
if (date < from || date > to) continue
|
||||
const instance = getInstance(event, date)
|
||||
const exists = res.find((p) => p.date === instance.date)
|
||||
if (exists === undefined) res.push(instance)
|
||||
}
|
||||
|
||||
const excludes = new Set(event.exdate)
|
||||
res = res.filter((p) => !excludes.has(p.date))
|
||||
res = res.filter((i) => {
|
||||
const override = instances.find((p) => p.originalStartTime === i.date)
|
||||
return override === undefined
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getAllEvents (events: Event[], from: Timestamp, to: Timestamp): Event[] {
|
||||
const base: Event[] = []
|
||||
const recur: ReccuringEvent[] = []
|
||||
const instances: ReccuringInstance[] = []
|
||||
const recurData: ReccuringInstance[] = []
|
||||
const instancesMap: Map<string, ReccuringInstance[]> = new Map()
|
||||
for (const event of events) {
|
||||
if (event._class === calendar.class.Event) {
|
||||
base.push(event)
|
||||
} else if (event._class === calendar.class.ReccuringEvent) {
|
||||
recur.push(event as ReccuringEvent)
|
||||
} else if (event._class === calendar.class.ReccuringInstance) {
|
||||
const instance = event as ReccuringInstance
|
||||
instances.push(instance)
|
||||
const arr = instancesMap.get(instance.recurringEventId) ?? []
|
||||
arr.push(instance)
|
||||
instancesMap.set(instance.recurringEventId, arr)
|
||||
}
|
||||
}
|
||||
for (const rec of recur) {
|
||||
recurData.push(...getReccuringEventInstances(rec, instancesMap.get(rec.eventId) ?? [], from, to))
|
||||
}
|
||||
const res = [...base, ...recurData, ...instances]
|
||||
res.sort((a, b) => a.date - b.date)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function generateEventId (): string {
|
||||
const id = generateId()
|
||||
return encodeToBase32Hex(id)
|
||||
}
|
||||
|
||||
function encodeToBase32Hex (input: string): string {
|
||||
const encoder = new TextEncoder()
|
||||
const bytes = encoder.encode(input)
|
||||
const base32HexDigits = '0123456789abcdefghijklmnopqrstuv'
|
||||
let result = ''
|
||||
|
||||
for (let i = 0; i < bytes.length; i += 5) {
|
||||
const octets = [
|
||||
(bytes[i] ?? 0) >> 3,
|
||||
((bytes[i] & 0x07) << 2) | (bytes[i + 1] >> 6),
|
||||
(bytes[i + 1] >> 1) & 0x1f,
|
||||
((bytes[i + 1] & 0x01) << 4) | (bytes[i + 2] >> 4),
|
||||
((bytes[i + 2] & 0x0f) << 1) | (bytes[i + 3] >> 7),
|
||||
(bytes[i + 3] >> 2) & 0x1f,
|
||||
((bytes[i + 3] & 0x03) << 3) | (bytes[i + 4] >> 5),
|
||||
bytes[i + 4] & 0x1f
|
||||
]
|
||||
|
||||
for (let j = 0; j < 8; j++) {
|
||||
result += base32HexDigits.charAt(octets[j])
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
@ -44,7 +44,7 @@
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.8",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
|
@ -41,7 +41,7 @@
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.8",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
|
@ -48,7 +48,7 @@
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/templates": "^0.6.6"
|
||||
}
|
||||
}
|
||||
|
@ -91,8 +91,7 @@ export default plugin(gmailId, {
|
||||
Gmail: '' as Ref<IntegrationType>
|
||||
},
|
||||
handler: {
|
||||
DisconnectHandler: '' as Handler,
|
||||
ReconnectHandler: '' as Handler
|
||||
DisconnectHandler: '' as Handler
|
||||
},
|
||||
class: {
|
||||
Message: '' as Ref<Class<Message>>,
|
||||
|
@ -33,13 +33,13 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"svelte": "3.55.1",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/calendar-resources": "^0.6.0",
|
||||
"@hcengineering/hr": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
|
@ -36,7 +36,7 @@
|
||||
"svelte": "3.55.1",
|
||||
"@hcengineering/inventory": "^0.6.6",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
|
@ -37,7 +37,7 @@
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/task": "^0.6.10",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/recruit-assets",
|
||||
"version": "0.6.9",
|
||||
"version": "0.6.10",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"template": "@hcengineering/assets-package",
|
||||
@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/recruit": "^0.6.15"
|
||||
"@hcengineering/recruit": "^0.6.16"
|
||||
},
|
||||
"repository": "https://github.com/hcenginneing/anticrm",
|
||||
"publishConfig": {
|
||||
|
@ -34,7 +34,7 @@
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"svelte": "3.55.1",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
@ -42,7 +42,7 @@
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
"@hcengineering/workbench": "^0.6.8",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.8",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
@ -55,7 +55,7 @@
|
||||
"@hcengineering/rekoni": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
"@hcengineering/tags": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/tracker": "^0.6.10"
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import calendar from '@hcengineering/calendar'
|
||||
import contact, { Contact } from '@hcengineering/contact'
|
||||
import { UserBox } from '@hcengineering/contact-resources'
|
||||
import { Hierarchy } from '@hcengineering/core'
|
||||
@ -41,8 +40,7 @@
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', {
|
||||
ignoreKeys: ['number', 'comments', 'title', 'description', 'verdict'],
|
||||
ignoreMixins: [calendar.mixin.Reminder]
|
||||
ignoreKeys: ['number', 'comments', 'title', 'description', 'verdict']
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/recruit",
|
||||
"version": "0.6.15",
|
||||
"version": "0.6.16",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
@ -31,7 +31,7 @@
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"@hcengineering/task": "^0.6.10",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/tags": "^0.6.11"
|
||||
},
|
||||
|
@ -40,7 +40,7 @@
|
||||
"@hcengineering/attachment": "^0.6.8",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/task": "^0.6.10",
|
||||
|
@ -41,7 +41,7 @@
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"@hcengineering/attachment": "^0.6.8",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
|
@ -46,7 +46,7 @@
|
||||
"@hcengineering/notification-resources": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.8",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/templates": "^0.6.6"
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
"@hcengineering/setting": "^0.6.9",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/tags": "^0.6.11",
|
||||
"@hcengineering/task": "^0.6.10",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
@ -52,7 +52,7 @@
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/kanban": "^0.6.0",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.8",
|
||||
|
@ -36,7 +36,7 @@
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/model": "^0.6.6",
|
||||
"@hcengineering/panel": "^0.6.9",
|
||||
"@hcengineering/panel": "^0.6.10",
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
|
@ -41,7 +41,7 @@
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
"@hcengineering/setting": "^0.6.9",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/request": "^0.6.3",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
"@hcengineering/notification-resources": "^0.6.0",
|
||||
|
@ -90,7 +90,7 @@
|
||||
"@hcengineering/view-assets": "^0.6.6",
|
||||
"@hcengineering/task-assets": "^0.6.9",
|
||||
"@hcengineering/chunter-assets": "^0.6.7",
|
||||
"@hcengineering/recruit-assets": "^0.6.9",
|
||||
"@hcengineering/recruit-assets": "^0.6.10",
|
||||
"@hcengineering/setting-assets": "^0.6.7",
|
||||
"@hcengineering/contact-assets": "^0.6.7",
|
||||
"@hcengineering/activity-assets": "^0.6.1",
|
||||
@ -105,7 +105,7 @@
|
||||
"@hcengineering/notification-assets": "^0.6.8",
|
||||
"@hcengineering/preference-assets": "^0.6.0",
|
||||
"@hcengineering/tags-assets": "^0.6.0",
|
||||
"@hcengineering/calendar-assets": "^0.6.9",
|
||||
"@hcengineering/calendar-assets": "^0.6.10",
|
||||
"@hcengineering/tracker-assets": "^0.6.0",
|
||||
"@hcengineering/board-assets": "^0.6.9",
|
||||
"@hcengineering/hr-assets": "^0.6.9",
|
||||
@ -116,7 +116,7 @@
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/task": "^0.6.10",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"@hcengineering/setting": "^0.6.9",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
@ -131,12 +131,12 @@
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
"@hcengineering/preference": "^0.6.8",
|
||||
"@hcengineering/tags": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/tracker": "^0.6.10",
|
||||
"@hcengineering/board": "^0.6.9",
|
||||
"@hcengineering/hr": "^0.6.9",
|
||||
"@hcengineering/document": "^0.6.0",
|
||||
"@hcengineering/bitrix": "^0.6.38",
|
||||
"@hcengineering/bitrix": "^0.6.39",
|
||||
"@hcengineering/request": "^0.6.3"
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,8 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/calendar": "^0.6.11",
|
||||
"@hcengineering/calendar": "^0.6.12",
|
||||
"@hcengineering/contact": "^0.6.18",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0"
|
||||
}
|
||||
|
@ -13,11 +13,26 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import calendar, { Event } from '@hcengineering/calendar'
|
||||
import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } from '@hcengineering/core'
|
||||
import calendar, { Calendar, Event, ReccuringEvent } from '@hcengineering/calendar'
|
||||
import core, {
|
||||
Class,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
FindResult,
|
||||
Hierarchy,
|
||||
Ref,
|
||||
Tx,
|
||||
TxCUD,
|
||||
TxCreateDoc,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import { getHTMLPresenter, getTextPresenter } from '@hcengineering/server-notification-resources'
|
||||
import contact, { EmployeeAccount } from '@hcengineering/contact'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -31,14 +46,14 @@ export async function FindReminders (
|
||||
options?: FindOptions<T>
|
||||
) => Promise<FindResult<T>>
|
||||
): Promise<Doc[]> {
|
||||
const result = await findAll(calendar.mixin.Reminder, { attachedTo: doc._id })
|
||||
return result
|
||||
const events = await findAll(calendar.class.Event, { attachedTo: doc._id })
|
||||
return events
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function EventHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string | undefined> {
|
||||
export async function ReminderHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string | undefined> {
|
||||
const event = doc as Event
|
||||
const target = (await control.findAll(event.attachedToClass, { _id: event.attachedTo }, { limit: 1 }))[0]
|
||||
if (target !== undefined) {
|
||||
@ -52,7 +67,7 @@ export async function EventHTMLPresenter (doc: Doc, control: TriggerControl): Pr
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function EventTextPresenter (doc: Doc, control: TriggerControl): Promise<string | undefined> {
|
||||
export async function ReminderTextPresenter (doc: Doc, control: TriggerControl): Promise<string | undefined> {
|
||||
const event = doc as Event
|
||||
const target = (await control.findAll(event.attachedToClass, { _id: event.attachedTo }, { limit: 1 }))[0]
|
||||
if (target !== undefined) {
|
||||
@ -64,11 +79,154 @@ export async function EventTextPresenter (doc: Doc, control: TriggerControl): Pr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnEmployeeAccountCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<EmployeeAccount>
|
||||
const user = TxProcessor.createDoc2Doc(ctx)
|
||||
|
||||
const res: TxCreateDoc<Calendar> = control.txFactory.createTxCreateDoc(
|
||||
calendar.class.Calendar,
|
||||
core.space.Space,
|
||||
{
|
||||
name: user.email,
|
||||
description: '',
|
||||
archived: false,
|
||||
private: false,
|
||||
members: [user._id]
|
||||
},
|
||||
`${user._id}_calendar` as Ref<Calendar>,
|
||||
undefined,
|
||||
user._id
|
||||
)
|
||||
return [res]
|
||||
}
|
||||
|
||||
async function onEventCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Event>
|
||||
const ev = TxProcessor.createDoc2Doc(ctx)
|
||||
|
||||
const res: Tx[] = []
|
||||
const accounts = await control.modelDb.findAll(contact.class.EmployeeAccount, {})
|
||||
const participants = accounts.filter(
|
||||
(p) => (p._id !== ev.createdBy ?? ev.modifiedBy) && ev.participants.includes(p.employee)
|
||||
)
|
||||
for (const acc of participants) {
|
||||
const innerTx = control.txFactory.createTxCreateDoc(ev._class, `${acc._id}_calendar` as Ref<Calendar>, {
|
||||
eventId: ev.eventId,
|
||||
participants: ev.participants,
|
||||
externalParticipants: ev.externalParticipants,
|
||||
title: ev.title,
|
||||
description: ev.description,
|
||||
allDay: ev.allDay,
|
||||
attachedTo: ev.attachedTo,
|
||||
attachedToClass: ev.attachedToClass,
|
||||
collection: ev.collection,
|
||||
date: ev.date,
|
||||
dueDate: ev.dueDate,
|
||||
reminders: ev.reminders,
|
||||
location: ev.location,
|
||||
access: 'reader'
|
||||
})
|
||||
res.push(
|
||||
control.txFactory.createTxCollectionCUD(ev.attachedToClass, ev.attachedTo, ev.space, ev.collection, innerTx)
|
||||
)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async function onEventUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxUpdateDoc<Event>
|
||||
const ev = (await control.findAll(calendar.class.Event, { _id: ctx.objectId }))[0]
|
||||
|
||||
const res: Tx[] = []
|
||||
if (ev !== undefined) {
|
||||
const events = await control.findAll(calendar.class.Event, { eventId: ev.eventId, _id: { $ne: ev._id } })
|
||||
for (const event of events) {
|
||||
const innerTx = control.txFactory.createTxUpdateDoc(event._class, event.space, event._id, ctx.operations)
|
||||
res.push(
|
||||
control.txFactory.createTxCollectionCUD(
|
||||
event.attachedToClass,
|
||||
event.attachedTo,
|
||||
event.space,
|
||||
event.collection,
|
||||
innerTx
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
async function onEventRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc<Event>
|
||||
const event = control.removedMap.get(ctx.objectId)
|
||||
const res: Tx[] = []
|
||||
if (event !== undefined) {
|
||||
const events = await control.findAll(calendar.class.Event, { eventId: (event as Event).eventId })
|
||||
for (const event of events) {
|
||||
const innerTx = control.txFactory.createTxRemoveDoc(event._class, event.space, event._id)
|
||||
res.push(
|
||||
control.txFactory.createTxCollectionCUD(
|
||||
event.attachedToClass,
|
||||
event.attachedTo,
|
||||
event.space,
|
||||
event.collection,
|
||||
innerTx
|
||||
)
|
||||
)
|
||||
}
|
||||
if (event._class === calendar.class.ReccuringEvent) {
|
||||
const childs = await control.findAll(calendar.class.ReccuringInstance, {
|
||||
recurringEventId: (event as ReccuringEvent).eventId
|
||||
})
|
||||
for (const child of childs) {
|
||||
const innerTx = control.txFactory.createTxRemoveDoc(child._class, child.space, child._id)
|
||||
res.push(
|
||||
control.txFactory.createTxCollectionCUD(
|
||||
child.attachedToClass,
|
||||
child.attachedTo,
|
||||
child.space,
|
||||
child.collection,
|
||||
innerTx
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnEvent (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
if (tx.space === core.space.DerivedTx) return []
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCUD<Event>
|
||||
if (!control.hierarchy.isDerived(ctx.objectClass, calendar.class.Event)) return []
|
||||
switch (ctx._class) {
|
||||
case core.class.TxCreateDoc:
|
||||
return await onEventCreate(ctx, control)
|
||||
case core.class.TxUpdateDoc:
|
||||
return await onEventUpdate(ctx, control)
|
||||
case core.class.TxRemoveDoc:
|
||||
return await onEventRemove(ctx, control)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
function: {
|
||||
EventHTMLPresenter,
|
||||
EventTextPresenter,
|
||||
ReminderHTMLPresenter,
|
||||
ReminderTextPresenter,
|
||||
FindReminders
|
||||
},
|
||||
trigger: {
|
||||
OnEvent,
|
||||
OnEmployeeAccountCreate
|
||||
}
|
||||
})
|
||||
|
@ -28,6 +28,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/platform": "^0.6.9"
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } fr
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { Presenter } from '@hcengineering/server-notification'
|
||||
import type { TriggerFunc } from '@hcengineering/server-core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -29,8 +30,8 @@ export const serverCalendarId = 'server-calendar' as Plugin
|
||||
*/
|
||||
export default plugin(serverCalendarId, {
|
||||
function: {
|
||||
EventHTMLPresenter: '' as Resource<Presenter>,
|
||||
EventTextPresenter: '' as Resource<Presenter>,
|
||||
ReminderHTMLPresenter: '' as Resource<Presenter>,
|
||||
ReminderTextPresenter: '' as Resource<Presenter>,
|
||||
FindReminders: '' as Resource<
|
||||
(
|
||||
doc: Doc,
|
||||
@ -42,5 +43,9 @@ export default plugin(serverCalendarId, {
|
||||
) => Promise<FindResult<T>>
|
||||
) => Promise<Doc[]>
|
||||
>
|
||||
},
|
||||
trigger: {
|
||||
OnEvent: '' as Resource<TriggerFunc>,
|
||||
OnEmployeeAccountCreate: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
|
@ -33,7 +33,7 @@
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server": "^0.6.4",
|
||||
"@hcengineering/chunter": "^0.6.9",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"got": "^11.8.3",
|
||||
"fast-equals": "^2.0.3",
|
||||
"html-to-text": "^9.0.3"
|
||||
|
@ -29,7 +29,7 @@
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/recruit": "^0.6.15",
|
||||
"@hcengineering/recruit": "^0.6.16",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
"@hcengineering/login": "^0.6.7",
|
||||
"@hcengineering/workbench": "^0.6.8",
|
||||
|
@ -26,7 +26,8 @@ import core, {
|
||||
Ref,
|
||||
ServerStorage,
|
||||
Tx,
|
||||
TxCUD
|
||||
TxCUD,
|
||||
systemAccountEmail
|
||||
} from '@hcengineering/core'
|
||||
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
|
||||
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
|
||||
@ -63,7 +64,7 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware {
|
||||
if (account !== tx.modifiedBy && account !== core.account.System) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
target = [ctx.userEmail]
|
||||
target = [ctx.userEmail, systemAccountEmail]
|
||||
}
|
||||
}
|
||||
const res = await this.provideTx(ctx, tx)
|
||||
|
@ -30,6 +30,7 @@ import core, {
|
||||
Ref,
|
||||
ServerStorage,
|
||||
Space,
|
||||
systemAccountEmail,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
@ -265,10 +266,11 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
|
||||
}
|
||||
}
|
||||
|
||||
async getTargets (accounts: Ref<Account>[] | undefined): Promise<string[] | undefined> {
|
||||
if (accounts === undefined) return
|
||||
async getTargets (accounts: Ref<Account>[]): Promise<string[]> {
|
||||
const users = await this.storage.modelDb.findAll(core.class.Account, { _id: { $in: accounts } })
|
||||
return users.map((p) => p.email)
|
||||
const res = users.map((p) => p.email)
|
||||
res.push(systemAccountEmail)
|
||||
return res
|
||||
}
|
||||
|
||||
private async getTxTargets (ctx: SessionContext, tx: Tx): Promise<string[] | undefined> {
|
||||
@ -278,7 +280,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
|
||||
if (h.isDerived(tx._class, core.class.TxCUD)) {
|
||||
const account = await getUser(this.storage, ctx)
|
||||
if (tx.objectSpace === (account._id as string)) {
|
||||
targets = [account.email]
|
||||
targets = [account.email, systemAccountEmail]
|
||||
} else {
|
||||
const space = this.privateSpaces[tx.objectSpace]
|
||||
if (space !== undefined) {
|
||||
@ -290,12 +292,8 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
|
||||
if (allowed === undefined || !allowed.includes(isSpace ? (cudTx.objectId as Ref<Space>) : tx.objectSpace)) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
} else {
|
||||
if (targets === undefined) {
|
||||
targets = [account.email]
|
||||
} else if (!targets.includes(account.email)) {
|
||||
targets.push(account.email)
|
||||
}
|
||||
} else if (!targets.includes(account.email)) {
|
||||
targets.push(account.email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user