mirror of
https://github.com/tutao/tutanota.git
synced 2025-12-08 06:09:50 +00:00
Initialize calendars in the CalendarModel and implement CalendarInfoBase
- This commit introduces CalendarInfoBase as a base type to hold common fields between CalendarInfo and BirthdayCalendarInfo - It also makes CalendarModel provide the available calendars. - SearchViewModel dependency on the locator is removed and CalendarModel is now injected.
This commit is contained in:
parent
e2804db800
commit
34d4614900
15 changed files with 265 additions and 363 deletions
|
|
@ -80,7 +80,6 @@ import { EventsOnDays } from "../view/CalendarViewModel.js"
|
|||
import { CalendarEventPreviewViewModel } from "./eventpopup/CalendarEventPreviewViewModel.js"
|
||||
import { createAsyncDropdown } from "../../../common/gui/base/Dropdown.js"
|
||||
import { UserController } from "../../../common/api/main/UserController.js"
|
||||
import { ClientOnlyCalendarsInfo } from "../../../common/misc/DeviceConfig.js"
|
||||
import { SelectOption } from "../../../common/gui/base/Select.js"
|
||||
import { RadioGroupOption } from "../../../common/gui/base/RadioGroup.js"
|
||||
import { ColorPickerModel } from "../../../common/gui/base/colorPicker/ColorPickerModel.js"
|
||||
|
|
@ -927,16 +926,6 @@ export const getClientOnlyColors = (userId: Id) => {
|
|||
return new Map([[calendarId, DEFAULT_BIRTHDAY_CALENDAR_COLOR]])
|
||||
}
|
||||
|
||||
export const getClientOnlyCalendars = (
|
||||
userId: Id,
|
||||
): (ClientOnlyCalendarsInfo & {
|
||||
id: string
|
||||
name: string
|
||||
})[] => {
|
||||
const calendarId = `${userId}#${BIRTHDAY_CALENDAR_BASE_ID}`
|
||||
return [{ id: calendarId, name: lang.get("birthdayCalendar_label"), color: DEFAULT_BIRTHDAY_CALENDAR_COLOR }]
|
||||
}
|
||||
|
||||
/**
|
||||
* find out how we ended up with this event, which determines the capabilities we have with it.
|
||||
* for shared events in calendar where we have read-write access, we can still only view events that have
|
||||
|
|
@ -1108,7 +1097,11 @@ export function renderCalendarColor(selectedCalendar: CalendarInfo | null, group
|
|||
* // Handle macOS modifier logic
|
||||
* }
|
||||
*/
|
||||
export function extractCalendarEventModifierKey<T extends MouseEvent | KeyboardEvent>(event: T & { redraw?: boolean }): Key | undefined {
|
||||
export function extractCalendarEventModifierKey<T extends MouseEvent | KeyboardEvent>(
|
||||
event: T & {
|
||||
redraw?: boolean
|
||||
},
|
||||
): Key | undefined {
|
||||
let key
|
||||
if (event.metaKey && isAppleDevice()) {
|
||||
key = Keys.META
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export class CalendarSidebarRow implements Component<CalendarSidebarRowAttrs> {
|
|||
size: IconSize.Medium,
|
||||
class: "pr-s",
|
||||
style: {
|
||||
fill: theme.content_button,
|
||||
fill: theme.on_surface_variant,
|
||||
},
|
||||
})
|
||||
: null,
|
||||
|
|
|
|||
|
|
@ -7,18 +7,17 @@ import {
|
|||
} from "../../../../common/calendar/date/CalendarUtils.js"
|
||||
import { CalendarEventModel, CalendarOperation, EventSaveResult, EventType, getNonOrganizerAttendees } from "../eventeditor-model/CalendarEventModel.js"
|
||||
import { NotFoundError } from "../../../../common/api/common/error/RestError.js"
|
||||
import { CalendarModel } from "../../model/CalendarModel.js"
|
||||
import { CalendarInfoBase, CalendarModel } from "../../model/CalendarModel.js"
|
||||
import { ProgrammingError } from "../../../../common/api/common/error/ProgrammingError.js"
|
||||
import { CalendarAttendeeStatus, EndType } from "../../../../common/api/common/TutanotaConstants.js"
|
||||
import m from "mithril"
|
||||
import { clone, deepEqual, incrementDate, Thunk } from "@tutao/tutanota-utils"
|
||||
import { clone, deepEqual, incrementDate, LazyLoaded, Thunk } from "@tutao/tutanota-utils"
|
||||
import { CalendarEventUidIndexEntry } from "../../../../common/api/worker/facades/lazy/CalendarFacade.js"
|
||||
import { EventEditorDialog } from "../eventeditor-view/CalendarEventEditDialog.js"
|
||||
import { convertTextToHtml } from "../../../../common/misc/Formatter.js"
|
||||
import { prepareCalendarDescription } from "../../../../common/api/common/utils/CommonCalendarUtils.js"
|
||||
import { SearchToken } from "../../../../common/api/common/utils/QueryTokenUtils"
|
||||
import { lang } from "../../../../common/misc/LanguageViewModel.js"
|
||||
import { CalendarRenderInfo } from "../../view/CalendarViewModel"
|
||||
|
||||
/**
|
||||
* makes decisions about which operations are available from the popup and knows how to implement them depending on the event's type.
|
||||
|
|
@ -50,6 +49,13 @@ export class CalendarEventPreviewViewModel {
|
|||
*/
|
||||
comment: string = ""
|
||||
|
||||
private readonly calendar: LazyLoaded<CalendarInfoBase | undefined> = new LazyLoaded<CalendarInfoBase | undefined>(async () => {
|
||||
if (!this.calendarEvent?._ownerGroup) {
|
||||
return undefined
|
||||
}
|
||||
return this.calendarModel.getCalendarInfo(this.calendarEvent._ownerGroup)
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
* @param calendarEvent the event to display in the popup
|
||||
|
|
@ -93,6 +99,8 @@ export class CalendarEventPreviewViewModel {
|
|||
|
||||
this.isRepeatingForEditing =
|
||||
(calendarEvent.repeatRule != null || calendarEvent.recurrenceId != null) && (eventType === EventType.OWN || eventType === EventType.SHARED_RW)
|
||||
|
||||
this.calendar.getAsync().then(m.redraw)
|
||||
}
|
||||
|
||||
/** for deleting, an event that has only one non-deleted instance behaves as if it wasn't repeating
|
||||
|
|
@ -370,8 +378,8 @@ export class CalendarEventPreviewViewModel {
|
|||
}
|
||||
|
||||
// Returns null if there is no ownerGroup, which might be the case if an event invitation is being viewed
|
||||
getCalendarRenderInfo(): CalendarRenderInfo | null {
|
||||
if (!this.calendarEvent._ownerGroup) return null
|
||||
return this.calendarModel.getCalendarRenderInfo(this.calendarEvent._ownerGroup)
|
||||
getCalendarInfoBase(): CalendarInfoBase | undefined {
|
||||
if (!this.calendarEvent._ownerGroup) return undefined
|
||||
return this.calendar.getLoaded()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export class EventPreviewView implements Component<EventPreviewViewAttrs> {
|
|||
const attendees = prepareAttendees(event.attendees, event.organizer)
|
||||
const eventTitle = getDisplayEventTitle(event.summary)
|
||||
|
||||
const renderInfo = calendarEventPreviewModel.getCalendarRenderInfo()
|
||||
const renderInfo = calendarEventPreviewModel.getCalendarInfoBase()
|
||||
|
||||
return m(".flex.col.smaller", [
|
||||
this.renderRow(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
getFromMap,
|
||||
isNotEmpty,
|
||||
isSameDay,
|
||||
LazyLoaded,
|
||||
Require,
|
||||
splitInChunks,
|
||||
symmetricDifference,
|
||||
|
|
@ -19,6 +20,7 @@ import {
|
|||
import {
|
||||
BIRTHDAY_CALENDAR_BASE_ID,
|
||||
CalendarMethod,
|
||||
DEFAULT_BIRTHDAY_CALENDAR_COLOR,
|
||||
defaultCalendarColor,
|
||||
EXTERNAL_CALENDAR_SYNC_INTERVAL,
|
||||
FeatureType,
|
||||
|
|
@ -98,6 +100,8 @@ import {
|
|||
getCalendarRenderType,
|
||||
getTimeZone,
|
||||
hasSourceUrl,
|
||||
isBirthdayCalendar,
|
||||
RenderType,
|
||||
} from "../../../common/calendar/date/CalendarUtils.js"
|
||||
import { getSharedGroupName, isSharedGroupOwner, loadGroupMembers } from "../../../common/sharing/GroupUtils.js"
|
||||
import { ExternalCalendarFacade } from "../../../common/native/common/generatedipc/ExternalCalendarFacade.js"
|
||||
|
|
@ -117,13 +121,19 @@ import { lang } from "../../../common/misc/LanguageViewModel.js"
|
|||
import { NativePushServiceApp } from "../../../common/native/main/NativePushServiceApp.js"
|
||||
import { SyncTracker } from "../../../common/api/main/SyncTracker.js"
|
||||
import { CacheMode } from "../../../common/api/worker/rest/EntityRestClient"
|
||||
import { CalendarRenderInfo } from "../view/CalendarViewModel"
|
||||
|
||||
const TAG = "[CalendarModel]"
|
||||
const EXTERNAL_CALENDAR_RETRY_LIMIT = 3
|
||||
const EXTERNAL_CALENDAR_RETRY_DELAY_MS = 1000
|
||||
|
||||
export type CalendarInfo = {
|
||||
export type CalendarInfoBase = {
|
||||
id: string
|
||||
name: string
|
||||
color: string
|
||||
renderType: RenderType // FIXME should we still use renderType - maybe merge with CalendarType?
|
||||
}
|
||||
|
||||
export type CalendarInfo = CalendarInfoBase & {
|
||||
groupRoot: CalendarGroupRoot
|
||||
groupInfo: GroupInfo
|
||||
group: Group
|
||||
|
|
@ -132,11 +142,18 @@ export type CalendarInfo = {
|
|||
isExternal: boolean
|
||||
}
|
||||
|
||||
export type BirthdayCalendarInfo = {
|
||||
id: string
|
||||
export type BirthdayCalendarInfo = CalendarInfoBase & {
|
||||
contactGroupId: Id
|
||||
}
|
||||
|
||||
export function isBirthdayCalendarInfo(calendarInfoBase: CalendarInfoBase): calendarInfoBase is BirthdayCalendarInfo {
|
||||
return calendarInfoBase.renderType === RenderType.ClientOnly
|
||||
}
|
||||
|
||||
export function isCalendarInfo(calendarInfoBase: CalendarInfoBase): calendarInfoBase is CalendarInfo {
|
||||
return calendarInfoBase.renderType !== RenderType.ClientOnly
|
||||
}
|
||||
|
||||
type ExternalCalendarQueueItem = {
|
||||
url: string
|
||||
group: string
|
||||
|
|
@ -187,6 +204,10 @@ export class CalendarModel {
|
|||
return calendarInfoPromise
|
||||
}, new Map())
|
||||
|
||||
private readonly userHasNewPaidPlan: LazyLoaded<boolean> = new LazyLoaded<boolean>(async () => {
|
||||
return await this.logins.getUserController().isNewPaidPlan()
|
||||
})
|
||||
|
||||
/**
|
||||
* Stores the queued calendars to be synchronized
|
||||
*/
|
||||
|
|
@ -224,11 +245,15 @@ export class CalendarModel {
|
|||
}
|
||||
})
|
||||
this.birthdayCalendarInfo = this.createBirthdayCalendarInfo()
|
||||
this.userHasNewPaidPlan.getAsync().then(m.redraw)
|
||||
}
|
||||
|
||||
private createBirthdayCalendarInfo(): BirthdayCalendarInfo {
|
||||
return {
|
||||
id: `${this.logins.getUserController().userId}#${BIRTHDAY_CALENDAR_BASE_ID}`,
|
||||
name: lang.get("birthdayCalendar_label"),
|
||||
color: DEFAULT_BIRTHDAY_CALENDAR_COLOR, // FIXME
|
||||
renderType: RenderType.ClientOnly,
|
||||
contactGroupId: getFirstOrThrow(this.logins.getUserController().getContactGroupMemberships()).group,
|
||||
}
|
||||
}
|
||||
|
|
@ -249,18 +274,26 @@ export class CalendarModel {
|
|||
return this.calendarInfos.stream
|
||||
}
|
||||
|
||||
getCalendarRenderInfo(calendarId: Id, existingGroupSettings?: GroupSettings | null): CalendarRenderInfo {
|
||||
const calendarInfo = this.calendarInfos.stream().get(calendarId)
|
||||
if (!calendarInfo) throw new Error("Calendar infos not loaded")
|
||||
let groupSettings = existingGroupSettings
|
||||
if (!groupSettings) {
|
||||
const { userSettingsGroupRoot } = this.logins.getUserController()
|
||||
groupSettings = userSettingsGroupRoot.groupSettings.find((gc) => gc.group === calendarInfo.groupInfo.group) ?? undefined
|
||||
getAvailableCalendars(includesBirthday: boolean = false): Array<CalendarInfoBase> {
|
||||
if (this.userHasNewPaidPlan.isLoaded() && this.calendarInfos.isLoaded()) {
|
||||
// Load user's calendar list
|
||||
const calendarInfos: Array<CalendarInfoBase> = Array.from(this.calendarInfos.getLoaded().values())
|
||||
if (includesBirthday && this.userHasNewPaidPlan.getLoaded()) {
|
||||
const birthdayCalendarInfo = this.getBirthdayCalendarInfo()
|
||||
calendarInfos.push(birthdayCalendarInfo)
|
||||
}
|
||||
return calendarInfos
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
const color = "#" + (groupSettings?.color ?? defaultCalendarColor)
|
||||
const name = getSharedGroupName(calendarInfo.groupInfo, locator.logins.getUserController().userSettingsGroupRoot, calendarInfo.shared)
|
||||
const renderType = getCalendarRenderType(calendarInfo)
|
||||
return { name, color, renderType }
|
||||
}
|
||||
|
||||
async getCalendarInfo(calendarId: Id): Promise<CalendarInfoBase | undefined> {
|
||||
if (isBirthdayCalendar(calendarId)) {
|
||||
return this.birthdayCalendarInfo
|
||||
}
|
||||
const calendars = await this.getCalendarInfos()
|
||||
return calendars.get(calendarId)
|
||||
}
|
||||
|
||||
async createEvent(event: CalendarEvent, alarmInfos: ReadonlyArray<AlarmInfoTemplate>, zone: string, groupRoot: CalendarGroupRoot): Promise<void> {
|
||||
|
|
@ -328,14 +361,28 @@ export class CalendarModel {
|
|||
}
|
||||
|
||||
const calendarInfos: Map<Id, CalendarInfo> = new Map()
|
||||
const groupSettings = userController.userSettingsGroupRoot.groupSettings
|
||||
const groupSettingsList = userController.userSettingsGroupRoot.groupSettings
|
||||
for (const [groupRoot, groupInfo, group] of groupInstances) {
|
||||
try {
|
||||
const groupMembers = await loadGroupMembers(group, this.entityClient)
|
||||
const shared = groupMembers.length > 1
|
||||
const userIsOwner = !shared || isSharedGroupOwner(group, userController.userId)
|
||||
const isExternal = hasSourceUrl(groupSettings.find((groupSettings) => groupSettings.group === group._id))
|
||||
const groupSettings = groupSettingsList.find((groupSettings) => groupSettings.group === group._id)
|
||||
const isExternal = hasSourceUrl(groupSettings)
|
||||
const calendarId = groupRoot._id
|
||||
const color = groupSettings?.color ?? defaultCalendarColor
|
||||
const sharedGroupName = getSharedGroupName(groupInfo, userController.userSettingsGroupRoot, shared)
|
||||
const calendarType = getCalendarRenderType({
|
||||
calendarId: calendarId,
|
||||
isExternalCalendar: isExternal,
|
||||
isUserOwner: userIsOwner,
|
||||
})
|
||||
|
||||
calendarInfos.set(groupRoot._id, {
|
||||
id: groupRoot._id,
|
||||
name: sharedGroupName,
|
||||
color: color,
|
||||
renderType: calendarType,
|
||||
groupRoot,
|
||||
groupInfo,
|
||||
group: group,
|
||||
|
|
@ -1233,6 +1280,7 @@ export class CalendarModel {
|
|||
// and user might have subscribed to a new calendar, so we must reload
|
||||
// calendar infos to make sure that the calendar has been put in the correct section
|
||||
this.calendarInfos.reload()
|
||||
this.userHasNewPaidPlan.reload()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ import { formatDate } from "../../../../common/misc/Formatter"
|
|||
import { createDropdown } from "../../../../common/gui/base/Dropdown"
|
||||
import { ProgrammingError } from "../../../../common/api/common/error/ProgrammingError"
|
||||
import { showDateRangeSelectionDialog } from "../../gui/pickers/DatePickerDialog"
|
||||
import { isSameId } from "../../../../common/api/common/utils/EntityUtils"
|
||||
import { CalendarInfo } from "../../model/CalendarModel"
|
||||
|
||||
assertMainOrNode()
|
||||
|
||||
|
|
@ -77,8 +79,9 @@ export class CalendarSearchView extends BaseTopLevelView implements TopLevelView
|
|||
|
||||
private getSanitizedPreviewData: (event: CalendarEvent) => LazyLoaded<CalendarEventPreviewViewModel> = memoized((event: CalendarEvent) =>
|
||||
new LazyLoaded(async () => {
|
||||
const calendars = await this.searchViewModel.getLazyCalendarInfos().getAsync()
|
||||
const eventPreviewModel = await calendarLocator.calendarEventPreviewModel(event, calendars, [])
|
||||
const calendars = await this.searchViewModel.getAvailableCalendars(false)
|
||||
const calendarInfosMap = new Map(calendars.map((calendarInfo) => [calendarInfo.id, calendarInfo as CalendarInfo]))
|
||||
const eventPreviewModel = await calendarLocator.calendarEventPreviewModel(event, calendarInfosMap, [])
|
||||
eventPreviewModel.sanitizeDescription().then(() => m.redraw())
|
||||
return eventPreviewModel
|
||||
}).load(),
|
||||
|
|
@ -431,11 +434,9 @@ export class CalendarSearchView extends BaseTopLevelView implements TopLevelView
|
|||
const dateToUse = this.searchViewModel.startDate ? setNextHalfHour(new Date(this.searchViewModel.startDate)) : setNextHalfHour(new Date())
|
||||
|
||||
// Disallow creation of events when there is no existing calendar
|
||||
const lazyCalendarInfo = this.searchViewModel.getLazyCalendarInfos()
|
||||
const calendarInfos = lazyCalendarInfo.isLoaded() ? lazyCalendarInfo.getSync() : lazyCalendarInfo.getAsync()
|
||||
|
||||
if (calendarInfos instanceof Promise) {
|
||||
await showProgressDialog("pleaseWait_msg", calendarInfos)
|
||||
const calendarInfos = this.searchViewModel.getAvailableCalendars(false)
|
||||
if (!calendarInfos.length) {
|
||||
await showProgressDialog("pleaseWait_msg", this.searchViewModel.loadCalendarInfos())
|
||||
}
|
||||
|
||||
const mailboxDetails = await calendarLocator.mailboxModel.getUserMailboxDetails()
|
||||
|
|
@ -515,7 +516,7 @@ export class CalendarSearchView extends BaseTopLevelView implements TopLevelView
|
|||
}
|
||||
|
||||
private renderCalendarFilterChips() {
|
||||
const availableCalendars = this.searchViewModel.getAvailableCalendars()
|
||||
const availableCalendars = this.searchViewModel.getAvailableCalendars(true)
|
||||
const selectedCalendar = this.searchViewModel.selectedCalendar
|
||||
return [
|
||||
m(FilterChip, {
|
||||
|
|
@ -533,7 +534,10 @@ export class CalendarSearchView extends BaseTopLevelView implements TopLevelView
|
|||
}),
|
||||
m(FilterChip, {
|
||||
label: selectedCalendar
|
||||
? lang.makeTranslation("calendar_label", availableCalendars.find((f) => f.info === this.searchViewModel.selectedCalendar)?.name ?? "")
|
||||
? lang.makeTranslation(
|
||||
"calendar_label",
|
||||
availableCalendars.find((calendarInfo) => isSameId(calendarInfo.id, selectedCalendar.id))?.name ?? "",
|
||||
)
|
||||
: lang.getTranslation("calendar_label"),
|
||||
selected: selectedCalendar != null,
|
||||
chevron: true,
|
||||
|
|
@ -543,9 +547,9 @@ export class CalendarSearchView extends BaseTopLevelView implements TopLevelView
|
|||
label: lang.getTranslation("all_label"),
|
||||
click: () => this.searchViewModel.selectCalendar(null),
|
||||
},
|
||||
...availableCalendars.map((f) => ({
|
||||
label: lang.makeTranslation(f.name, f.name),
|
||||
click: () => this.searchViewModel.selectCalendar(f.info),
|
||||
...availableCalendars.map((calendarInfo) => ({
|
||||
label: lang.makeTranslation(calendarInfo.name, calendarInfo.name),
|
||||
click: () => this.searchViewModel.selectCalendar(calendarInfo),
|
||||
})),
|
||||
],
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import {
|
|||
incrementMonth,
|
||||
isSameDayOfDate,
|
||||
isSameTypeRef,
|
||||
LazyLoaded,
|
||||
lazyMemoized,
|
||||
neverNull,
|
||||
ofClass,
|
||||
|
|
@ -25,24 +24,19 @@ import { NotFoundError } from "../../../../common/api/common/error/RestError.js"
|
|||
import { createRestriction, decodeCalendarSearchKey, encodeCalendarSearchKey, getRestriction } from "../model/SearchUtils.js"
|
||||
import Stream from "mithril/stream"
|
||||
import stream from "mithril/stream"
|
||||
import { generateCalendarInstancesInRange, retrieveBirthdayEventsForUser } from "../../../../common/calendar/date/CalendarUtils.js"
|
||||
import { generateCalendarInstancesInRange, isBirthdayCalendar, retrieveBirthdayEventsForUser } from "../../../../common/calendar/date/CalendarUtils.js"
|
||||
import { LoginController } from "../../../../common/api/main/LoginController.js"
|
||||
import { EntityClient } from "../../../../common/api/common/EntityClient.js"
|
||||
import { EntityUpdateData, isUpdateForTypeRef } from "../../../../common/api/common/utils/EntityUpdateUtils.js"
|
||||
import { CalendarInfo } from "../../model/CalendarModel.js"
|
||||
import m from "mithril"
|
||||
import { CalendarInfoBase, CalendarModel, isBirthdayCalendarInfo, isCalendarInfo } from "../../model/CalendarModel.js"
|
||||
import { CalendarFacade } from "../../../../common/api/worker/facades/lazy/CalendarFacade.js"
|
||||
import { ProgressTracker } from "../../../../common/api/main/ProgressTracker.js"
|
||||
import { ListAutoSelectBehavior } from "../../../../common/misc/DeviceConfig.js"
|
||||
import { ProgrammingError } from "../../../../common/api/common/error/ProgrammingError.js"
|
||||
import { SearchRouter } from "../../../../common/search/view/SearchRouter.js"
|
||||
import { locator } from "../../../../common/api/main/CommonLocator.js"
|
||||
import { CalendarEventsRepository } from "../../../../common/calendar/date/CalendarEventsRepository"
|
||||
import { getClientOnlyCalendars } from "../../gui/CalendarGuiUtils"
|
||||
import { ListElementListModel } from "../../../../common/misc/ListElementListModel"
|
||||
import { getStartOfTheWeekOffsetForUser } from "../../../../common/misc/weekOffset"
|
||||
import { getSharedGroupName } from "../../../../common/sharing/GroupUtils"
|
||||
import { BIRTHDAY_CALENDAR_BASE_ID } from "../../../../common/api/common/TutanotaConstants"
|
||||
|
||||
const SEARCH_PAGE_SIZE = 100
|
||||
|
||||
|
|
@ -93,20 +87,20 @@ export class CalendarSearchViewModel {
|
|||
}
|
||||
|
||||
// isn't an IdTuple because it is two list ids
|
||||
private _selectedCalendar: readonly [Id, Id] | string | null = null
|
||||
get selectedCalendar(): CalendarInfo | string | null {
|
||||
const calendars = this.getAvailableCalendars()
|
||||
return (
|
||||
calendars.find((calendar) => {
|
||||
if (typeof calendar.info === "string") {
|
||||
return calendar.info === this._selectedCalendar
|
||||
private _selectedCalendar: readonly [Id, Id] | Id | null = null // [longListId, shorListId] || birthDay_calendar_id | null
|
||||
get selectedCalendar(): CalendarInfoBase | null {
|
||||
const calendars = this.getAvailableCalendars(true)
|
||||
const selectedCalendar =
|
||||
calendars.find((calendarInfo) => {
|
||||
if (isBirthdayCalendarInfo(calendarInfo)) {
|
||||
return calendarInfo.id === this._selectedCalendar
|
||||
}
|
||||
|
||||
// It isn't a string, so it can be only a Calendar Info
|
||||
const calendarValue = calendar.info
|
||||
return isSameId([calendarValue.groupRoot.longEvents, calendarValue.groupRoot.shortEvents], this._selectedCalendar)
|
||||
})?.info ?? null
|
||||
)
|
||||
if (isCalendarInfo(calendarInfo)) {
|
||||
const groupRoot = calendarInfo.groupRoot
|
||||
return isSameId([groupRoot.longEvents, groupRoot.shortEvents], this._selectedCalendar)
|
||||
}
|
||||
}) ?? null
|
||||
return selectedCalendar
|
||||
}
|
||||
|
||||
// Contains load more results even when searchModel doesn't.
|
||||
|
|
@ -117,22 +111,12 @@ export class CalendarSearchViewModel {
|
|||
private listStateSubscription: Stream<unknown> | null = null
|
||||
loadingAllForSearchResult: SearchResult | null = null
|
||||
|
||||
private readonly lazyCalendarInfos: LazyLoaded<ReadonlyMap<string, CalendarInfo>> = new LazyLoaded(async () => {
|
||||
const calendarModel = await locator.calendarModel()
|
||||
const calendarInfos = await calendarModel.getCalendarInfos()
|
||||
m.redraw()
|
||||
return calendarInfos
|
||||
})
|
||||
|
||||
private readonly userHasNewPaidPlan: LazyLoaded<boolean> = new LazyLoaded<boolean>(async () => {
|
||||
return await this.logins.getUserController().isNewPaidPlan()
|
||||
})
|
||||
|
||||
currentQuery: string = ""
|
||||
|
||||
constructor(
|
||||
readonly router: SearchRouter,
|
||||
private readonly search: CalendarSearchModel,
|
||||
private readonly calendarModel: CalendarModel,
|
||||
private readonly logins: LoginController,
|
||||
private readonly entityClient: EntityClient,
|
||||
private readonly eventController: EventController,
|
||||
|
|
@ -145,40 +129,6 @@ export class CalendarSearchViewModel {
|
|||
this._listModel = this.createList()
|
||||
}
|
||||
|
||||
getLazyCalendarInfos() {
|
||||
return this.lazyCalendarInfos
|
||||
}
|
||||
|
||||
getAvailableCalendars(): Array<{ info: CalendarInfo | string; name: string }> {
|
||||
if (this.getLazyCalendarInfos().isLoaded() && this.getUserHasNewPaidPlan().isLoaded()) {
|
||||
// Load user's calendar list
|
||||
const items: {
|
||||
info: CalendarInfo | string
|
||||
name: string
|
||||
}[] = Array.from(this.getLazyCalendarInfos().getLoaded().values()).map((ci) => ({
|
||||
info: ci,
|
||||
name: getSharedGroupName(ci.groupInfo, locator.logins.getUserController().userSettingsGroupRoot, true),
|
||||
}))
|
||||
|
||||
if (this.getUserHasNewPaidPlan().getSync()) {
|
||||
const localCalendars = this.getLocalCalendars().map((cal) => ({
|
||||
info: cal.id,
|
||||
name: cal.name,
|
||||
}))
|
||||
|
||||
items.push(...localCalendars)
|
||||
}
|
||||
|
||||
return items
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
getUserHasNewPaidPlan() {
|
||||
return this.userHasNewPaidPlan
|
||||
}
|
||||
|
||||
readonly init = lazyMemoized(() => {
|
||||
this.resultSubscription = this.search.result.map((result) => {
|
||||
if (this.searchResult == null || result == null || !areResultsForTheSameQuery(result, this.searchResult)) {
|
||||
|
|
@ -257,28 +207,22 @@ export class CalendarSearchViewModel {
|
|||
|
||||
this._startDate = restriction.start ? new Date(restriction.start) : null
|
||||
this._endDate = restriction.end ? new Date(restriction.end) : null
|
||||
|
||||
// Check if user is trying to search in a client only calendar while using a free account
|
||||
const selectedCalendar = this.extractCalendarListIds(restriction.folderIds)
|
||||
if (!selectedCalendar || Array.isArray(selectedCalendar)) {
|
||||
this._selectedCalendar = selectedCalendar
|
||||
} else if (selectedCalendar.toString().includes(BIRTHDAY_CALENDAR_BASE_ID)) {
|
||||
this.getUserHasNewPaidPlan()
|
||||
.getAsync()
|
||||
.then((isNewPaidPlan) => {
|
||||
if (!isNewPaidPlan) {
|
||||
return (this._selectedCalendar = null)
|
||||
}
|
||||
|
||||
this._selectedCalendar = selectedCalendar
|
||||
})
|
||||
}
|
||||
|
||||
this._includeRepeatingEvents = restriction.eventSeries ?? true
|
||||
this.lazyCalendarInfos.load()
|
||||
this.userHasNewPaidPlan.load()
|
||||
this.latestCalendarRestriction = restriction
|
||||
|
||||
// Check if user is trying to search in a birthday calendar while using a free account
|
||||
const listIdsOrBirthdayCalendarId = this.extractCalendarListIds(restriction.folderIds)
|
||||
if (!listIdsOrBirthdayCalendarId || Array.isArray(listIdsOrBirthdayCalendarId)) {
|
||||
this._selectedCalendar = listIdsOrBirthdayCalendarId
|
||||
} else if (isBirthdayCalendar(listIdsOrBirthdayCalendarId.toString())) {
|
||||
const availableCalendars = this.getAvailableCalendars(true)
|
||||
if (availableCalendars.some(isBirthdayCalendarInfo)) {
|
||||
this._selectedCalendar = listIdsOrBirthdayCalendarId
|
||||
}
|
||||
this._selectedCalendar = null
|
||||
return
|
||||
}
|
||||
|
||||
if (args.id != null) {
|
||||
try {
|
||||
const { start, id } = decodeCalendarSearchKey(args.id)
|
||||
|
|
@ -378,10 +322,12 @@ export class CalendarSearchViewModel {
|
|||
return null
|
||||
}
|
||||
|
||||
selectCalendar(calendarInfo: CalendarInfo | string | null) {
|
||||
if (typeof calendarInfo === "string" || calendarInfo == null) {
|
||||
this._selectedCalendar = calendarInfo
|
||||
} else {
|
||||
selectCalendar(calendarInfo: CalendarInfoBase | null) {
|
||||
if (!calendarInfo) {
|
||||
this._selectedCalendar = null
|
||||
} else if (isBirthdayCalendarInfo(calendarInfo)) {
|
||||
this._selectedCalendar = calendarInfo.id
|
||||
} else if (isCalendarInfo(calendarInfo)) {
|
||||
this._selectedCalendar = [calendarInfo.groupRoot.longEvents, calendarInfo.groupRoot.shortEvents]
|
||||
}
|
||||
this.searchAgain()
|
||||
|
|
@ -443,13 +389,14 @@ export class CalendarSearchViewModel {
|
|||
}
|
||||
|
||||
private getCalendarLists(): string[] {
|
||||
if (typeof this.selectedCalendar === "string") {
|
||||
return [this.selectedCalendar]
|
||||
} else if (this.selectedCalendar != null) {
|
||||
const calendarInfo = this.selectedCalendar
|
||||
return [calendarInfo.groupRoot.longEvents, calendarInfo.groupRoot.shortEvents]
|
||||
const selectedCalendar = this.selectedCalendar
|
||||
if (!selectedCalendar) {
|
||||
return []
|
||||
} else if (isBirthdayCalendarInfo(selectedCalendar)) {
|
||||
return [this.selectedCalendar.id]
|
||||
} else if (isCalendarInfo(selectedCalendar)) {
|
||||
return [selectedCalendar.groupRoot.longEvents, selectedCalendar.groupRoot.shortEvents]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
|
|
@ -649,12 +596,16 @@ export class CalendarSearchViewModel {
|
|||
return generateCalendarInstancesInRange(eventList, { start, end })
|
||||
}
|
||||
|
||||
sendStopLoadingSignal() {
|
||||
this.search.sendCancelSignal()
|
||||
getAvailableCalendars(includesBirthday: boolean): Array<CalendarInfoBase> {
|
||||
return this.calendarModel.getAvailableCalendars(includesBirthday)
|
||||
}
|
||||
|
||||
getLocalCalendars() {
|
||||
return getClientOnlyCalendars(this.logins.getUserController().userId)
|
||||
loadCalendarInfos() {
|
||||
return this.calendarModel.getCalendarInfos()
|
||||
}
|
||||
|
||||
sendStopLoadingSignal() {
|
||||
this.search.sendCancelSignal()
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ import {
|
|||
hasSourceUrl,
|
||||
isBirthdayCalendar,
|
||||
isBirthdayEvent,
|
||||
isCalendarInfoOfRenderType,
|
||||
parseAlarmInterval,
|
||||
RenderType,
|
||||
} from "../../../common/calendar/date/CalendarUtils"
|
||||
|
|
@ -262,7 +261,6 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
|
|||
this.contentColumn = new ViewColumn(
|
||||
{
|
||||
view: () => {
|
||||
this.viewModel.loadCalendarColors()
|
||||
switch (this.currentViewType) {
|
||||
case CalendarViewType.MONTH:
|
||||
return m(BackgroundColumnLayout, {
|
||||
|
|
@ -995,11 +993,11 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
|
|||
private renderCalendar(renderType: RenderType): Children {
|
||||
const calendarInfos = Array.from(this.viewModel.calendarInfos.entries())
|
||||
const filteredCalendarInfos = calendarInfos.filter(([_, calendarInfo]) => {
|
||||
return isCalendarInfoOfRenderType(calendarInfo, renderType)
|
||||
return calendarInfo.renderType === renderType
|
||||
})
|
||||
|
||||
return filteredCalendarInfos.map(([calendarId, calendarInfo]) => {
|
||||
const { name, color, renderType } = this.viewModel.getCalendarRenderInfo(calendarInfo)
|
||||
const { name, color, renderType } = calendarInfo
|
||||
const rightIconData = this.viewModel.getIcon(renderType, calendarId)
|
||||
return m(CalendarSidebarRow, {
|
||||
id: calendarId,
|
||||
|
|
@ -1018,7 +1016,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
|
|||
return m(CalendarSidebarRow, {
|
||||
id: id,
|
||||
name: lang.get("birthdayCalendar_label"),
|
||||
color: `#${DEFAULT_BIRTHDAY_CALENDAR_COLOR}`, // FIXME get new persisted color
|
||||
color: DEFAULT_BIRTHDAY_CALENDAR_COLOR, // FIXME get new persisted color
|
||||
isHidden: this.viewModel.hiddenCalendars.has(id),
|
||||
toggleHiddenCalendar: this.viewModel.toggleHiddenCalendar,
|
||||
} satisfies CalendarSidebarRowAttrs)
|
||||
|
|
@ -1182,7 +1180,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
|
|||
const clientOnlyCalendar = isBirthdayCalendar(groupInfo.group)
|
||||
|
||||
if (clientOnlyCalendar) {
|
||||
this.viewModel.handleClientOnlyUpdate(groupInfo, { name: properties.nameData.name, color: properties.color })
|
||||
this.viewModel.handleClientOnlyUpdate(groupInfo, properties.color)
|
||||
dialog.close()
|
||||
return this.viewModel.redraw(undefined)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {
|
|||
} from "@tutao/tutanota-utils"
|
||||
import { CalendarEvent, CalendarEventTypeRef, Contact, ContactTypeRef, GroupSettings } from "../../../common/api/entities/tutanota/TypeRefs.js"
|
||||
import {
|
||||
defaultCalendarColor,
|
||||
EndType,
|
||||
EXTERNAL_CALENDAR_SYNC_INTERVAL,
|
||||
getWeekStart,
|
||||
|
|
@ -34,7 +33,6 @@ import {
|
|||
addDaysForRecurringEvent,
|
||||
CalendarTimeRange,
|
||||
extractContactIdFromEvent,
|
||||
getCalendarRenderType,
|
||||
getDiffIn60mIntervals,
|
||||
getMonthRange,
|
||||
getStartOfDayWithZone,
|
||||
|
|
@ -44,13 +42,13 @@ import {
|
|||
} from "../../../common/calendar/date/CalendarUtils"
|
||||
import { isAllDayEvent } from "../../../common/api/common/utils/CommonCalendarUtils"
|
||||
import { CalendarEventModel, CalendarOperation, EventSaveResult, EventType, getNonOrganizerAttendees } from "../gui/eventeditor-model/CalendarEventModel.js"
|
||||
import { askIfShouldSendCalendarUpdatesToAttendees, getClientOnlyColors, getEventType, shouldDisplayEvent } from "../gui/CalendarGuiUtils.js"
|
||||
import { askIfShouldSendCalendarUpdatesToAttendees, getEventType, shouldDisplayEvent } from "../gui/CalendarGuiUtils.js"
|
||||
import { ReceivedGroupInvitationsModel } from "../../../common/sharing/model/ReceivedGroupInvitationsModel"
|
||||
import type { CalendarInfo, CalendarModel } from "../model/CalendarModel"
|
||||
import { EventController } from "../../../common/api/main/EventController"
|
||||
import { EntityClient } from "../../../common/api/common/EntityClient"
|
||||
import { ProgressTracker } from "../../../common/api/main/ProgressTracker"
|
||||
import { ClientOnlyCalendarsInfo, deviceConfig, DeviceConfig } from "../../../common/misc/DeviceConfig"
|
||||
import { deviceConfig, DeviceConfig } from "../../../common/misc/DeviceConfig"
|
||||
import type { EventDragHandlerCallbacks } from "./EventDragHandler"
|
||||
import { ProgrammingError } from "../../../common/api/common/error/ProgrammingError.js"
|
||||
import { Time } from "../../../common/calendar/date/Time.js"
|
||||
|
|
@ -60,20 +58,17 @@ import { EntityUpdateData, isUpdateFor, isUpdateForTypeRef } from "../../../comm
|
|||
import { MailboxModel } from "../../../common/mailFunctionality/MailboxModel.js"
|
||||
import { getEnabledMailAddressesWithUser } from "../../../common/mailFunctionality/SharedMailUtils.js"
|
||||
import { ContactModel } from "../../../common/contactsFunctionality/ContactModel.js"
|
||||
import type { GroupColors } from "./CalendarView.js"
|
||||
import { lang } from "../../../common/misc/LanguageViewModel.js"
|
||||
import { CalendarContactPreviewViewModel } from "../gui/eventpopup/CalendarContactPreviewViewModel.js"
|
||||
import { Dialog } from "../../../common/gui/base/Dialog.js"
|
||||
import { SearchToken } from "../../../common/api/common/utils/QueryTokenUtils"
|
||||
import { getGroupColors } from "../../../common/misc/GroupColors"
|
||||
import { GroupNameData, GroupSettingsModel } from "../../../common/sharing/model/GroupSettingsModel"
|
||||
import { EventEditorDialog } from "../gui/eventeditor-view/CalendarEventEditDialog.js"
|
||||
import { showPlanUpgradeRequiredDialog } from "../../../common/misc/SubscriptionDialogs"
|
||||
import { formatDate, formatTime } from "../../../common/misc/Formatter"
|
||||
import { CalendarSidebarIconData } from "../gui/CalendarSidebarRow"
|
||||
import { Icons } from "../../../common/gui/base/icons/Icons"
|
||||
import { getSharedGroupName } from "../../../common/sharing/GroupUtils"
|
||||
import { locator } from "../../../common/api/main/CommonLocator"
|
||||
import { SyncStatus } from "../../../common/calendar/gui/ImportExportUtils"
|
||||
|
||||
export type EventsOnDays = {
|
||||
days: Array<Date>
|
||||
|
|
@ -102,13 +97,6 @@ export type CalendarEventPreviewModelFactory = (
|
|||
export type CalendarContactPreviewModelFactory = (event: CalendarEvent, contact: Contact, canEdit: boolean) => Promise<CalendarContactPreviewViewModel>
|
||||
export type CalendarPreviewModels = CalendarEventPreviewViewModel | CalendarContactPreviewViewModel
|
||||
|
||||
export type CalendarRenderInfo = {
|
||||
// FIXME later use this tupe only to extract the sidebar calendar row
|
||||
name: string
|
||||
color: string
|
||||
renderType: RenderType
|
||||
}
|
||||
|
||||
export class CalendarViewModel implements EventDragHandlerCallbacks {
|
||||
// Should not be changed directly but only through the URL
|
||||
readonly selectedDate: Stream<Date> = stream(getStartOfDay(new Date()))
|
||||
|
|
@ -140,7 +128,6 @@ export class CalendarViewModel implements EventDragHandlerCallbacks {
|
|||
private viewSize: number | null = null
|
||||
|
||||
private _isNewPaidPlan: boolean = false
|
||||
private _calendarColors: GroupColors = new Map()
|
||||
isCreatingExternalCalendar: boolean = false
|
||||
|
||||
private cancelSignal: Stream<boolean> = stream(false)
|
||||
|
|
@ -199,8 +186,6 @@ export class CalendarViewModel implements EventDragHandlerCallbacks {
|
|||
this.doRedraw()
|
||||
})
|
||||
|
||||
this.loadCalendarColors()
|
||||
|
||||
// disable birthday calendars by default if the user is not on a new paid plan.
|
||||
logins
|
||||
.getUserController()
|
||||
|
|
@ -246,16 +231,12 @@ export class CalendarViewModel implements EventDragHandlerCallbacks {
|
|||
this.deviceConfig.setCalendarDaySelectorExpanded(expanded)
|
||||
}
|
||||
|
||||
loadCalendarColors() {
|
||||
const clientOnlyColors = getClientOnlyColors(this.logins.getUserController().userId)
|
||||
const groupColors = getGroupColors(this.logins.getUserController().userSettingsGroupRoot)
|
||||
for (let [calendarId, color] of clientOnlyColors.entries()) {
|
||||
groupColors.set(calendarId, color)
|
||||
}
|
||||
|
||||
if (!deepEqual(this._calendarColors, groupColors)) {
|
||||
this._calendarColors = new Map(groupColors)
|
||||
get calendarColors() {
|
||||
const calendarColors = new Map()
|
||||
for (let calendarInfo of this.calendarModel.getAvailableCalendars(true)) {
|
||||
calendarColors.set(calendarInfo.id, calendarInfo.color)
|
||||
}
|
||||
return calendarColors
|
||||
}
|
||||
|
||||
async getCalendarNameData(groupInfo: GroupInfo): Promise<GroupNameData> {
|
||||
|
|
@ -317,10 +298,6 @@ export class CalendarViewModel implements EventDragHandlerCallbacks {
|
|||
return this.calendarInvitationsModel.invitations
|
||||
}
|
||||
|
||||
get calendarColors(): GroupColors {
|
||||
return this._calendarColors
|
||||
}
|
||||
|
||||
get calendarInfos(): ReadonlyMap<Id, CalendarInfo> {
|
||||
return this.calendarModel.getCalendarInfosStream()()
|
||||
}
|
||||
|
|
@ -778,7 +755,7 @@ export class CalendarViewModel implements EventDragHandlerCallbacks {
|
|||
return this.calendarModel
|
||||
}
|
||||
|
||||
handleClientOnlyUpdate(groupInfo: GroupInfo, newGroupSettings: ClientOnlyCalendarsInfo) {
|
||||
handleClientOnlyUpdate(groupInfo: GroupInfo, newBirthdayColor: string) {
|
||||
console.log("Update to handle new birthday colors at userSettings") // FIXME
|
||||
}
|
||||
|
||||
|
|
@ -805,24 +782,14 @@ export class CalendarViewModel implements EventDragHandlerCallbacks {
|
|||
this.setHiddenCalendars(newHiddenCalendars)
|
||||
}
|
||||
|
||||
getCalendarRenderInfo(calendarInfo: CalendarInfo): CalendarRenderInfo {
|
||||
const { userSettingsGroupRoot } = this.logins.getUserController()
|
||||
const groupSettings = userSettingsGroupRoot.groupSettings.find((gc) => gc.group === calendarInfo.groupInfo.group) ?? undefined
|
||||
|
||||
const sharedGroupName = getSharedGroupName(calendarInfo.groupInfo, locator.logins.getUserController().userSettingsGroupRoot, calendarInfo.shared)
|
||||
const color = "#" + (groupSettings?.color ?? defaultCalendarColor)
|
||||
const calendarType = getCalendarRenderType(calendarInfo)
|
||||
return {
|
||||
name: sharedGroupName,
|
||||
color,
|
||||
renderType: calendarType,
|
||||
}
|
||||
}
|
||||
|
||||
getIcon(renderType: RenderType, calendarId: string): CalendarSidebarIconData | undefined {
|
||||
switch (renderType) {
|
||||
case RenderType.External: {
|
||||
const lastSyncEntry = deviceConfig.getLastExternalCalendarSync().get(calendarId)
|
||||
if (!lastSyncEntry || lastSyncEntry.lastSyncStatus === SyncStatus.Success) {
|
||||
// lastSyncEntry won't exist in the webClient
|
||||
return
|
||||
}
|
||||
const lastSyncDate = lastSyncEntry?.lastSuccessfulSync ? new Date(lastSyncEntry.lastSuccessfulSync) : null
|
||||
const lastSyncStr = lastSyncDate
|
||||
? lang.get("lastSync_label", { "{date}": `${formatDate(lastSyncDate)} at ${formatTime(lastSyncDate)}` })
|
||||
|
|
|
|||
|
|
@ -227,30 +227,12 @@ class CalendarLocator implements CommonLocator {
|
|||
const redraw = await this.redraw()
|
||||
const searchRouter = await this.scopedSearchRouter()
|
||||
const calendarEventsRepository = await this.calendarEventsRepository()
|
||||
const calendarModel = await this.calendarModel()
|
||||
return () => {
|
||||
return new CalendarSearchViewModel(
|
||||
searchRouter,
|
||||
this.search,
|
||||
this.logins,
|
||||
this.entityClient,
|
||||
this.eventController,
|
||||
this.calendarFacade,
|
||||
this.progressTracker,
|
||||
calendarEventsRepository,
|
||||
redraw,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async calendarSearchViewModelFactory(): Promise<() => CalendarSearchViewModel> {
|
||||
const { CalendarSearchViewModel } = await import("./calendar/search/view/CalendarSearchViewModel.js")
|
||||
const redraw = await this.redraw()
|
||||
const searchRouter = await this.scopedSearchRouter()
|
||||
const calendarEventsRepository = await this.calendarEventsRepository()
|
||||
return () => {
|
||||
return new CalendarSearchViewModel(
|
||||
searchRouter,
|
||||
this.search,
|
||||
calendarModel,
|
||||
this.logins,
|
||||
this.entityClient,
|
||||
this.eventController,
|
||||
|
|
|
|||
|
|
@ -1704,35 +1704,29 @@ export const RENDER_TYPE_TRANSLATION_MAP: ReadonlyMap<RenderType, TranslationKey
|
|||
]),
|
||||
)
|
||||
|
||||
export function isCalendarInfoOfRenderType(calendarInfo: CalendarInfo, renderType: RenderType) {
|
||||
switch (renderType) {
|
||||
case RenderType.Private:
|
||||
return isPrivateRenderType(calendarInfo)
|
||||
case RenderType.Shared:
|
||||
return isSharedRenderType(calendarInfo)
|
||||
case RenderType.External:
|
||||
return isExternalRenderType(calendarInfo)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
export function isPrivateRenderType(renderTypeInfo: RenderTypeInfo) {
|
||||
return renderTypeInfo.isUserOwner && !renderTypeInfo.isExternalCalendar && !isBirthdayCalendar(renderTypeInfo.calendarId)
|
||||
}
|
||||
|
||||
export function isPrivateRenderType(calendarInfo: CalendarInfo) {
|
||||
return calendarInfo.userIsOwner && !calendarInfo.isExternal && !isBirthdayCalendar(calendarInfo.group._id)
|
||||
export function isSharedRenderType(renderTypeInfo: RenderTypeInfo) {
|
||||
return !renderTypeInfo.isUserOwner
|
||||
}
|
||||
|
||||
export function isSharedRenderType(calendarInfo: CalendarInfo) {
|
||||
return !calendarInfo.userIsOwner
|
||||
export function isExternalRenderType(renderTypeInfo: RenderTypeInfo) {
|
||||
return renderTypeInfo.isUserOwner && renderTypeInfo.isExternalCalendar
|
||||
}
|
||||
|
||||
export function isExternalRenderType(calendarInfo: CalendarInfo) {
|
||||
return calendarInfo.userIsOwner && calendarInfo.isExternal
|
||||
export type RenderTypeInfo = {
|
||||
calendarId: string
|
||||
isExternalCalendar: boolean
|
||||
isUserOwner: boolean
|
||||
}
|
||||
|
||||
export function getCalendarRenderType(calendarInfo: CalendarInfo): RenderType {
|
||||
if (isPrivateRenderType(calendarInfo)) return RenderType.Private
|
||||
if (isSharedRenderType(calendarInfo)) return RenderType.Shared
|
||||
if (isExternalRenderType(calendarInfo)) return RenderType.External
|
||||
export function getCalendarRenderType(renderTypeInfo: RenderTypeInfo): RenderType {
|
||||
if (isBirthdayCalendar(renderTypeInfo.calendarId)) return RenderType.ClientOnly
|
||||
if (isPrivateRenderType(renderTypeInfo)) return RenderType.Private
|
||||
if (isSharedRenderType(renderTypeInfo)) return RenderType.Shared
|
||||
if (isExternalRenderType(renderTypeInfo)) return RenderType.External
|
||||
throw new Error("Unknown calendar Render Type")
|
||||
}
|
||||
|
||||
|
|
@ -1775,16 +1769,22 @@ export function extractYearFromBirthday(birthday: string | null): number | null
|
|||
return Number.parseInt(dateParts[0])
|
||||
}
|
||||
|
||||
export async function retrieveBirthdayEventsForUser(logins: LoginController, events: IdTuple[], localEvents: Map<number, BirthdayEventRegistry[]>) {
|
||||
export async function retrieveBirthdayEventsForUser(
|
||||
logins: LoginController,
|
||||
searchResultEventIds: IdTuple[],
|
||||
birthdayEventsByMonth: Map<number, BirthdayEventRegistry[]>,
|
||||
) {
|
||||
if (!(await logins.getUserController().isNewPaidPlan())) {
|
||||
return []
|
||||
}
|
||||
|
||||
const clientOnlyEvents = events.filter(([calendarId, _]) => isBirthdayCalendar(calendarId)).flatMap((event) => event.join("/"))
|
||||
const birthdayEventsFromSearchResult = searchResultEventIds.filter(([calendarId, _]) => isBirthdayCalendar(calendarId))
|
||||
const birthdayEventIdsString = birthdayEventsFromSearchResult.flatMap((eventId) => eventId.join("/"))
|
||||
const retrievedEvents: CalendarEvent[] = []
|
||||
|
||||
for (const event of Array.from(localEvents.values()).flat()) {
|
||||
if (clientOnlyEvents.includes(event.event._id.join("/"))) {
|
||||
const allBirthdayEvents = Array.from(birthdayEventsByMonth.values()).flat()
|
||||
for (const event of allBirthdayEvents) {
|
||||
if (birthdayEventIdsString.includes(event.event._id.join("/"))) {
|
||||
retrievedEvents.push(event.event)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { CalendarViewType } from "../api/common/utils/CommonCalendarUtils.js"
|
|||
import { SyncStatus } from "../calendar/gui/ImportExportUtils.js"
|
||||
import Stream from "mithril/stream"
|
||||
import stream from "mithril/stream"
|
||||
import type { GroupSettings } from "../api/entities/tutanota/TypeRefs.js"
|
||||
|
||||
assertMainOrNodeBoot()
|
||||
export const defaultThemePreference: ThemePreference = "auto:light|dark"
|
||||
|
|
@ -33,8 +32,6 @@ export type LastExternalCalendarSyncEntry = {
|
|||
lastSyncStatus: SyncStatus
|
||||
}
|
||||
|
||||
export type ClientOnlyCalendarsInfo = Pick<GroupSettings, "name" | "color">
|
||||
|
||||
/**
|
||||
* Definition of the config object that will be saved to local storage
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -304,6 +304,7 @@ class MailLocator implements CommonLocator {
|
|||
const searchRouter = await this.scopedSearchRouter()
|
||||
const calendarEventsRepository = await this.calendarEventsRepository()
|
||||
const offlineStorageSettings = await this.offlineStorageSettingsModel()
|
||||
const calendarModel = await this.calendarModel()
|
||||
return () => {
|
||||
return new SearchViewModel(
|
||||
searchRouter,
|
||||
|
|
@ -319,6 +320,7 @@ class MailLocator implements CommonLocator {
|
|||
this.progressTracker,
|
||||
conversationViewModelFactory,
|
||||
calendarEventsRepository,
|
||||
calendarModel,
|
||||
redraw,
|
||||
deviceConfig.getMailAutoSelectBehavior(),
|
||||
offlineStorageSettings,
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ import { showDateRangeSelectionDialog } from "../../../calendar-app/calendar/gui
|
|||
import { ProgrammingError } from "../../../common/api/common/error/ProgrammingError"
|
||||
import { UndoModel } from "../../UndoModel"
|
||||
import { deviceConfig } from "../../../common/misc/DeviceConfig"
|
||||
import { CalendarInfo } from "../../../calendar-app/calendar/model/CalendarModel"
|
||||
|
||||
assertMainOrNode()
|
||||
|
||||
|
|
@ -142,8 +143,9 @@ export class SearchView extends BaseTopLevelView implements TopLevelView<SearchV
|
|||
|
||||
private getSanitizedPreviewData: (event: CalendarEvent) => LazyLoaded<CalendarEventPreviewViewModel> = memoized((event: CalendarEvent) =>
|
||||
new LazyLoaded(async () => {
|
||||
const calendars = await this.searchViewModel.getLazyCalendarInfos().getAsync()
|
||||
const eventPreviewModel = await locator.calendarEventPreviewModel(event, calendars, this.searchViewModel.getHighlightedStrings())
|
||||
const calendars = await this.searchViewModel.getAvailableCalendars(false)
|
||||
const calendarInfosMap = new Map(calendars.map((calendarInfo) => [calendarInfo.id, calendarInfo as CalendarInfo]))
|
||||
const eventPreviewModel = await locator.calendarEventPreviewModel(event, calendarInfosMap, this.searchViewModel.getHighlightedStrings())
|
||||
eventPreviewModel.sanitizeDescription().then(() => m.redraw())
|
||||
return eventPreviewModel
|
||||
}).load(),
|
||||
|
|
@ -1260,11 +1262,9 @@ export class SearchView extends BaseTopLevelView implements TopLevelView<SearchV
|
|||
const dateToUse = this.searchViewModel.startDate ? setNextHalfHour(new Date(this.searchViewModel.startDate)) : setNextHalfHour(new Date())
|
||||
|
||||
// Disallow creation of events when there is no existing calendar
|
||||
const lazyCalendarInfo = this.searchViewModel.getLazyCalendarInfos()
|
||||
const calendarInfos = lazyCalendarInfo.isLoaded() ? lazyCalendarInfo.getSync() : lazyCalendarInfo.getAsync()
|
||||
|
||||
if (calendarInfos instanceof Promise) {
|
||||
await showProgressDialog("pleaseWait_msg", calendarInfos)
|
||||
const calendarInfos = this.searchViewModel.getAvailableCalendars(false)
|
||||
if (!calendarInfos.length) {
|
||||
await showProgressDialog("pleaseWait_msg", this.searchViewModel.loadCalendarInfos())
|
||||
}
|
||||
|
||||
const mailboxDetails = await locator.mailboxModel.getUserMailboxDetails()
|
||||
|
|
@ -1375,7 +1375,8 @@ export class SearchView extends BaseTopLevelView implements TopLevelView<SearchV
|
|||
}
|
||||
|
||||
private renderCalendarFilterChips() {
|
||||
const availableCalendars = this.searchViewModel.getAvailableCalendars()
|
||||
const availableCalendars = this.searchViewModel.getAvailableCalendars(true)
|
||||
const selectedCalendar = this.searchViewModel.selectedCalendar
|
||||
return [
|
||||
this.renderCategoryChip("calendar_label", BootIcons.Calendar),
|
||||
m(FilterChip, {
|
||||
|
|
@ -1392,10 +1393,13 @@ export class SearchView extends BaseTopLevelView implements TopLevelView<SearchV
|
|||
onClick: (_) => this.onCalendarDateRangeSelect(),
|
||||
}),
|
||||
m(FilterChip, {
|
||||
label: this.searchViewModel.selectedCalendar
|
||||
? lang.makeTranslation("calendar_label", availableCalendars.find((f) => f.info === this.searchViewModel.selectedCalendar)?.name ?? "")
|
||||
label: selectedCalendar
|
||||
? lang.makeTranslation(
|
||||
"calendar_label",
|
||||
availableCalendars.find((calendarInfo) => isSameId(calendarInfo.id, selectedCalendar?.id))?.name ?? "",
|
||||
)
|
||||
: lang.getTranslation("calendar_label"),
|
||||
selected: this.searchViewModel.selectedCalendar != null,
|
||||
selected: selectedCalendar != null,
|
||||
chevron: true,
|
||||
onClick: createDropdown({
|
||||
lazyButtons: () => [
|
||||
|
|
@ -1403,9 +1407,9 @@ export class SearchView extends BaseTopLevelView implements TopLevelView<SearchV
|
|||
label: lang.getTranslation("all_label"),
|
||||
click: () => this.searchViewModel.selectCalendar(null),
|
||||
},
|
||||
...availableCalendars.map((f) => ({
|
||||
label: lang.makeTranslation(f.name, f.name),
|
||||
click: () => this.searchViewModel.selectCalendar(f.info),
|
||||
...availableCalendars.map((calendarInfo) => ({
|
||||
label: lang.makeTranslation(calendarInfo.name, calendarInfo.name),
|
||||
click: () => this.searchViewModel.selectCalendar(calendarInfo),
|
||||
})),
|
||||
],
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -4,13 +4,7 @@ import { SearchRestriction, SearchResult } from "../../../common/api/worker/sear
|
|||
import { EntityEventsListener, EventController } from "../../../common/api/main/EventController.js"
|
||||
import { CalendarEvent, CalendarEventTypeRef, Contact, ContactTypeRef, Mail, MailFolder, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs.js"
|
||||
import { ListElementEntity } from "../../../common/api/common/EntityTypes.js"
|
||||
import {
|
||||
BIRTHDAY_CALENDAR_BASE_ID,
|
||||
FULL_INDEXED_TIMESTAMP,
|
||||
MailSetKind,
|
||||
NOTHING_INDEXED_TIMESTAMP,
|
||||
OperationType,
|
||||
} from "../../../common/api/common/TutanotaConstants.js"
|
||||
import { FULL_INDEXED_TIMESTAMP, MailSetKind, NOTHING_INDEXED_TIMESTAMP, OperationType } from "../../../common/api/common/TutanotaConstants.js"
|
||||
import {
|
||||
assertIsEntity,
|
||||
assertIsEntity2,
|
||||
|
|
@ -33,7 +27,6 @@ import {
|
|||
incrementMonth,
|
||||
isSameDayOfDate,
|
||||
isSameTypeRef,
|
||||
LazyLoaded,
|
||||
memoizedWithHiddenArgument,
|
||||
neverNull,
|
||||
ofClass,
|
||||
|
|
@ -61,18 +54,15 @@ import { EntityClient, loadMultipleFromLists } from "../../../common/api/common/
|
|||
import { SearchRouter } from "../../../common/search/view/SearchRouter.js"
|
||||
import { MailOpenedListener } from "../../mail/view/MailViewModel.js"
|
||||
import { EntityUpdateData, isUpdateForTypeRef } from "../../../common/api/common/utils/EntityUpdateUtils.js"
|
||||
import { CalendarInfo } from "../../../calendar-app/calendar/model/CalendarModel.js"
|
||||
import { locator } from "../../../common/api/main/CommonLocator.js"
|
||||
import m from "mithril"
|
||||
import { CalendarInfoBase, CalendarModel, isBirthdayCalendarInfo, isCalendarInfo } from "../../../calendar-app/calendar/model/CalendarModel.js"
|
||||
import { CalendarFacade } from "../../../common/api/worker/facades/lazy/CalendarFacade.js"
|
||||
import { ProgrammingError } from "../../../common/api/common/error/ProgrammingError.js"
|
||||
import { ProgressTracker } from "../../../common/api/main/ProgressTracker.js"
|
||||
import { ListAutoSelectBehavior } from "../../../common/misc/DeviceConfig.js"
|
||||
import { generateCalendarInstancesInRange, retrieveBirthdayEventsForUser } from "../../../common/calendar/date/CalendarUtils.js"
|
||||
import { generateCalendarInstancesInRange, isBirthdayCalendar, retrieveBirthdayEventsForUser } from "../../../common/calendar/date/CalendarUtils.js"
|
||||
import { mailLocator } from "../../mailLocator.js"
|
||||
import { getMailFilterForType, MailFilterType } from "../../mail/view/MailViewerUtils.js"
|
||||
import { CalendarEventsRepository } from "../../../common/calendar/date/CalendarEventsRepository.js"
|
||||
import { getClientOnlyCalendars } from "../../../calendar-app/calendar/gui/CalendarGuiUtils.js"
|
||||
import { ListFilter } from "../../../common/misc/ListModel"
|
||||
import { client } from "../../../common/misc/ClientDetector"
|
||||
import { OfflineStorageSettingsModel } from "../../../common/offline/OfflineStorageSettingsModel"
|
||||
|
|
@ -82,7 +72,6 @@ import { SearchFacade } from "../../workerUtils/index/SearchFacade"
|
|||
import { compareMails } from "../../mail/model/MailUtils"
|
||||
import { isOfflineStorageAvailable } from "../../../common/api/common/Env"
|
||||
import { SearchToken } from "../../../common/api/common/utils/QueryTokenUtils"
|
||||
import { getSharedGroupName } from "../../../common/sharing/GroupUtils"
|
||||
|
||||
const SEARCH_PAGE_SIZE = 100
|
||||
|
||||
|
|
@ -159,20 +148,20 @@ export class SearchViewModel {
|
|||
}
|
||||
|
||||
// isn't an IdTuple because it is two list ids
|
||||
private _selectedCalendar: readonly [Id, Id] | string | null = null
|
||||
get selectedCalendar(): CalendarInfo | string | null {
|
||||
const calendars = this.getAvailableCalendars()
|
||||
return (
|
||||
calendars.find((calendar) => {
|
||||
if (typeof calendar.info === "string") {
|
||||
return calendar.info === this._selectedCalendar
|
||||
private _selectedCalendar: readonly [Id, Id] | Id | null = null // [longListId, shorListId] || birthDay_calendar_id | null
|
||||
get selectedCalendar(): CalendarInfoBase | null {
|
||||
const calendars = this.getAvailableCalendars(true)
|
||||
const selectedCalendar =
|
||||
calendars.find((calendarInfo) => {
|
||||
if (isBirthdayCalendarInfo(calendarInfo)) {
|
||||
return calendarInfo.id === this._selectedCalendar
|
||||
}
|
||||
|
||||
// It isn't a string, so it can be only a Calendar Info
|
||||
const calendarValue = calendar.info
|
||||
return isSameId([calendarValue.groupRoot.longEvents, calendarValue.groupRoot.shortEvents], this._selectedCalendar)
|
||||
})?.info ?? null
|
||||
)
|
||||
if (isCalendarInfo(calendarInfo)) {
|
||||
const groupRoot = calendarInfo.groupRoot
|
||||
return isSameId([groupRoot.longEvents, groupRoot.shortEvents], this._selectedCalendar)
|
||||
}
|
||||
}) ?? null
|
||||
return selectedCalendar
|
||||
}
|
||||
|
||||
private _mailboxes: MailboxDetail[] = []
|
||||
|
|
@ -195,16 +184,6 @@ export class SearchViewModel {
|
|||
private resultSubscription: Stream<void> | null = null
|
||||
private listStateSubscription: Stream<unknown> | null = null
|
||||
loadingAllForSearchResult: SearchResult | null = null
|
||||
private readonly lazyCalendarInfos: LazyLoaded<ReadonlyMap<string, CalendarInfo>> = new LazyLoaded(async () => {
|
||||
const calendarModel = await locator.calendarModel()
|
||||
const calendarInfos = await calendarModel.getCalendarInfos()
|
||||
m.redraw()
|
||||
return calendarInfos
|
||||
})
|
||||
|
||||
private readonly userHasNewPaidPlan: LazyLoaded<boolean> = new LazyLoaded<boolean>(async () => {
|
||||
return await this.logins.getUserController().isNewPaidPlan()
|
||||
})
|
||||
|
||||
private currentQuery: string = ""
|
||||
|
||||
|
|
@ -225,6 +204,7 @@ export class SearchViewModel {
|
|||
private readonly progressTracker: ProgressTracker,
|
||||
private readonly conversationViewModelFactory: ConversationViewModelFactory | null,
|
||||
private readonly eventsRepository: CalendarEventsRepository,
|
||||
private readonly calendarModel: CalendarModel,
|
||||
private readonly updateUi: () => unknown,
|
||||
private readonly selectionBehavior: ListAutoSelectBehavior,
|
||||
private readonly offlineStorageSettings: OfflineStorageSettingsModel | null,
|
||||
|
|
@ -233,40 +213,6 @@ export class SearchViewModel {
|
|||
this._listModel = this.createList()
|
||||
}
|
||||
|
||||
getLazyCalendarInfos() {
|
||||
return this.lazyCalendarInfos
|
||||
}
|
||||
|
||||
getAvailableCalendars(): Array<{ info: CalendarInfo | string; name: string }> {
|
||||
if (this.getLazyCalendarInfos().isLoaded() && this.getUserHasNewPaidPlan().isLoaded()) {
|
||||
// Load user's calendar list
|
||||
const items: {
|
||||
info: CalendarInfo | string
|
||||
name: string
|
||||
}[] = Array.from(this.getLazyCalendarInfos().getLoaded().values()).map((ci) => ({
|
||||
info: ci,
|
||||
name: getSharedGroupName(ci.groupInfo, locator.logins.getUserController().userSettingsGroupRoot, true),
|
||||
}))
|
||||
|
||||
if (this.getUserHasNewPaidPlan().getSync()) {
|
||||
const localCalendars = this.getLocalCalendars().map((cal) => ({
|
||||
info: cal.id,
|
||||
name: cal.name,
|
||||
}))
|
||||
|
||||
items.push(...localCalendars)
|
||||
}
|
||||
|
||||
return items
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
getUserHasNewPaidPlan() {
|
||||
return this.userHasNewPaidPlan
|
||||
}
|
||||
|
||||
async init(extendIndexConfirmationCallback: SearchViewModel["extendIndexConfirmationCallback"]) {
|
||||
if (this.extendIndexConfirmationCallback) {
|
||||
return
|
||||
|
|
@ -380,24 +326,19 @@ export class SearchViewModel {
|
|||
this._startDate = restriction.start ? new Date(restriction.start) : null
|
||||
this._endDate = restriction.end ? new Date(restriction.end) : null
|
||||
this._includeRepeatingEvents = restriction.eventSeries ?? true
|
||||
this.lazyCalendarInfos.load()
|
||||
this.userHasNewPaidPlan.load()
|
||||
this.latestCalendarRestriction = restriction
|
||||
|
||||
// Check if user is trying to search in a client only calendar while using a free account
|
||||
const selectedCalendar = this.extractCalendarListIds(restriction.folderIds)
|
||||
if (!selectedCalendar || Array.isArray(selectedCalendar)) {
|
||||
this._selectedCalendar = selectedCalendar
|
||||
} else if (selectedCalendar.toString().includes(BIRTHDAY_CALENDAR_BASE_ID)) {
|
||||
this.getUserHasNewPaidPlan()
|
||||
.getAsync()
|
||||
.then((isNewPaidPlan) => {
|
||||
if (!isNewPaidPlan) {
|
||||
return (this._selectedCalendar = null)
|
||||
}
|
||||
|
||||
this._selectedCalendar = selectedCalendar
|
||||
})
|
||||
// Check if user is trying to search in a birthday calendar while using a free account
|
||||
const listIdsOrBirthdayCalendarId = this.extractCalendarListIds(restriction.folderIds)
|
||||
if (!listIdsOrBirthdayCalendarId || Array.isArray(listIdsOrBirthdayCalendarId)) {
|
||||
this._selectedCalendar = listIdsOrBirthdayCalendarId
|
||||
} else if (isBirthdayCalendar(listIdsOrBirthdayCalendarId.toString())) {
|
||||
const availableCalendars = this.getAvailableCalendars(true)
|
||||
if (availableCalendars.some(isBirthdayCalendarInfo)) {
|
||||
this._selectedCalendar = listIdsOrBirthdayCalendarId
|
||||
}
|
||||
this._selectedCalendar = null
|
||||
return
|
||||
}
|
||||
|
||||
if (args.id != null) {
|
||||
|
|
@ -566,10 +507,12 @@ export class SearchViewModel {
|
|||
return PaidFunctionResult.Success
|
||||
}
|
||||
|
||||
selectCalendar(calendarInfo: CalendarInfo | string | null) {
|
||||
if (typeof calendarInfo === "string" || calendarInfo == null) {
|
||||
this._selectedCalendar = calendarInfo
|
||||
} else {
|
||||
selectCalendar(calendarInfo: CalendarInfoBase | null) {
|
||||
if (!calendarInfo) {
|
||||
this._selectedCalendar = null
|
||||
} else if (isBirthdayCalendarInfo(calendarInfo)) {
|
||||
this._selectedCalendar = calendarInfo.id
|
||||
} else if (isCalendarInfo(calendarInfo)) {
|
||||
this._selectedCalendar = [calendarInfo.groupRoot.longEvents, calendarInfo.groupRoot.shortEvents]
|
||||
}
|
||||
this.searchAgain()
|
||||
|
|
@ -693,13 +636,14 @@ export class SearchViewModel {
|
|||
}
|
||||
|
||||
private getCalendarLists(): string[] {
|
||||
if (typeof this.selectedCalendar === "string") {
|
||||
return [this.selectedCalendar]
|
||||
} else if (this.selectedCalendar != null) {
|
||||
const calendarInfo = this.selectedCalendar
|
||||
return [calendarInfo.groupRoot.longEvents, calendarInfo.groupRoot.shortEvents]
|
||||
const selectedCalendar = this.selectedCalendar
|
||||
if (!selectedCalendar) {
|
||||
return []
|
||||
} else if (isBirthdayCalendarInfo(selectedCalendar)) {
|
||||
return [this.selectedCalendar.id]
|
||||
} else if (isCalendarInfo(selectedCalendar)) {
|
||||
return [selectedCalendar.groupRoot.longEvents, selectedCalendar.groupRoot.shortEvents]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
|
|
@ -1074,6 +1018,14 @@ export class SearchViewModel {
|
|||
return generateCalendarInstancesInRange(eventList, { start, end })
|
||||
}
|
||||
|
||||
getAvailableCalendars(includesBirthday: boolean): Array<CalendarInfoBase> {
|
||||
return this.calendarModel.getAvailableCalendars(includesBirthday)
|
||||
}
|
||||
|
||||
loadCalendarInfos() {
|
||||
return this.calendarModel.getCalendarInfos()
|
||||
}
|
||||
|
||||
/**
|
||||
* take a list of IDs and load them by list, filtering out the ones that could not be loaded.
|
||||
* updates the passed currentResult.result list to not include the failed IDs anymore
|
||||
|
|
@ -1113,10 +1065,6 @@ export class SearchViewModel {
|
|||
this.search.sendCancelSignal()
|
||||
}
|
||||
|
||||
getLocalCalendars() {
|
||||
return getClientOnlyCalendars(this.logins.getUserController().userId)
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.stopLoadAll()
|
||||
this.extendIndexConfirmationCallback = null
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue