Skip to content

Commit

Permalink
out of office notification, closes #241
Browse files Browse the repository at this point in the history
  • Loading branch information
vaf-hub committed Feb 2, 2021
1 parent e001f91 commit ba6c326
Show file tree
Hide file tree
Showing 17 changed files with 892 additions and 24 deletions.
11 changes: 11 additions & 0 deletions src/api/common/TutanotaConstants.js
Expand Up @@ -27,6 +27,17 @@ export const REQUEST_SIZE_LIMIT_MAP: Map<string, number> = new Map([
["/rest/tutanota/draftservice", 1024 * 1024], // should be large enough
])

export const OutOfOfficeNotificationMessageType = Object.freeze({
Default: "0",
InsideOrganization: "1"
})
export type OutOfOfficeNotificationMessageTypeEnum = $Values<typeof OutOfOfficeNotificationMessageType>

export const OUT_OF_OFFICE_SUBJECT_PREFIX = "Auto-reply: "

export const OUT_OF_OFFICE_SUBJECT_MAX_LENGTH = 128
export const OUT_OF_OFFICE_MESSAGE_MAX_LENGTH = 20 * 1024

export const GroupType = Object.freeze({
User: "0",
Admin: "1",
Expand Down
112 changes: 112 additions & 0 deletions src/api/entities/tutanota/OutOfOfficeNotification.js
@@ -0,0 +1,112 @@
// @flow

import {create, TypeRef} from "../../common/EntityFunctions"

import type {OutOfOfficeNotificationMessage} from "./OutOfOfficeNotificationMessage"

export const OutOfOfficeNotificationTypeRef: TypeRef<OutOfOfficeNotification> = new TypeRef("tutanota", "OutOfOfficeNotification")
export const _TypeModel: TypeModel = {
"name": "OutOfOfficeNotification",
"since": 44,
"type": "ELEMENT_TYPE",
"id": 1131,
"rootId": "CHR1dGFub3RhAARr",
"versioned": false,
"encrypted": false,
"values": {
"_format": {
"name": "_format",
"id": 1135,
"since": 44,
"type": "Number",
"cardinality": "One",
"final": false,
"encrypted": false
},
"_id": {
"name": "_id",
"id": 1133,
"since": 44,
"type": "GeneratedId",
"cardinality": "One",
"final": true,
"encrypted": false
},
"_ownerGroup": {
"name": "_ownerGroup",
"id": 1136,
"since": 44,
"type": "GeneratedId",
"cardinality": "ZeroOrOne",
"final": true,
"encrypted": false
},
"_permissions": {
"name": "_permissions",
"id": 1134,
"since": 44,
"type": "GeneratedId",
"cardinality": "One",
"final": true,
"encrypted": false
},
"enabled": {
"name": "enabled",
"id": 1137,
"since": 44,
"type": "Boolean",
"cardinality": "One",
"final": false,
"encrypted": false
},
"endDate": {
"name": "endDate",
"id": 1139,
"since": 44,
"type": "Date",
"cardinality": "ZeroOrOne",
"final": false,
"encrypted": false
},
"startDate": {
"name": "startDate",
"id": 1138,
"since": 44,
"type": "Date",
"cardinality": "ZeroOrOne",
"final": false,
"encrypted": false
}
},
"associations": {
"notifications": {
"name": "notifications",
"id": 1140,
"since": 44,
"type": "AGGREGATION",
"cardinality": "Any",
"refType": "OutOfOfficeNotificationMessage",
"final": false
}
},
"app": "tutanota",
"version": "44"
}

export function createOutOfOfficeNotification(values?: $Shape<$Exact<OutOfOfficeNotification>>): OutOfOfficeNotification {
return Object.assign(create(_TypeModel, OutOfOfficeNotificationTypeRef), values)
}

export type OutOfOfficeNotification = {
_type: TypeRef<OutOfOfficeNotification>;

_format: NumberString;
_id: Id;
_ownerGroup: ?Id;
_permissions: Id;
enabled: boolean;
endDate: ?Date;
startDate: ?Date;

notifications: OutOfOfficeNotificationMessage[];
}
69 changes: 69 additions & 0 deletions src/api/entities/tutanota/OutOfOfficeNotificationMessage.js
@@ -0,0 +1,69 @@
// @flow

import {create, TypeRef} from "../../common/EntityFunctions"


export const OutOfOfficeNotificationMessageTypeRef: TypeRef<OutOfOfficeNotificationMessage> = new TypeRef("tutanota", "OutOfOfficeNotificationMessage")
export const _TypeModel: TypeModel = {
"name": "OutOfOfficeNotificationMessage",
"since": 44,
"type": "AGGREGATED_TYPE",
"id": 1126,
"rootId": "CHR1dGFub3RhAARm",
"versioned": false,
"encrypted": false,
"values": {
"_id": {
"name": "_id",
"id": 1127,
"since": 44,
"type": "CustomId",
"cardinality": "One",
"final": true,
"encrypted": false
},
"message": {
"name": "message",
"id": 1129,
"since": 44,
"type": "String",
"cardinality": "One",
"final": false,
"encrypted": false
},
"subject": {
"name": "subject",
"id": 1128,
"since": 44,
"type": "String",
"cardinality": "One",
"final": false,
"encrypted": false
},
"type": {
"name": "type",
"id": 1130,
"since": 44,
"type": "Number",
"cardinality": "One",
"final": false,
"encrypted": false
}
},
"associations": {},
"app": "tutanota",
"version": "44"
}

export function createOutOfOfficeNotificationMessage(values?: $Shape<$Exact<OutOfOfficeNotificationMessage>>): OutOfOfficeNotificationMessage {
return Object.assign(create(_TypeModel, OutOfOfficeNotificationMessageTypeRef), values)
}

export type OutOfOfficeNotificationMessage = {
_type: TypeRef<OutOfOfficeNotificationMessage>;

_id: Id;
message: string;
subject: string;
type: NumberString;
}
6 changes: 3 additions & 3 deletions src/gui/base/Banner.js
Expand Up @@ -20,7 +20,7 @@ export const BannerType = Object.freeze({
})
export type BannerTypeEnum = $Values<typeof BannerType>

export type Attrs = {
export type BannerAttrs = {
icon: AllIconsEnum,
title: string,
message: string,
Expand All @@ -29,8 +29,8 @@ export type Attrs = {
type: BannerTypeEnum
}

export class Banner implements MComponent<Attrs> {
view({attrs}: Vnode<Attrs>): Children {
export class Banner implements MComponent<BannerAttrs> {
view({attrs}: Vnode<BannerAttrs>): Children {
const colors = getColors(attrs.type)
const isVertical = attrs.type === BannerType.Warning
return m(MessageBoxN, {
Expand Down
14 changes: 8 additions & 6 deletions src/gui/base/BubbleTextField.js
Expand Up @@ -85,6 +85,8 @@ export class BubbleTextField<T> {

this.bubbles = []

//TODO the class .flex-wrap was removed in TextFieldN and needs to be reinserted for the BubbleTextField exclusively
// see the related TODO in TextFieldN.js
this.textField._injectionsLeft = () => this.bubbles.map((b, i) => {
// We need overflow: hidden on both so that ellipsis on button works.
// flex is for reserving space for the comma. align-items: end so that comma is pushed to the bottom.
Expand Down Expand Up @@ -188,7 +190,7 @@ export class BubbleTextField<T> {
switch (key.keyCode) {
case 13: // return
case 32: // whitespace
return this.createBubbles() || false
return this.createBubbles() || false
case 8:
return this.handleBackspace()
case 46:
Expand All @@ -206,12 +208,12 @@ export class BubbleTextField<T> {
case 17: // do not react on ctrl key
return true
}

// Handle commas
if (key.key === ",") {
return this.createBubbles() || false
}
if (key.key === ",") {
return this.createBubbles() || false
}

this.removeBubbleSelection()
return true
}
Expand Down
2 changes: 1 addition & 1 deletion src/gui/base/Dialog.js
Expand Up @@ -150,7 +150,7 @@ export class Dialog {
}

/**
* By default the focus is set on the first text field after this dialog is fully visible. This behavor can be overwritten by calling this function.
* By default the focus is set on the first text field after this dialog is fully visible. This behavior can be overwritten by calling this function.
*/
setFocusOnLoadFunction(callback: Function): void {
this._focusOnLoadFunction = callback
Expand Down
7 changes: 6 additions & 1 deletion src/gui/base/HtmlEditor.js
Expand Up @@ -108,7 +108,12 @@ export class HtmlEditor {
this._editor.isEnabled() && injections ? injections() : null,
]),
this._editor.isEnabled() ? m("hr.hr.mb-s") : null,
m(this._editor)
m(this._editor,
{
oncreate: vnode => this._editor.initialized.promise.then(() => this._editor.setHTML(this._value())),
onremove: vnode => this._value(this.getValue())
}
)
]) : null,
this._mode() === Mode.HTML ? m(".html", m("textarea.input-area.selectable", {
oncreate: vnode => {
Expand Down
6 changes: 4 additions & 2 deletions src/gui/base/TextFieldN.js
Expand Up @@ -16,7 +16,7 @@ export type TextFieldAttrs = {
type?: TextFieldTypeEnum,
helpLabel?: ?lazy<Children>,
alignRight?: boolean,
injectionsLeft?: lazy<Children>, // only used by the BubbleTextField to display bubbles
injectionsLeft?: lazy<Children>, // only used by the BubbleTextField to display bubbles and out of office notification
injectionsRight?: lazy<Children>,
keyHandler?: keyHandler, // interceptor used by the BubbleTextField to react on certain keys
onfocus?: (dom: HTMLElement, input: HTMLInputElement) => mixed,
Expand Down Expand Up @@ -78,7 +78,9 @@ export class TextFieldN implements MComponent<TextFieldAttrs> {
}
}, lang.getMaybeLazy(a.label)),
m(".flex.flex-column", [ // another wrapper to fix IE 11 min-height bug https://github.com/philipwalton/flexbugs#3-min-height-on-a-flex-container-wont-apply-to-its-flex-items
m(".flex.items-end.flex-wrap", {
//TODO we need to add the class "flex-wrap" to the component below for BubbleTextFieldN
// once we refactor to have a BubbleTextFieldN component that uses this TextFieldN instead of TextField
m(".flex.items-end", { // .flex-wrap
style: {
'min-height': px(size.button_height + 2), // 2 px border
'padding-bottom': this.active ? px(0) : px(1),
Expand Down
36 changes: 32 additions & 4 deletions src/login/LoginViewController.js
Expand Up @@ -13,7 +13,7 @@ import {
TooManyRequestsError
} from "../api/common/error/RestError"
import {load, serviceRequestVoid, update} from "../api/main/Entity"
import {assertMainOrNode, isAdminClient, isApp, LOGIN_TITLE, Mode} from "../api/Env"
import {assertMainOrNode, isAdminClient, isApp, isTutanotaDomain, LOGIN_TITLE, Mode} from "../api/Env"
import {CloseEventBusOption, Const} from "../api/common/TutanotaConstants"
import {CustomerPropertiesTypeRef} from "../api/entities/sys/CustomerProperties"
import {neverNull, noOp} from "../api/common/utils/Utils"
Expand Down Expand Up @@ -42,6 +42,10 @@ import {locator} from "../api/main/MainLocator"
import {checkApprovalStatus} from "../misc/LoginUtils"
import {getHourCycle} from "../misc/Formatter"
import {formatPrice} from "../subscription/PriceUtils"
import {showEditOutOfOfficeNotificationDialog} from "../settings/EditOutOfOfficeNotificationDialog"
import * as notificationOverlay from "../gui/base/NotificationOverlay"
import {ButtonType} from "../gui/base/ButtonN"
import {isNotificationCurrentlyActive, loadOutOfOfficeNotification} from "../settings/OutOfOfficeNotificationUtils"
import {showMoreStorageNeededOrderDialog} from "../subscription/SubscriptionUtils"

assertMainOrNode()
Expand Down Expand Up @@ -266,10 +270,34 @@ export class LoginViewController implements ILoginViewController {
if (!isAdminClient()) {
return locator.calendarModel.init()
}
}).then(() => {
lang.updateFormats({
hourCycle: getHourCycle(logins.getUserController().userSettingsGroupRoot)
})
.then(() => {
lang.updateFormats({
hourCycle: getHourCycle(logins.getUserController().userSettingsGroupRoot)
})
})
.then(() => {
return this._remindActiveOutOfOfficeNotification()
})
}

_remindActiveOutOfOfficeNotification(): Promise<void> {
return loadOutOfOfficeNotification().then((notification) => {
if (notification && isNotificationCurrentlyActive(notification, new Date())) {
const notificationMessage: Component = {
view: () => {
return m("", lang.get("outOfOfficeReminder_label"))
}
}
notificationOverlay.show(notificationMessage, {label: "close_alt"}, [
{
label: "deactivate_action",
click: () => showEditOutOfOfficeNotificationDialog(notification),
type: ButtonType.Primary
}
])

}
})
}

Expand Down
21 changes: 20 additions & 1 deletion src/misc/TranslationKey.js
Expand Up @@ -1267,4 +1267,23 @@ export type TranslationKeyType = "about_label"
| "yourMessage_label"
| "you_label"
| "emptyString_msg"
| "dragAndDropExport_action"
| "dragAndDropExport_action"
// TODO
| "outOfOfficeNotification_title"
| "outOfOfficeDefaultSubject_msg"
| "outOfOfficeDefault_msg"
| "invalidTimePeriod_msg"
| "message_label"
| "outOfOfficeInternal_msg"
| "outOfOfficeExternal_msg"
| "outOfOfficeEveryone_msg"
| "outOfOfficeMessageInvalid_msg"
| "outOfOfficeTimeRange_msg"
| "outOfOfficeTimeRangeHelp_msg"
| "outOfOfficeReminder_label"
| "outOfOfficeRecipients_label"
| "insideOnly_label"
| "insideOutside_label"
| "everyone_label"
| "outOfOfficeRecipientsHelp_label"
| "outOfOfficeUnencrypted_msg"

0 comments on commit ba6c326

Please sign in to comment.