@social-mail/social-mail-client 1.0.1 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -91
- package/dist/admin/Admin.js +50 -2
- package/dist/admin/Admin.js.map +1 -1
- package/dist/admin/commands/AdminCommands.js +41 -0
- package/dist/admin/commands/AdminCommands.js.map +1 -1
- package/dist/admin/drawer/AdminDrawer.js +40 -2
- package/dist/admin/drawer/AdminDrawer.js.map +1 -1
- package/dist/admin/pages/config/ConfigPage.js +18 -0
- package/dist/admin/pages/config/ConfigPage.js.map +1 -0
- package/dist/admin/pages/domains/ChooseDomainPage.js +86 -0
- package/dist/admin/pages/domains/ChooseDomainPage.js.map +1 -0
- package/dist/admin/pages/domains/DomainListPage.js +71 -0
- package/dist/admin/pages/domains/DomainListPage.js.map +1 -0
- package/dist/admin/pages/domains/EditDomainPage.js +79 -0
- package/dist/admin/pages/domains/EditDomainPage.js.map +1 -0
- package/dist/admin/pages/domains/choose/ChooseDomainPage.js +86 -0
- package/dist/admin/pages/domains/choose/ChooseDomainPage.js.map +1 -0
- package/dist/admin/pages/domains/edit/EditDomainPage.js +104 -0
- package/dist/admin/pages/domains/edit/EditDomainPage.js.map +1 -0
- package/dist/admin/pages/domains/services/AdminDomainService.js +56 -0
- package/dist/admin/pages/domains/services/AdminDomainService.js.map +1 -0
- package/dist/admin/pages/home/AdminHome.js +18 -0
- package/dist/admin/pages/home/AdminHome.js.map +1 -0
- package/dist/admin/pages/users/EditUserPage.js +105 -0
- package/dist/admin/pages/users/EditUserPage.js.map +1 -0
- package/dist/admin/pages/users/UserListPage.js +82 -0
- package/dist/admin/pages/users/UserListPage.js.map +1 -0
- package/dist/admin/pages/users/services/AdminUserService.js +57 -0
- package/dist/admin/pages/users/services/AdminUserService.js.map +1 -0
- package/dist/common/SocialMailApp.js +14 -0
- package/dist/common/SocialMailApp.js.map +1 -0
- package/dist/common/Sql.js +12 -0
- package/dist/common/Sql.js.map +1 -0
- package/dist/common/commands/CommonCommands.js +18 -0
- package/dist/common/commands/CommonCommands.js.map +1 -0
- package/dist/common/controls/buttons/IconButton.js +51 -0
- package/dist/common/controls/buttons/IconButton.js.map +1 -0
- package/dist/common/controls/form/RequiredFormField.js +57 -0
- package/dist/common/controls/form/RequiredFormField.js.map +1 -0
- package/dist/common/controls/repeater/InfiniteRepeater.js +100 -0
- package/dist/common/controls/repeater/InfiniteRepeater.js.map +1 -0
- package/dist/common/pages/LoginPage.js +79 -0
- package/dist/common/pages/LoginPage.js.map +1 -0
- package/dist/common/pages/mailboxes/MailboxEditorPage.js +79 -0
- package/dist/common/pages/mailboxes/MailboxEditorPage.js.map +1 -0
- package/dist/common/pages/mailboxes/MailboxListPage.js +80 -0
- package/dist/common/pages/mailboxes/MailboxListPage.js.map +1 -0
- package/dist/common/pages/mailboxes/services/MailboxService.js +60 -0
- package/dist/common/pages/mailboxes/services/MailboxService.js.map +1 -0
- package/dist/common/pages/user/LoginPage.js +79 -0
- package/dist/common/pages/user/LoginPage.js.map +1 -0
- package/dist/model/model.js +37 -0
- package/dist/model/model.js.map +1 -1
- package/dist/services/BusyProgress.js +2 -2
- package/dist/services/ConfigService.js +45 -0
- package/dist/services/ConfigService.js.map +1 -0
- package/dist/services/EntityService.js +4 -0
- package/dist/services/EntityService.js.map +1 -1
- package/dist/services/UserService.js +54 -0
- package/dist/services/UserService.js.map +1 -0
- package/dist/style/GlobalStyle.js +13 -5
- package/dist/style/GlobalStyle.js.map +1 -1
- package/dist/style/OverrideAppStyle.js +22 -0
- package/dist/style/OverrideAppStyle.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/web/AppIndex.js +38 -2
- package/dist/web/AppIndex.js.map +1 -1
- package/node_modules/@web-atoms/entity/dist/services/BaseEntityService.d.ts +5 -2
- package/node_modules/@web-atoms/entity/dist/services/BaseEntityService.d.ts.map +1 -1
- package/node_modules/@web-atoms/entity/dist/services/BaseEntityService.js +18 -1
- package/node_modules/@web-atoms/entity/dist/services/BaseEntityService.js.map +1 -1
- package/node_modules/@web-atoms/entity/dist/services/MockBaseEntityService.d.ts +2 -0
- package/node_modules/@web-atoms/entity/dist/services/MockBaseEntityService.d.ts.map +1 -1
- package/node_modules/@web-atoms/entity/dist/services/MockBaseEntityService.js +1 -0
- package/node_modules/@web-atoms/entity/dist/services/MockBaseEntityService.js.map +1 -1
- package/node_modules/@web-atoms/entity/dist/services/Query.d.ts +2 -0
- package/node_modules/@web-atoms/entity/dist/services/Query.d.ts.map +1 -1
- package/node_modules/@web-atoms/entity/dist/services/Query.js +9 -6
- package/node_modules/@web-atoms/entity/dist/services/Query.js.map +1 -1
- package/node_modules/@web-atoms/entity/dist/services/QueryProcessor.d.ts +2 -0
- package/node_modules/@web-atoms/entity/dist/services/QueryProcessor.d.ts.map +1 -0
- package/node_modules/@web-atoms/entity/dist/services/QueryProcessor.js +9 -0
- package/node_modules/@web-atoms/entity/dist/services/QueryProcessor.js.map +1 -0
- package/node_modules/@web-atoms/entity/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@web-atoms/entity/package.json +1 -1
- package/node_modules/@web-atoms/entity/src/services/BaseEntityService.ts +23 -3
- package/node_modules/@web-atoms/entity/src/services/MockBaseEntityService.ts +3 -0
- package/node_modules/@web-atoms/entity/src/services/Query.ts +12 -7
- package/node_modules/@web-atoms/entity/src/services/QueryProcessor.ts +1 -0
- package/package.json +2 -2
- package/src/admin/Admin.tsx +40 -1
- package/src/admin/commands/AdminCommands.ts +33 -0
- package/src/admin/drawer/AdminDrawer.tsx +70 -0
- package/src/admin/pages/config/ConfigPage.tsx +5 -0
- package/src/admin/pages/domains/DomainListPage.tsx +43 -0
- package/src/admin/pages/domains/choose/ChooseDomainPage.tsx +57 -0
- package/src/admin/pages/domains/edit/EditDomainPage.tsx +102 -0
- package/src/admin/pages/domains/services/AdminDomainService.ts +34 -0
- package/src/admin/pages/home/AdminHome.tsx +6 -0
- package/src/admin/pages/users/EditUserPage.tsx +85 -0
- package/src/admin/pages/users/UserListPage.tsx +53 -0
- package/src/admin/pages/users/services/AdminUserService.ts +34 -0
- package/src/common/SocialMailApp.ts +7 -0
- package/src/common/Sql.ts +74 -0
- package/src/common/commands/CommonCommands.ts +6 -0
- package/src/common/controls/buttons/IconButton.tsx +60 -0
- package/src/common/controls/form/RequiredFormField.tsx +53 -0
- package/src/common/controls/repeater/InfiniteRepeater.tsx +93 -0
- package/src/common/pages/mailboxes/MailboxEditorPage.tsx +66 -0
- package/src/common/pages/mailboxes/MailboxListPage.tsx +48 -0
- package/src/common/pages/mailboxes/services/MailboxService.ts +34 -0
- package/src/common/pages/user/LoginPage.tsx +51 -0
- package/src/model/model.ts +234 -0
- package/src/services/BusyProgress.ts +2 -2
- package/src/services/ConfigService.ts +19 -0
- package/src/services/EntityService.ts +3 -0
- package/src/services/UserService.ts +26 -0
- package/src/style/GlobalStyle.ts +11 -2
- package/src/style/OverrideAppStyle.ts +9 -0
- package/src/web/AppIndex.tsx +26 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import XNode from "@web-atoms/core/dist/core/XNode";
|
|
2
|
+
import styled from "@web-atoms/core/dist/style/styled";
|
|
3
|
+
import IElement from "@web-atoms/web-controls/dist/basic/IElement";
|
|
4
|
+
|
|
5
|
+
styled.css `
|
|
6
|
+
`.installGlobal("[data-layout=icon-button]");
|
|
7
|
+
|
|
8
|
+
export interface IButtonOptions extends IElement {
|
|
9
|
+
text?: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const createButton = ({
|
|
14
|
+
text: defaultText = void 0,
|
|
15
|
+
icon: defaultIcon = void 0,
|
|
16
|
+
layout: defaultLayout = ""
|
|
17
|
+
}: IButtonOptions) =>
|
|
18
|
+
({
|
|
19
|
+
text = defaultText,
|
|
20
|
+
icon = defaultIcon,
|
|
21
|
+
layout = defaultLayout || (icon ? "icon-button" : "button"),
|
|
22
|
+
... a
|
|
23
|
+
}: IButtonOptions) => {
|
|
24
|
+
a["data-layout"] = layout;
|
|
25
|
+
if (text && icon) {
|
|
26
|
+
return <button { ... a}>
|
|
27
|
+
<i class={icon}/>
|
|
28
|
+
<span text={text}/>
|
|
29
|
+
</button>;
|
|
30
|
+
}
|
|
31
|
+
if (icon) {
|
|
32
|
+
return <button { ... a} class={icon}/>;
|
|
33
|
+
}
|
|
34
|
+
return <button { ... a} text={text}/>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const IconButton = createButton({});
|
|
38
|
+
|
|
39
|
+
export const AddIconButton = createButton({ icon: "fas fa-plus", layout: "go-button" });
|
|
40
|
+
export const AddIconTextButton = createButton({ text : "Add", icon: "fas fa-plus", layout: "go-icon-button" });
|
|
41
|
+
|
|
42
|
+
export const ArchiveIconButton = createButton({ icon: "fas fa-trash-can", layout: "warning-button" });
|
|
43
|
+
export const ArchiveIconTextButton = createButton({ text : "Archive", icon: "fas fa-trash-can", layout: "warning-icon-button" });
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export const DeleteIconButton = createButton({ icon: "fas fa-trash-can", layout: "reject-button" });
|
|
47
|
+
export const DeleteIconTextButton = createButton({ text : "Delete", icon: "fas fa-trash-can", layout: "reject-icon-button" });
|
|
48
|
+
|
|
49
|
+
export const EmailIconButton = createButton({ icon: "fas fa-envelope", layout: "go-button" });
|
|
50
|
+
export const EmailIconTextButton = createButton({ text : "Email", icon: "fas fa-envelope", layout: "go-icon-button" });
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export const SaveIconButton = createButton({ icon: "fas fa-floppy-disk", layout: "accept-button" });
|
|
54
|
+
export const SaveIconTextButton = createButton({ text : "Save", icon: "fas fa-floppy-disk", layout: "accept-icon-button" });
|
|
55
|
+
|
|
56
|
+
export const EditIconButton = createButton({ icon: "fas fa-edit", layout: "button"});
|
|
57
|
+
export const EditIconTextButton = createButton({ text : "Edit", icon: "fas fa-edit", layout: "icon-button"});
|
|
58
|
+
|
|
59
|
+
export const UploadIconButton = createButton({ icon: "fas fa-upload", layout: "go-button" });
|
|
60
|
+
export const UploadIconTextButton = createButton({ text : "Upload", icon: "fas fa-upload", layout: "go-icon-button" });
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import Bind from "@web-atoms/core/dist/core/Bind";
|
|
2
|
+
import XNode from "@web-atoms/core/dist/core/XNode";
|
|
3
|
+
import { BindError } from "@web-atoms/web-controls/dist/basic/Form";
|
|
4
|
+
import FormField from "@web-atoms/web-controls/dist/basic/FormField";
|
|
5
|
+
import IElement from "@web-atoms/web-controls/dist/basic/IElement";
|
|
6
|
+
import PasswordBox from "@web-atoms/web-controls/dist/basic/PasswordBox";
|
|
7
|
+
|
|
8
|
+
export interface IRequiredFormField<T = any> extends IElement {
|
|
9
|
+
label: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
value: () => T;
|
|
12
|
+
message?: (v: string, label: string) => string;
|
|
13
|
+
isValid?: (v: string) => boolean;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
valuePropertyName?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function RequiredFormField<T = any> ({
|
|
19
|
+
label,
|
|
20
|
+
value,
|
|
21
|
+
required = true,
|
|
22
|
+
message,
|
|
23
|
+
isValid,
|
|
24
|
+
placeholder,
|
|
25
|
+
valuePropertyName = "value",
|
|
26
|
+
... extra
|
|
27
|
+
}: IRequiredFormField<T>, node: XNode) {
|
|
28
|
+
const a = (node.attributes ??= {});
|
|
29
|
+
a[valuePropertyName] = Bind.twoWaysImmediate(value);
|
|
30
|
+
if (placeholder) {
|
|
31
|
+
a.placeholder = placeholder;
|
|
32
|
+
}
|
|
33
|
+
for (const key in extra) {
|
|
34
|
+
if (Object.prototype.hasOwnProperty.call(extra, key)) {
|
|
35
|
+
const element = extra[key];
|
|
36
|
+
a[key] ??= element;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return <FormField
|
|
40
|
+
required={required}
|
|
41
|
+
label={label}
|
|
42
|
+
error={BindError({ value, message, isValid})}>
|
|
43
|
+
{ node }
|
|
44
|
+
</FormField>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function TextFormField( a: IRequiredFormField) {
|
|
48
|
+
return <RequiredFormField { ... a}><input type="text"/></RequiredFormField>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function PasswordFormField(a: IRequiredFormField) {
|
|
52
|
+
return <RequiredFormField { ... a}><PasswordBox value={Bind.twoWaysImmediate(a.value)}/></RequiredFormField>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Bind from "@web-atoms/core/dist/core/Bind";
|
|
2
|
+
import { CancelToken } from "@web-atoms/core/dist/core/types";
|
|
3
|
+
import XNode from "@web-atoms/core/dist/core/XNode";
|
|
4
|
+
import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
|
|
5
|
+
import AtomRepeater from "@web-atoms/web-controls/dist/basic/AtomRepeater";
|
|
6
|
+
|
|
7
|
+
const loadMoreSet = Symbol("loadMoreSet");
|
|
8
|
+
|
|
9
|
+
export interface IAnyPagedItems {
|
|
10
|
+
items: any[];
|
|
11
|
+
total: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IInfiniteRepeater extends AtomRepeater {
|
|
15
|
+
pagedItems: IAnyPagedItems;
|
|
16
|
+
[loadMoreSet]: CancelToken;
|
|
17
|
+
start: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const pagedItemsSetter = AtomControl.registerProperty("paged-items", "paged-items",
|
|
21
|
+
(control: IInfiniteRepeater, element, value) => {
|
|
22
|
+
if (value && value.items) {
|
|
23
|
+
let cancelToken = control[loadMoreSet];
|
|
24
|
+
control.footerRenderer ??= () => <div data-click-event="load-more-items"><span text="Load more ... "/></div>;
|
|
25
|
+
control.headerRenderer ??= () => <div class="scroll-header" style-height="1px" style-width="100%"/>;
|
|
26
|
+
if (!cancelToken) {
|
|
27
|
+
control[loadMoreSet] = cancelToken = new CancelToken();
|
|
28
|
+
control.bindEvent(control.element, "loadMoreItems", (e: any) => {
|
|
29
|
+
const ce = new CustomEvent("moreItems", {
|
|
30
|
+
detail: {
|
|
31
|
+
pagedItems: control.pagedItems,
|
|
32
|
+
cancelToken
|
|
33
|
+
},
|
|
34
|
+
bubbles: false
|
|
35
|
+
});
|
|
36
|
+
control.element.dispatchEvent(ce);
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
cancelToken.cancel();
|
|
40
|
+
control[loadMoreSet] = cancelToken = new CancelToken();
|
|
41
|
+
}
|
|
42
|
+
const items = control.items;
|
|
43
|
+
if (items && Array.isArray(items)) {
|
|
44
|
+
items.clear();
|
|
45
|
+
items.addAll(value.items);
|
|
46
|
+
} else {
|
|
47
|
+
control.items = value.items;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
const ce = control.element?.firstElementChild as HTMLElement;
|
|
52
|
+
ce?.scrollIntoView();
|
|
53
|
+
}, 100);
|
|
54
|
+
|
|
55
|
+
control.footer = value.total && value.items && value.items.length < value.total;
|
|
56
|
+
(control as any).pagedItems = value;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type RepeaterLoader = (start: number) => (control: AtomControl, element: HTMLElement, cancelToken: CancelToken) => any;
|
|
61
|
+
|
|
62
|
+
const scrollToBottom = ({ target, detail: { type, index}}) => {
|
|
63
|
+
if (!(type === "add" && index === 0) && type !== "reset") {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
const e = target.firstElementChild as HTMLElement;
|
|
68
|
+
console.log(e);
|
|
69
|
+
e?.scrollIntoView(true);
|
|
70
|
+
}, 1);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default class InfiniteRepeater {
|
|
74
|
+
|
|
75
|
+
public static pagedItems = (start: RepeaterLoader) => {
|
|
76
|
+
const setter = start(0);
|
|
77
|
+
const setters = pagedItemsSetter(Bind.oneWayAsync(setter)) as any;
|
|
78
|
+
setters["event-more-items"] = Bind.event(async (s: any, e) => {
|
|
79
|
+
const st = s.items?.length ?? 0;
|
|
80
|
+
if (!st) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const cancelToken = s[loadMoreSet];
|
|
84
|
+
const loader = start(st);
|
|
85
|
+
const results = await loader(s, e.target as any, cancelToken);
|
|
86
|
+
s.items.addAll(results.items);
|
|
87
|
+
s.footer = results.items.length > 0;
|
|
88
|
+
});
|
|
89
|
+
// setters["event-items-updated"] = scrollToBottom;
|
|
90
|
+
return setters;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PopupWindowPage } from "@web-atoms/web-controls/dist/mobile-app/MobileApp";
|
|
2
|
+
import { EmailAddress, IDomain, IMailbox, Mailbox } from "../../../model/model";
|
|
3
|
+
import XNode from "@web-atoms/core/dist/core/XNode";
|
|
4
|
+
import Form from "@web-atoms/web-controls/dist/basic/Form";
|
|
5
|
+
import { TextFormField } from "../../controls/form/RequiredFormField";
|
|
6
|
+
import FormField from "@web-atoms/web-controls/dist/basic/FormField";
|
|
7
|
+
import ComboBox from "@web-atoms/web-controls/dist/basic/ComboBox";
|
|
8
|
+
import MailboxService from "./services/MailboxService";
|
|
9
|
+
import InjectProperty from "@web-atoms/core/dist/core/InjectProperty";
|
|
10
|
+
import Bind from "@web-atoms/core/dist/core/Bind";
|
|
11
|
+
|
|
12
|
+
export interface IMailboxEx extends IMailbox {
|
|
13
|
+
prefix?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default class MailboxEditorPage extends PopupWindowPage {
|
|
17
|
+
|
|
18
|
+
public parameters: { model: IMailboxEx };
|
|
19
|
+
|
|
20
|
+
private model: IMailboxEx;
|
|
21
|
+
|
|
22
|
+
private domain: IDomain = null;
|
|
23
|
+
|
|
24
|
+
@InjectProperty
|
|
25
|
+
private mailboxService: MailboxService;
|
|
26
|
+
|
|
27
|
+
async init() {
|
|
28
|
+
|
|
29
|
+
this.title = "Add Mailbox";
|
|
30
|
+
|
|
31
|
+
this.model = this.parameters?.model ?? Mailbox.create({
|
|
32
|
+
emailAddress: EmailAddress.create({
|
|
33
|
+
emailAddress: ""
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.model.prefix = "";
|
|
38
|
+
|
|
39
|
+
const domains = await this.mailboxService.domains();
|
|
40
|
+
this.domain = domains[0];
|
|
41
|
+
|
|
42
|
+
this.renderer = <div>
|
|
43
|
+
<Form data-submit-event="save-mailbox">
|
|
44
|
+
<TextFormField label="Email Prefix" value={() => this.model.prefix}/>
|
|
45
|
+
<FormField label="Domain">
|
|
46
|
+
<ComboBox
|
|
47
|
+
labelPath={(item: IDomain) => item?.name}
|
|
48
|
+
valuePath={(item: IDomain) => item?.domainID}
|
|
49
|
+
selectedItem={Bind.twoWaysImmediate(() => this.domain)}
|
|
50
|
+
items={domains}
|
|
51
|
+
/>
|
|
52
|
+
</FormField>
|
|
53
|
+
<FormField label="Email Address">
|
|
54
|
+
<span text={Bind.oneWay(() => `${this.model.prefix}@${this.domain.name}`)}/>
|
|
55
|
+
</FormField>
|
|
56
|
+
<div data-layout="command-row">
|
|
57
|
+
<button
|
|
58
|
+
data-layout="accept-button"
|
|
59
|
+
text="Add"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</Form>
|
|
63
|
+
</div>;
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import Bind from "@web-atoms/core/dist/core/Bind";
|
|
2
|
+
import XNode from "@web-atoms/core/dist/core/XNode";
|
|
3
|
+
import AtomRepeater from "@web-atoms/web-controls/dist/basic/AtomRepeater";
|
|
4
|
+
import { ContentPage } from "@web-atoms/web-controls/dist/mobile-app/MobileApp";
|
|
5
|
+
import InfiniteRepeater from "../../controls/repeater/InfiniteRepeater";
|
|
6
|
+
import MailboxService from "./services/MailboxService";
|
|
7
|
+
import InjectProperty from "@web-atoms/core/dist/core/InjectProperty";
|
|
8
|
+
import { IMailbox } from "../../../model/model";
|
|
9
|
+
import Action from "@web-atoms/core/dist/view-model/Action";
|
|
10
|
+
import PageNavigator from "@web-atoms/web-controls/dist/PageNavigator";
|
|
11
|
+
import MailboxEditorPage from "./MailboxEditorPage";
|
|
12
|
+
|
|
13
|
+
export default class MailboxListPage extends ContentPage {
|
|
14
|
+
|
|
15
|
+
private search: string = "";
|
|
16
|
+
|
|
17
|
+
@InjectProperty
|
|
18
|
+
private mailboxService: MailboxService;
|
|
19
|
+
|
|
20
|
+
private mailboxes: IMailbox[] = [];
|
|
21
|
+
|
|
22
|
+
private version = 0;
|
|
23
|
+
|
|
24
|
+
async init() {
|
|
25
|
+
this.titleRenderer = () => <div data-layout="row">
|
|
26
|
+
<input value={Bind.twoWaysImmediate(() => this.search)}/>
|
|
27
|
+
<i class="fas fa-plus" data-click-event="add-mailbox"/>
|
|
28
|
+
</div>;
|
|
29
|
+
|
|
30
|
+
this.renderer = <div>
|
|
31
|
+
<AtomRepeater
|
|
32
|
+
items={this.mailboxes}
|
|
33
|
+
{ ... InfiniteRepeater.pagedItems((start) =>
|
|
34
|
+
(c, e, cancelToken) => this.mailboxService.list({ start, cancelToken, search: this.search, version: this.version }))}
|
|
35
|
+
itemRenderer={(item: IMailbox) => <tr>
|
|
36
|
+
<td></td>
|
|
37
|
+
</tr>}
|
|
38
|
+
/>
|
|
39
|
+
</div>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Action({ onEvent: "add-mailbox"})
|
|
43
|
+
async addMailbox() {
|
|
44
|
+
await PageNavigator.pushPageForResult(MailboxEditorPage);
|
|
45
|
+
this.version++;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import DITransient from "@web-atoms/core/dist/di/DITransient";
|
|
2
|
+
import { Inject } from "@web-atoms/core/dist/di/Inject";
|
|
3
|
+
import EntityService from "../../../../services/EntityService";
|
|
4
|
+
import { Domain, Mailbox } from "../../../../model/model";
|
|
5
|
+
import { Sql } from "../../../Sql";
|
|
6
|
+
|
|
7
|
+
@DITransient()
|
|
8
|
+
export default class MailboxService {
|
|
9
|
+
|
|
10
|
+
@Inject
|
|
11
|
+
private entityService: EntityService;
|
|
12
|
+
|
|
13
|
+
public domains() {
|
|
14
|
+
return this.entityService.query(Domain).toArray();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public list({ search = "", start = 0, size = 50, version = 0, cancelToken}) {
|
|
18
|
+
let q = this.entityService.query(Mailbox);
|
|
19
|
+
if (search) {
|
|
20
|
+
if (search.endsWith("@")) {
|
|
21
|
+
search = search + "%";
|
|
22
|
+
q = q.where({ search }, (p) => (x) => Sql.text.like(x.emailAddress.emailAddress, p.search));
|
|
23
|
+
// q = q.where({ search }, (p) => (x) => Sql.text.st x.emailAddress.emailAddress)
|
|
24
|
+
} else if(search.startsWith("@")) {
|
|
25
|
+
search = "%" + search;
|
|
26
|
+
q = q.where({ search }, (p) => (x) => Sql.text.like(x.emailAddress.emailAddress, p.search));
|
|
27
|
+
} else {
|
|
28
|
+
q = q.where({ search }, (p) => (x) => x.emailAddress.emailAddress === p.search);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return q.toPagedList({ start, size, cancelToken });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import XNode from "@web-atoms/core/dist/core/XNode";
|
|
2
|
+
import { ContentPage } from "@web-atoms/web-controls/dist/mobile-app/MobileApp";
|
|
3
|
+
import Form from "@web-atoms/web-controls/dist/basic/Form";
|
|
4
|
+
import FormField from "@web-atoms/web-controls/dist/basic/FormField";
|
|
5
|
+
import { ILoginSession, LoginSession } from "../../../model/model";
|
|
6
|
+
import Bind from "@web-atoms/core/dist/core/Bind";
|
|
7
|
+
import PasswordBox from "@web-atoms/web-controls/dist/basic/PasswordBox";
|
|
8
|
+
import Action from "@web-atoms/core/dist/view-model/Action";
|
|
9
|
+
import InjectProperty from "@web-atoms/core/dist/core/InjectProperty";
|
|
10
|
+
import UserService from "../../../services/UserService";
|
|
11
|
+
|
|
12
|
+
export default class LoginPage extends ContentPage {
|
|
13
|
+
|
|
14
|
+
@InjectProperty
|
|
15
|
+
private userService: UserService;
|
|
16
|
+
|
|
17
|
+
private model: ILoginSession & { userName?: string, checkPassword?: string };
|
|
18
|
+
|
|
19
|
+
async init() {
|
|
20
|
+
this.model = LoginSession.create({});
|
|
21
|
+
this.model.userName = "";
|
|
22
|
+
this.model.checkPassword = "";
|
|
23
|
+
this.render(<div>
|
|
24
|
+
<div data-layout="center-all">
|
|
25
|
+
<Form data-submit-event="login">
|
|
26
|
+
<FormField label="Username">
|
|
27
|
+
<input value={Bind.twoWaysImmediate(() => this.model.userName)}/>
|
|
28
|
+
</FormField>
|
|
29
|
+
<FormField label="Username">
|
|
30
|
+
<PasswordBox
|
|
31
|
+
value={Bind.twoWaysImmediate(() => this.model.checkPassword)}
|
|
32
|
+
/>
|
|
33
|
+
</FormField>
|
|
34
|
+
<div>
|
|
35
|
+
<button
|
|
36
|
+
data-layout="accent-button"
|
|
37
|
+
data-type="submit"
|
|
38
|
+
text="Login"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</Form>
|
|
42
|
+
</div>
|
|
43
|
+
</div>);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Action({ onEvent: "login" })
|
|
47
|
+
async login() {
|
|
48
|
+
await this.userService.login(this.model);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
package/src/model/model.ts
CHANGED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import DateTime from "@web-atoms/date-time/dist/DateTime";
|
|
2
|
+
import type IClrEntity from "@web-atoms/entity/dist/models/IClrEntity";
|
|
3
|
+
import { ICollection, IGeometry, IModel, Model } from "@web-atoms/entity/dist/services/BaseEntityService";
|
|
4
|
+
|
|
5
|
+
export interface IAppRole extends IClrEntity {
|
|
6
|
+
roleName?: string;
|
|
7
|
+
roles?: ICollection<IUserRole>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const AppRole: IModel<IAppRole> = new Model<IAppRole>("AppRole", { roleName: "" });
|
|
11
|
+
|
|
12
|
+
export interface IAppFile extends IClrEntity {
|
|
13
|
+
appFileID?: number;
|
|
14
|
+
folder?: string;
|
|
15
|
+
fileName?: string;
|
|
16
|
+
fileSize?: number;
|
|
17
|
+
contentType?: string;
|
|
18
|
+
fileContentID?: number;
|
|
19
|
+
fileContent?: IFileContent;
|
|
20
|
+
attachments?: ICollection<IEmailAttachment>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const AppFile: IModel<IAppFile> = new Model<IAppFile>("AppFile", { folder: "",fileName: "",fileSize: 0,contentType: "",fileContentID: 0 });
|
|
24
|
+
|
|
25
|
+
export interface IFileContent extends IClrEntity {
|
|
26
|
+
fileContentID?: number;
|
|
27
|
+
contentHash?: string;
|
|
28
|
+
cloudPath?: string | null;
|
|
29
|
+
contentType?: string;
|
|
30
|
+
contentSize?: number;
|
|
31
|
+
appFiles?: ICollection<IAppFile>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const FileContent: IModel<IFileContent> = new Model<IFileContent>("FileContent", { contentHash: "",contentType: "",contentSize: 0 });
|
|
35
|
+
|
|
36
|
+
export interface IConfiguration extends IClrEntity {
|
|
37
|
+
name?: string;
|
|
38
|
+
configValue?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Configuration: IModel<IConfiguration> = new Model<IConfiguration>("Configuration", { name: "",configValue: "" });
|
|
42
|
+
|
|
43
|
+
export interface IDomain extends IClrEntity {
|
|
44
|
+
domainID?: number;
|
|
45
|
+
name?: string;
|
|
46
|
+
hostedDomain?: IHostedDomain;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const Domain: IModel<IDomain> = new Model<IDomain>("Domain", { name: "" });
|
|
50
|
+
|
|
51
|
+
export interface IHostedDomain extends IClrEntity {
|
|
52
|
+
domainID?: number;
|
|
53
|
+
enableReceive?: boolean;
|
|
54
|
+
enableSend?: boolean;
|
|
55
|
+
useRelay?: boolean;
|
|
56
|
+
aliasForName?: string | null;
|
|
57
|
+
domain?: IDomain;
|
|
58
|
+
aliasFor?: IHostedDomain;
|
|
59
|
+
aliases?: ICollection<IHostedDomain>;
|
|
60
|
+
mailboxes?: ICollection<IMailbox>;
|
|
61
|
+
domainUsers?: ICollection<IDomainUser>;
|
|
62
|
+
domainKeys?: ICollection<IDomainKey>;
|
|
63
|
+
relays?: ICollection<IDomainRelay>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const HostedDomain: IModel<IHostedDomain> = new Model<IHostedDomain>("HostedDomain", { domainID: 0,enableReceive: false,enableSend: false,useRelay: false });
|
|
67
|
+
|
|
68
|
+
export interface IDomainUser extends IClrEntity {
|
|
69
|
+
domainUserID?: number;
|
|
70
|
+
domainID?: number;
|
|
71
|
+
userID?: number;
|
|
72
|
+
prefix?: string | null;
|
|
73
|
+
hostedDomain?: IHostedDomain;
|
|
74
|
+
user?: IUser;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const DomainUser: IModel<IDomainUser> = new Model<IDomainUser>("DomainUser", { domainID: 0,userID: 0 });
|
|
78
|
+
|
|
79
|
+
export interface IDomainRelay extends IClrEntity {
|
|
80
|
+
domainRelayID?: number;
|
|
81
|
+
domainID?: number;
|
|
82
|
+
type?: "smtp" | "ses";
|
|
83
|
+
host?: string;
|
|
84
|
+
username?: string;
|
|
85
|
+
password?: string;
|
|
86
|
+
priority?: number;
|
|
87
|
+
hostedDomain?: IHostedDomain;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const DomainRelay: IModel<IDomainRelay> = new Model<IDomainRelay>("DomainRelay", { domainID: 0,type: "smtp",host: "",username: "",password: "",priority: 0 });
|
|
91
|
+
|
|
92
|
+
export interface IDomainKey extends IClrEntity {
|
|
93
|
+
domainKeyID?: number;
|
|
94
|
+
name?: string;
|
|
95
|
+
publicKey?: string;
|
|
96
|
+
privateKey?: string;
|
|
97
|
+
domainID?: number;
|
|
98
|
+
domain?: IHostedDomain;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const DomainKey: IModel<IDomainKey> = new Model<IDomainKey>("DomainKey", { name: "",publicKey: "",privateKey: "",domainID: 0 });
|
|
102
|
+
|
|
103
|
+
export interface IEmail extends IClrEntity {
|
|
104
|
+
emailID?: number;
|
|
105
|
+
mailBoxID?: number;
|
|
106
|
+
subject?: string;
|
|
107
|
+
textBody?: string;
|
|
108
|
+
htmlBody?: string;
|
|
109
|
+
senderName?: string;
|
|
110
|
+
senderID?: number;
|
|
111
|
+
headerFrom?: string;
|
|
112
|
+
headerTo?: string;
|
|
113
|
+
headerCc?: string;
|
|
114
|
+
headerBcc?: string;
|
|
115
|
+
mdBody?: string;
|
|
116
|
+
dateReceived?: DateTime;
|
|
117
|
+
dateCreated?: DateTime;
|
|
118
|
+
dateSent?: DateTime | null;
|
|
119
|
+
status?: "queued" | "received" | "draft" | "scheduled" | "sent";
|
|
120
|
+
mailbox?: IMailbox;
|
|
121
|
+
senderEmail?: IEmailAddress;
|
|
122
|
+
tags?: ICollection<IEmailTag>;
|
|
123
|
+
recipients?: ICollection<IEmailRecipient>;
|
|
124
|
+
attachments?: ICollection<IEmailAttachment>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const Email: IModel<IEmail> = new Model<IEmail>("Email", { mailBoxID: 0,subject: "",textBody: "",htmlBody: "",senderName: "",senderID: 0,headerFrom: "",headerTo: "",headerCc: "",headerBcc: "",mdBody: "",dateReceived: new DateTime(),dateCreated: new DateTime(),status: "queued" });
|
|
128
|
+
|
|
129
|
+
export interface IEmailRecipient extends IClrEntity {
|
|
130
|
+
emailRecipientID?: number;
|
|
131
|
+
emailID?: number;
|
|
132
|
+
email?: IEmail;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const EmailRecipient: IModel<IEmailRecipient> = new Model<IEmailRecipient>("EmailRecipient", { emailID: 0 });
|
|
136
|
+
|
|
137
|
+
export interface IEmailTag extends IClrEntity {
|
|
138
|
+
emailTagID?: number;
|
|
139
|
+
emailID?: number;
|
|
140
|
+
tagID?: number;
|
|
141
|
+
email?: IEmail;
|
|
142
|
+
tag?: ITag;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const EmailTag: IModel<IEmailTag> = new Model<IEmailTag>("EmailTag", { emailID: 0,tagID: 0 });
|
|
146
|
+
|
|
147
|
+
export interface IEmailAddress extends IClrEntity {
|
|
148
|
+
emailAddressID?: number;
|
|
149
|
+
emailAddress?: string;
|
|
150
|
+
mailbox?: IMailbox;
|
|
151
|
+
senders?: ICollection<IEmail>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const EmailAddress: IModel<IEmailAddress> = new Model<IEmailAddress>("EmailAddress", { emailAddress: "" });
|
|
155
|
+
|
|
156
|
+
export interface IEmailAttachment extends IClrEntity {
|
|
157
|
+
emailAttachmentID?: number;
|
|
158
|
+
emailID?: number;
|
|
159
|
+
appFileID?: number;
|
|
160
|
+
fileName?: string;
|
|
161
|
+
contentType?: string;
|
|
162
|
+
contentDisposition?: string;
|
|
163
|
+
contentID?: string | null;
|
|
164
|
+
email?: IEmail;
|
|
165
|
+
appFile?: IAppFile;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const EmailAttachment: IModel<IEmailAttachment> = new Model<IEmailAttachment>("EmailAttachment", { emailID: 0,appFileID: 0,fileName: "",contentType: "",contentDisposition: "" });
|
|
169
|
+
|
|
170
|
+
export interface IMailbox extends IClrEntity {
|
|
171
|
+
mailboxID?: number;
|
|
172
|
+
domainID?: number;
|
|
173
|
+
storage?: number;
|
|
174
|
+
maxStorage?: number;
|
|
175
|
+
emailAddress?: IEmailAddress;
|
|
176
|
+
domain?: IHostedDomain;
|
|
177
|
+
emails?: ICollection<IEmail>;
|
|
178
|
+
userMailboxes?: ICollection<IUserMailbox>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const Mailbox: IModel<IMailbox> = new Model<IMailbox>("Mailbox", { mailboxID: 0,domainID: 0,storage: 0,maxStorage: 0 });
|
|
182
|
+
|
|
183
|
+
export interface IUser extends IClrEntity {
|
|
184
|
+
userID?: number;
|
|
185
|
+
userName?: string;
|
|
186
|
+
displayName?: string;
|
|
187
|
+
passwordHash?: string;
|
|
188
|
+
passwordSalt?: string;
|
|
189
|
+
roles?: ICollection<IUserRole>;
|
|
190
|
+
sessions?: ICollection<ILoginSession>;
|
|
191
|
+
domainUsers?: ICollection<IDomainUser>;
|
|
192
|
+
userMailboxes?: ICollection<IUserMailbox>;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const User: IModel<IUser> = new Model<IUser>("User", { userName: "",displayName: "",passwordHash: "",passwordSalt: "" });
|
|
196
|
+
|
|
197
|
+
export interface IUserRole extends IClrEntity {
|
|
198
|
+
id?: number;
|
|
199
|
+
userID?: number;
|
|
200
|
+
roleName?: string;
|
|
201
|
+
user?: IUser;
|
|
202
|
+
role?: IAppRole;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export const UserRole: IModel<IUserRole> = new Model<IUserRole>("UserRole", { userID: 0,roleName: "" });
|
|
206
|
+
|
|
207
|
+
export interface IUserMailbox extends IClrEntity {
|
|
208
|
+
userMailboxID?: number;
|
|
209
|
+
emailAddress?: string;
|
|
210
|
+
userID?: number;
|
|
211
|
+
mailbox?: IMailbox;
|
|
212
|
+
user?: IUser;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const UserMailbox: IModel<IUserMailbox> = new Model<IUserMailbox>("UserMailbox", { emailAddress: "",userID: 0 });
|
|
216
|
+
|
|
217
|
+
export interface ILoginSession extends IClrEntity {
|
|
218
|
+
sessionID?: number;
|
|
219
|
+
userID?: number;
|
|
220
|
+
start?: Date;
|
|
221
|
+
expiry?: Date;
|
|
222
|
+
invalid?: boolean;
|
|
223
|
+
user?: IUser;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export const LoginSession: IModel<ILoginSession> = new Model<ILoginSession>("LoginSession", { userID: 0,start: new Date(),expiry: new Date(),invalid: false });
|
|
227
|
+
|
|
228
|
+
export interface ITag extends IClrEntity {
|
|
229
|
+
tagID?: number;
|
|
230
|
+
name?: string;
|
|
231
|
+
emailTags?: ICollection<IEmailTag>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const Tag: IModel<ITag> = new Model<ITag>("Tag", { name: "" });
|
|
@@ -20,13 +20,13 @@ const css = styled.css `
|
|
|
20
20
|
width: 100%;
|
|
21
21
|
height: 100%;
|
|
22
22
|
overflow: hidden;
|
|
23
|
-
background-color:
|
|
23
|
+
background-color: var(--accent2-color);
|
|
24
24
|
|
|
25
25
|
& > .progress-bar-value {
|
|
26
26
|
display: none;
|
|
27
27
|
width: 100%;
|
|
28
28
|
height: 100%;
|
|
29
|
-
background-color:
|
|
29
|
+
background-color: var(--accent-color);
|
|
30
30
|
transform-origin: 0% 50%;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import DITransient from "@web-atoms/core/dist/di/DITransient";
|
|
2
|
+
import { Inject } from "@web-atoms/core/dist/di/Inject";
|
|
3
|
+
import EntityService from "./EntityService";
|
|
4
|
+
import { Configuration } from "../model/model";
|
|
5
|
+
|
|
6
|
+
@DITransient()
|
|
7
|
+
export default class ConfigService {
|
|
8
|
+
|
|
9
|
+
@Inject
|
|
10
|
+
private entityService: EntityService;
|
|
11
|
+
|
|
12
|
+
async isConfigured() {
|
|
13
|
+
const configured = "configured";
|
|
14
|
+
const isConfigured = await this.entityService.query(Configuration)
|
|
15
|
+
.where({ configured }, (p) => (x) => x.name === p.configured)
|
|
16
|
+
.firstOrDefault();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|