Gmail plugin (#752)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
Co-authored-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Denis Bykhov 2021-12-30 15:13:16 +06:00 committed by GitHub
parent b0908ec251
commit 2a98f3c176
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1679 additions and 80 deletions

View File

@ -31,6 +31,9 @@ specifiers:
'@rush-temp/elastic': file:./projects/elastic.tgz
'@rush-temp/front': file:./projects/front.tgz
'@rush-temp/generator': file:./projects/generator.tgz
'@rush-temp/gmail': file:./projects/gmail.tgz
'@rush-temp/gmail-assets': file:./projects/gmail-assets.tgz
'@rush-temp/gmail-resources': file:./projects/gmail-resources.tgz
'@rush-temp/lead': file:./projects/lead.tgz
'@rush-temp/lead-assets': file:./projects/lead-assets.tgz
'@rush-temp/lead-resources': file:./projects/lead-resources.tgz
@ -45,6 +48,7 @@ specifiers:
'@rush-temp/model-contact': file:./projects/model-contact.tgz
'@rush-temp/model-core': file:./projects/model-core.tgz
'@rush-temp/model-demo': file:./projects/model-demo.tgz
'@rush-temp/model-gmail': file:./projects/model-gmail.tgz
'@rush-temp/model-lead': file:./projects/model-lead.tgz
'@rush-temp/model-recruit': file:./projects/model-recruit.tgz
'@rush-temp/model-rig': file:./projects/model-rig.tgz
@ -204,6 +208,9 @@ dependencies:
'@rush-temp/elastic': file:projects/elastic.tgz
'@rush-temp/front': file:projects/front.tgz
'@rush-temp/generator': file:projects/generator.tgz
'@rush-temp/gmail': file:projects/gmail.tgz
'@rush-temp/gmail-assets': file:projects/gmail-assets.tgz
'@rush-temp/gmail-resources': file:projects/gmail-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/lead': file:projects/lead.tgz
'@rush-temp/lead-assets': file:projects/lead-assets.tgz
'@rush-temp/lead-resources': file:projects/lead-resources.tgz_096c09b0b673a57c275d9767a12070b1
@ -218,6 +225,7 @@ dependencies:
'@rush-temp/model-contact': file:projects/model-contact.tgz_typescript@4.5.4
'@rush-temp/model-core': file:projects/model-core.tgz_typescript@4.5.4
'@rush-temp/model-demo': file:projects/model-demo.tgz_typescript@4.5.4
'@rush-temp/model-gmail': file:projects/model-gmail.tgz_typescript@4.5.4
'@rush-temp/model-lead': file:projects/model-lead.tgz_typescript@4.5.4
'@rush-temp/model-recruit': file:projects/model-recruit.tgz_typescript@4.5.4
'@rush-temp/model-rig': file:projects/model-rig.tgz_37f79b97d0d86442e45d380c86f520c5
@ -11273,6 +11281,82 @@ packages:
- utf-8-validate
dev: false
file:projects/gmail-assets.tgz:
resolution: {integrity: sha512-yvZyg36xh0IBxOyVTbHZUZ7BiJLCsBaiC/Djvrc+/t5x068o3Na4ncoQAVRuVGF4z1RGRxD8Ystf2lwsevvArg==, tarball: file:projects/gmail-assets.tgz}
name: '@rush-temp/gmail-assets'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@types/node': 16.11.14
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
typescript: 4.5.4
transitivePeerDependencies:
- supports-color
dev: false
file:projects/gmail-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-zZWhkD/HZH3/ljH7WhD2IG9NhQ6b1JPTgG2Rc35pGwScC2uzyI8xT3cq4K/jbsYLZ3BXj31BYOno7Mqo6pFcZg==, tarball: file:projects/gmail-resources.tgz}
id: file:projects/gmail-resources.tgz
name: '@rush-temp/gmail-resources'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
eslint-plugin-svelte3: 3.2.1_eslint@7.32.0+svelte@3.44.3
prettier: 2.5.1
prettier-plugin-svelte: 2.5.1_prettier@2.5.1+svelte@3.44.3
sass: 1.45.0
svelte: 3.44.3
svelte-check: 2.2.11_4374c622c67ed7479ff0e44c29d09bce
svelte-loader: 3.1.2_svelte@3.44.3
svelte-preprocess: 4.10.1_14d64cad431e31f100de7363af24a44f
typescript: 4.5.4
transitivePeerDependencies:
- '@babel/core'
- coffeescript
- less
- node-sass
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
dev: false
file:projects/gmail.tgz:
resolution: {integrity: sha512-56FU7r/ypcn+MoC5NDXlSBgUcpB6Qr533YRbaAysYKoojaRu2/AkTIrldSXuS5cJCWjKqDBG2t251e+rsHm/fQ==, tarball: file:projects/gmail.tgz}
name: '@rush-temp/gmail'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
typescript: 4.5.4
transitivePeerDependencies:
- supports-color
dev: false
file:projects/lead-assets.tgz:
resolution: {integrity: sha512-cRYB8PutP6HmaJjoEMLIEyMQEhKAQaCu0w2NJMF5TUW9vokia/22TXsHo1+xEGI1rx2epywbGXet/fL40tdbDw==, tarball: file:projects/lead-assets.tgz}
name: '@rush-temp/lead-assets'
@ -11417,7 +11501,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.5.4:
resolution: {integrity: sha512-iCugm1e/KTBXZ0FNnog/eP07l49sFIDU2orN0vAMUJ5pgZC7T38LSjnOL2y80OmoTbQK/3G2eH7Or+bxOYk2Cg==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-oKPQz2zoE53ROTtb76Vp43+PatfI+CKrdq0sUleUVaKPfWw74BKTXmqSAkZU1PVnr/DaKY/Gnl9Eo9JrXOUiig==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@ -11546,6 +11630,27 @@ packages:
- typescript
dev: false
file:projects/model-gmail.tgz_typescript@4.5.4:
resolution: {integrity: sha512-c7TM2OxH/Xp/6Il6K2obdrOpl0CVmj7z1FTu94EVLYY6ZNICX184K4kegYm39FrigozEFQLORjaZCV9vRnbx1g==, tarball: file:projects/model-gmail.tgz}
id: file:projects/model-gmail.tgz
name: '@rush-temp/model-gmail'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
transitivePeerDependencies:
- supports-color
- typescript
dev: false
file:projects/model-lead.tgz_typescript@4.5.4:
resolution: {integrity: sha512-3+Wf+/TdMpbuSoiTaI/ga+1KHSsPd1q9MDtpG6i4oAhxt/48aTZnfOV4nt9hrpWy2MmQWVhK9YJFTaKGIgKwOA==, tarball: file:projects/model-lead.tgz}
id: file:projects/model-lead.tgz
@ -11930,7 +12035,7 @@ packages:
dev: false
file:projects/presentation.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-KbCYAGG7rz+Ov6f0yICsx0yzzufqd0RcVJ6XVLzYxLTurJiTeYNYSthU045vLXkRURpz4gEICQ0RHv9dvckMMA==, tarball: file:projects/presentation.tgz}
resolution: {integrity: sha512-tpa5gk8H/quPYXkpBhp5jS0bp/E+Jk7mTGhfE54q4RTr5LOYGTCg2HKmxZWqRV1N3g1Jk7nKAZHWMxMSGHCuKw==, tarball: file:projects/presentation.tgz}
id: file:projects/presentation.tgz
name: '@rush-temp/presentation'
version: 0.0.0
@ -11966,7 +12071,7 @@ packages:
dev: false
file:projects/prod.tgz_sass@1.45.0+typescript@4.5.4:
resolution: {integrity: sha512-cWieTfrEsukKvJvSGr/ZOX6Vo4aZQHGDpOrE2smLt21Rk+/lSNpUcpAKKGRoFmW08sWkQoYrU6BdPMgw4+/UNQ==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-XgKxpfDD6oNTIijCj64CrOXM6sYgZhDuDoy5wWIrr/4Y5QFn7TWpOnSjIgmIXMtLUCGk4L55CAZBK+okRGX25Q==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@ -12612,7 +12717,7 @@ packages:
dev: false
file:projects/view-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-Ec9uQXBdFop5UPSMzA6UQaryHWmVTWVsDnWOzCH7BYtq7Zvw+763oJMFChaDsoGgeE2znn5zgam4sUn/1alqxQ==, tarball: file:projects/view-resources.tgz}
resolution: {integrity: sha512-oT79PCOUvwaL6QgjuwTNVJJqyE82WDmBx37jF5/c6SpSrYNF14+hN41rrPReKQyhhxkyrBts73IePyHxIn653Q==, tarball: file:projects/view-resources.tgz}
id: file:projects/view-resources.tgz
name: '@rush-temp/view-resources'
version: 0.0.0

View File

@ -85,6 +85,9 @@
"@anticrm/attachment-resources": "~0.6.0",
"@anticrm/lead": "~0.6.0",
"@anticrm/lead-assets": "~0.6.0",
"@anticrm/lead-resources": "~0.6.0"
"@anticrm/lead-resources": "~0.6.0",
"@anticrm/gmail": "~0.6.0",
"@anticrm/gmail-assets": "~0.6.0",
"@anticrm/gmail-resources": "~0.6.0"
}
}

View File

@ -28,6 +28,7 @@ import { telegramId } from '@anticrm/telegram'
import { attachmentId } from '@anticrm/attachment'
import { leadId } from '@anticrm/lead'
import { clientId } from '@anticrm/client'
import { gmailId } from '@anticrm/gmail'
import '@anticrm/login-assets'
import '@anticrm/task-assets'
@ -40,6 +41,7 @@ import '@anticrm/activity-assets'
import '@anticrm/setting-assets'
import '@anticrm/telegram-assets'
import '@anticrm/lead-assets'
import '@anticrm/gmail-assets'
import '@anticrm/workbench-assets'
import { setMetadata } from '@anticrm/platform'
@ -52,6 +54,7 @@ export function configurePlatform() {
})
})
setMetadata(login.metadata.TelegramUrl, process.env.TELEGRAM_URL ?? 'http://localhost:8086')
setMetadata(login.metadata.GmailUrl, process.env.GMAIL_URL ?? 'http://localhost:8087')
setMetadata(login.metadata.OverrideEndpoint, process.env.LOGIN_ENDPOINT)
addLocation(clientId, () => import(/* webpackChunkName: "client" */ '@anticrm/client-resources'))
@ -67,4 +70,5 @@ export function configurePlatform() {
addLocation(leadId, () => import(/* webpackChunkName: "lead" */ '@anticrm/lead-resources'))
addLocation(telegramId, () => import(/* webpackChunkName: "telegram" */ '@anticrm/telegram-resources'))
addLocation(attachmentId, () => import(/* webpackChunkName: "attachment" */ '@anticrm/attachment-resources'))
addLocation(gmailId, () => import(/* webpackChunkName: "gmail" */ '@anticrm/gmail-resources'))
}

View File

@ -45,6 +45,7 @@
"@anticrm/model-server-recruit": "~0.6.0",
"@anticrm/model-activity": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/model-gmail": "~0.6.0",
"@anticrm/core": "~0.6.13"
}
}

View File

@ -26,6 +26,7 @@ import { createModel as settingModel } from '@anticrm/model-setting'
import { createModel as telegramModel } from '@anticrm/model-telegram'
import { createModel as attachmentModel } from '@anticrm/model-attachment'
import { createModel as leadModel } from '@anticrm/model-lead'
import { createModel as gmailModel } from '@anticrm/model-gmail'
import { createModel as serverCoreModel } from '@anticrm/model-server-core'
import { createModel as serverChunterModel } from '@anticrm/model-server-chunter'
@ -48,6 +49,7 @@ recruitModel(builder)
settingModel(builder)
telegramModel(builder)
leadModel(builder)
gmailModel(builder)
serverCoreModel(builder)
serverChunterModel(builder)

View File

@ -190,17 +190,6 @@ export function createModel (builder: Builder): void {
presenter: contact.component.ChannelsPresenter
})
builder.createDoc(
contact.class.ChannelProvider,
core.space.Model,
{
label: 'Email' as IntlString,
icon: contact.icon.Email,
placeholder: 'john.appleseed@apple.com'
},
contact.channelProvider.Email
)
builder.createDoc(
contact.class.ChannelProvider,
core.space.Model,

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

4
models/gmail/.npmignore Normal file
View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

39
models/gmail/package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "@anticrm/model-gmail",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1"
},
"dependencies": {
"@anticrm/activity": "~0.6.0",
"@anticrm/model": "~0.6.0",
"@anticrm/core": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-contact": "~0.6.1",
"@anticrm/gmail": "~0.6.0",
"@anticrm/gmail-resources": "~0.6.0",
"@anticrm/setting": "~0.6.0",
"@anticrm/ui": "~0.6.0"
}
}

124
models/gmail/src/index.ts Normal file
View File

@ -0,0 +1,124 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { IntlString } from '@anticrm/platform'
import { Builder, Model, TypeString, Prop, ArrOf } from '@anticrm/model'
import core, { TAttachedDoc, TDoc } from '@anticrm/model-core'
import contact from '@anticrm/model-contact'
import gmail from './plugin'
import type { Message, SharedMessage, SharedMessages } from '@anticrm/gmail'
import type { Domain, Type } from '@anticrm/core'
import setting from '@anticrm/setting'
import activity from '@anticrm/activity'
export const DOMAIN_GMAIL = 'gmail' as Domain
function TypeSharedMessage (): Type<SharedMessage> {
return { _class: gmail.class.SharedMessage, label: 'Shared message' as IntlString }
}
@Model(gmail.class.Message, core.class.Doc, DOMAIN_GMAIL)
export class TMessage extends TDoc implements Message {
@Prop(TypeString(), 'MessageID' as IntlString)
messageId!: string
@Prop(TypeString(), 'ReplyTo' as IntlString)
replyTo?: string
@Prop(TypeString(), 'From' as IntlString)
from!: string
@Prop(TypeString(), 'To' as IntlString)
to!: string
@Prop(TypeString(), 'Contact' as IntlString)
contact!: string
@Prop(TypeString(), 'Subject' as IntlString)
subject!: string
@Prop(TypeString(), 'Message' as IntlString)
content!: string
@Prop(TypeString(), 'Message' as IntlString)
textContent!: string
@Prop(ArrOf(TypeString()), 'Copy' as IntlString)
copy?: string[]
}
@Model(gmail.class.SharedMessages, core.class.AttachedDoc, DOMAIN_GMAIL)
export class TSharedMessages extends TAttachedDoc implements SharedMessages {
@Prop(ArrOf(TypeSharedMessage()), 'Messages' as IntlString)
messages!: SharedMessage[]
}
export function createModel (builder: Builder): void {
builder.createModel(TMessage, TSharedMessages)
builder.createDoc(
contact.class.ChannelProvider,
core.space.Model,
{
label: 'Gmail' as IntlString,
icon: contact.icon.Email,
placeholder: 'john.appleseed@apple.com',
presenter: gmail.component.Main,
integrationType: gmail.integrationType.Gmail
},
contact.channelProvider.Email
)
builder.createDoc(
setting.class.IntegrationType,
core.space.Model,
{
label: 'Gmail',
description: 'Use gmail integration' as IntlString,
icon: gmail.component.IconGmail,
createComponent: gmail.component.Connect,
onDisconnect: gmail.handler.DisconnectHandler
},
gmail.integrationType.Gmail
)
builder.createDoc(
core.class.Space,
core.space.Model,
{
name: 'Gmail',
description: 'Space for all gmail messages',
private: false,
archived: false,
members: []
},
gmail.space.Gmail
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: gmail.class.SharedMessages,
icon: contact.icon.Telegram,
txClass: core.class.TxCreateDoc,
component: gmail.activity.TxSharedCreate,
label: gmail.string.SharedMessages,
display: 'content'
},
gmail.ids.TxSharedCreate
)
}

View File

@ -0,0 +1,34 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Ref } from '@anticrm/core'
import { IntlString, mergeIds } from '@anticrm/platform'
import { gmailId } from '@anticrm/gmail'
import gmail from '@anticrm/gmail-resources/src/plugin'
import type { AnyComponent } from '@anticrm/ui'
import type { TxViewlet } from '@anticrm/activity'
export default mergeIds(gmailId, gmail, {
string: {
SharedMessages: '' as IntlString
},
ids: {
TxSharedCreate: '' as Ref<TxViewlet>
},
activity: {
TxSharedCreate: '' as AnyComponent
}
})

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -90,12 +90,4 @@ export function createModel (builder: Builder): void {
spaces: []
}
}, setting.ids.SettingApp)
builder.createDoc(setting.class.IntegrationType, core.space.Model, {
label: 'Email',
description: 'Use email integration' as IntlString,
icon: setting.component.IconGmail,
createComponent: setting.component.ConnectEmail,
onDisconnect: setting.handler.EmailDisconnectHandler
})
}

View File

@ -98,6 +98,7 @@
.header {
flex-shrink: 0;
padding: 0 2rem 0 2.5rem;
min-height: 0;
height: 4rem;
color: var(--theme-content-accent-color);
border-bottom: 1px solid var(--theme-zone-bg);
@ -107,6 +108,7 @@
flex-shrink: 0;
padding: 0 2rem;
height: 3.5rem;
min-height: 0;
border-bottom: 1px solid var(--theme-zone-bg);
}
}
@ -128,6 +130,8 @@
.leftSection, .rightSection {
flex-basis: 50%;
width: 50%;
min-height: 0;
display: flex;
flex-direction: column;
}

View File

@ -14,9 +14,8 @@
-->
<script lang="ts">
import { Button, CircleButton, IconClose } from '@anticrm/ui'
import { Button, CircleButton, IconClose, IconArrowLeft } from '@anticrm/ui'
import Avatar from './Avatar.svelte'
import ArrowLeft from './icons/ArrowLeft.svelte'
import ExpandUp from './icons/ExpandUp.svelte'
import ExpandDown from './icons/ExpandDown.svelte'
@ -35,7 +34,7 @@
<div class="flex-between header">
<div class="flex-center arrow-back">
<div class="icon"><ArrowLeft size={'small'} /></div>
<div class="icon"><IconArrowLeft size={'small'} /></div>
</div>
<div class="flex-row-center flex-grow">
<Avatar size={'medium'} />

View File

@ -250,6 +250,10 @@ p:last-child { margin-block-end: 0; }
.w-full { width: 100%; }
.min-w-0 { min-width: 0; }
.min-h-0 { min-height: 0; }
.clear-mins {
min-width: 0;
min-height: 0;
}
.square-36 { width: 2.25rem; height: 2.25rem; }
/* --------- */
@ -386,10 +390,11 @@ a.no-line {
.background-highlight-red { background-color: var(--highlight-red); }
.background-button-bg-enabled { background-color: var(--theme-button-bg-enabled); }
.background-menu-divider { background-color: var(--theme-menu-divider); }
.background-primary-button-enabled { background-color: var(--primary-button-enabled); }
.background-primary-color { background-color: var(--primary-button-enabled); }
.content-color { color: var(--theme-content-color); }
.content-trans-color { color: var(--theme-content-trans-color); }
.content-accent-color { color: var(--theme-content-accent-color); }
.content-dark-color { color: var(--theme-content-dark-color); }
.caption-color { color: var(--theme-caption-color); }

View File

@ -82,6 +82,7 @@ export { default as IconDelete } from './components/icons/Delete.svelte'
export { default as IconEdit } from './components/icons/Edit.svelte'
export { default as IconInfo } from './components/icons/Info.svelte'
export { default as IconBlueCheck } from './components/icons/BlueCheck.svelte'
export { default as IconArrowLeft } from '../../presentation/src/components/icons/ArrowLeft.svelte'
export { default as Menu } from './components/Menu.svelte'
export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'

View File

@ -0,0 +1,20 @@
{
"string": {
"SharedMessages": "shared emails",
"To": "To:",
"From": "From:",
"Copy": "Copy",
"MessagesSelected": "messages selected",
"PublishSelected": "Publish selected",
"YouAnd": "You and",
"CreateMessage": "Create message",
"ShareMessages": "Share messages",
"Connect": "Connect",
"RedirectGoogle": "You will be redirect to google auth page",
"ConnectGmai": "Connect Gmail account",
"Reply": "Reply",
"Subject": "Subject",
"Send": "Send",
"NewMessageTo": "New message to"
}
}

View File

@ -0,0 +1,33 @@
{
"name": "@anticrm/gmail-assets",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write src && eslint --fix src",
"build:watch": "tsc"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"@types/heft-jest": "^1.0.2",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5",
"@types/node": "^16.4.10"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"@anticrm/gmail": "~0.6.0"
}
}

View File

@ -0,0 +1,20 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { addStringsLoader } from '@anticrm/platform'
import { gmailId } from '@anticrm/gmail'
addStringsLoader(gmailId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname },
settings: {
'svelte3/ignore-styles': () => true
}
}

View File

@ -0,0 +1,46 @@
{
"name": "@anticrm/gmail-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "echo 'no build for ui'",
"build:docs": "api-extractor run --local",
"lint": "svelte-check && eslint",
"lint:fix": "eslint --fix src",
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"svelte-loader": "^3.1.2",
"sass": "^1.37.5",
"svelte-preprocess": "^4.7.4",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-svelte3": "~3.2.1",
"prettier-plugin-svelte": "^2.2.0",
"prettier": "^2.4.1",
"svelte-check": "^2.2.10",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"svelte": "^3.37.0",
"@anticrm/gmail": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/text-editor": "~0.6.0",
"@anticrm/contact": "~0.6.0",
"@anticrm/setting": "~0.6.0",
"@anticrm/chunter": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0",
"@anticrm/login": "~0.6.1",
"@anticrm/core": "~0.6.11"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -0,0 +1,191 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createQuery, getClient } from '@anticrm/presentation'
import { Message, SharedMessage } from '@anticrm/gmail'
import gmail from '../plugin'
import { Contact, EmployeeAccount, formatName } from '@anticrm/contact'
import contact from '@anticrm/contact'
import { ActionIcon, IconShare, Button, ScrollBox, showPopup, Icon, Label } from '@anticrm/ui'
import { Account, Class, getCurrentAccount, Ref, SortingOrder, Space } from '@anticrm/core'
import setting from '@anticrm/setting'
import Connect from './Connect.svelte'
import Messages from './Messages.svelte'
export let object: Contact
export let contactString: string
export let newMessage: boolean
let messages: Message[] = []
let account: EmployeeAccount | undefined
let enabled: boolean
let selected: Set<Ref<SharedMessage>> = new Set<Ref<SharedMessage>>()
let selectable = false
let me = ''
const messagesQuery = createQuery()
const accauntQuery = createQuery()
const settingsQuery = createQuery()
const accountId = getCurrentAccount()._id
$: messagesQuery.query(
gmail.class.Message,
{ modifiedBy: accountId, contact: { $like: '%' + contactString + '%' } },
(res) => {
// messages = res
},
{ sort: { modifiedOn: SortingOrder.Descending } }
)
$: accauntQuery.query(contact.class.EmployeeAccount, { _id: accountId as Ref<EmployeeAccount> }, (result) => {
account = result[0]
})
$: settingsQuery.query(
setting.class.Integration,
{ type: gmail.integrationType.Gmail, space: accountId as string as Ref<Space> },
(res) => {
// enabled = res.length > 0
// me = res[0].value
}
)
const client = getClient()
async function share (): Promise<void> {
const selectedMessages = messages.filter((m) => selected.has(m._id as string as Ref<SharedMessage>))
await client.addCollection(gmail.class.SharedMessages, object.space, object._id, object._class, 'gmailMessages', {
messages: convertMessages(selectedMessages)
})
clear()
}
function clear (): void {
selectable = false
selected.clear()
selected = selected
}
function convertMessages (messages: Message[]): SharedMessage[] {
return messages.map((m) => {
return {
...m,
_id: m._id as string as Ref<SharedMessage>,
sender: account ? getName(m, account, true) : '',
receiver: account ? getName(m, account, false) : '',
incoming: !amISender(m)
}
})
}
function getName (message: Message, account: EmployeeAccount, sender: boolean): string {
return amISender(message) !== sender
? `${formatName(object.name)} (${contactString})`
: `${formatName(account.name)} (${me})`
}
function amISender (message: Message): boolean {
return !message.from.includes(contactString)
}
</script>
<div class="flex-between header">
{#if selectable}
<div class="flex-between w-full">
<span>{selected.size} <Label label={gmail.string.MessagesSelected} /></span>
<div class="flex">
<div>
<Button label={'Cancel'} size={'small'} on:click={clear} />
</div>
<div class="ml-3">
<Button
label={gmail.string.PublishSelected}
size={'small'}
primary
disabled={!selected.size}
on:click={share}
/>
</div>
</div>
</div>
{:else if enabled}
<div class="flex-center icon"><div class="scale-75"><Icon icon={contact.icon.Email} size="small" /></div></div>
<div class="flex-grow flex-col">
<div class="fs-title">Gmail</div>
<div class="small-text content-dark-color"><Label label={gmail.string.YouAnd} /> {formatName(object.name)}</div>
</div>
<div class="mr-3">
<Button
label={gmail.string.CreateMessage}
size={'small'}
primary
on:click={() => {
newMessage = true
}}
/>
</div>
<ActionIcon
icon={IconShare}
size={'medium'}
label={gmail.string.ShareMessages}
direction={'bottom'}
action={async () => {
selectable = !selectable
}}
/>
{:else}
<div class="flex-center">
<Button
label={gmail.string.Connect}
primary
size={'small'}
on:click={(e) => {
showPopup(Connect, {}, e.target)
}}
/>
</div>
{/if}
</div>
<div class="h-full right-content">
<ScrollBox vertical stretch>
{#if messages}
<Messages messages={convertMessages(messages)} {selectable} bind:selected on:select />
{/if}
</ScrollBox>
</div>
<style lang="scss">
.header {
flex-shrink: 0;
padding: 0 6rem 0 2.5rem;
height: 4rem;
color: var(--theme-content-accent-color);
border-bottom: 1px solid var(--theme-zone-bg);
.icon {
margin-right: 1rem;
width: 2.25rem;
height: 2.25rem;
color: var(--theme-caption-color);
background-color: var(--primary-button-enabled);
border-radius: 50%;
}
}
.right-content {
flex-grow: 1;
padding: 1.5rem 1rem;
}
</style>

View File

@ -1,28 +1,52 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { IconClose, Label } from '@anticrm/ui'
import { getMetadata } from '@anticrm/platform'
import { Button, IconClose, Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import login from '@anticrm/login'
import gmail from '../plugin'
const dispatch = createEventDispatcher()
let connecting = false
const gmailUrl = getMetadata(login.metadata.GmailUrl) ?? ''
async function sendRequest (): Promise<void> {
connecting = true
const url = new URL(gmailUrl + '/signin')
url.search = new URLSearchParams({
redirectURL: window.location.href
}).toString()
const res = await fetch(url.toString(), {
method: 'GET',
headers: {
Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken),
'Content-Type': 'application/json'
}
})
const redirectTo = await res.text()
window.open(redirectTo, '_self')
}
</script>
<div class="card">
<div class="card-bg" />
<div class="flex-between header">
<div class="overflow-label fs-title"><Label label={'Connect Email account'} /></div>
<div class="overflow-label fs-title"><Label label={gmail.string.ConnectGmai} /></div>
<div
class="tool"
on:click={() => {
@ -32,7 +56,12 @@
<IconClose size={'small'} />
</div>
</div>
<div class="content">Not implemented yet</div>
<div class="content">
<Label label={gmail.string.RedirectGoogle} />
<div class="footer">
<Button label={gmail.string.Connect} primary disabled={connecting} on:click={sendRequest} />
</div>
</div>
</div>
<style lang="scss">
@ -43,7 +72,10 @@
width: 20rem;
min-width: 20rem;
max-width: 20rem;
border-radius: 1.25rem;
background-color: var(--theme-tooltip-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: .75rem;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2);
.header {
flex-shrink: 0;
@ -67,17 +99,12 @@
margin: 0 1.75rem 0.5rem;
}
.card-bg {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--theme-card-bg);
border-radius: 1.25rem;
backdrop-filter: blur(15px);
box-shadow: var(--theme-card-shadow);
z-index: -1;
.footer {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
}
}
</style>

View File

@ -0,0 +1,97 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { SharedMessage } from '@anticrm/gmail'
import Button from '@anticrm/ui/src/components/Button.svelte'
import { createEventDispatcher } from 'svelte'
import { IconArrowLeft, Label } from '@anticrm/ui'
import gmail from '../plugin'
import FullMessageContent from './FullMessageContent.svelte'
export let currentMessage: SharedMessage
export let newMessage: boolean
let editor: HTMLDivElement
$: if (editor) editor.innerHTML = currentMessage.content
const dispatch = createEventDispatcher()
$: title = currentMessage.incoming ? currentMessage.sender : currentMessage.receiver
$: user = currentMessage.incoming ? currentMessage.receiver : currentMessage.sender
</script>
<div class="flex-between clear-mins header">
<div
class="flex-center icon"
on:click={() => {
dispatch('close')
}}
>
<IconArrowLeft size="medium" />
</div>
<div class="flex-grow flex-col mr-4 min-w-0">
<div class="fs-title overflow-label">{currentMessage.subject}</div>
<div class="small-text content-dark-color overflow-label">
<Label label={currentMessage.incoming ? gmail.string.From : gmail.string.To} />
{title}
</div>
</div>
<div class="mr-3">
<Button
label={gmail.string.Reply}
size={'small'}
primary
on:click={() => {
newMessage = true
}}
/>
</div>
</div>
<div class="flex-col clear-mins right-content">
<Label label={currentMessage.incoming ? gmail.string.To : gmail.string.From} />
{user}
{#if currentMessage.copy?.length}
<Label label={gmail.string.Copy} />: {currentMessage.copy.join(', ')}
{/if}
<div class="flex-col clear-mins mt-9">
<FullMessageContent content={currentMessage.content} />
</div>
</div>
<style lang="scss">
.header {
flex-shrink: 0;
padding: 0 6rem 0 2.5rem;
height: 4rem;
color: var(--theme-content-accent-color);
border-bottom: 1px solid var(--theme-zone-bg);
.icon {
flex-shrink: 0;
margin-right: 1rem;
width: 2.25rem;
height: 2.25rem;
color: var(--theme-caption-color);
border-radius: 50%;
cursor: pointer;
}
}
.right-content {
flex-grow: 1;
padding: 1.5rem 2.5rem;
}
</style>

View File

@ -0,0 +1,52 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let content: string
let editor: HTMLDivElement
$: if (editor) editor.innerHTML = content
</script>
<div bind:this={editor} class="input clear-mins" />
<style lang="scss">
.input {
overflow: auto;
padding: 1rem;
background-color: #fff;
color: #1f212b;
border-radius: .5rem;
:global(a) {
font: inherit;
font-weight: 500;
text-decoration: initial;
color: initial;
outline: initial;
&:hover {
color: initial;
text-decoration: initial;
}
&:active {
color: initial;
text-decoration: initial;
}
&:visited {
color: initial;
}
}
}
</style>

View File

@ -0,0 +1,57 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { SharedMessage } from '@anticrm/gmail'
import { Label } from '@anticrm/ui'
import gmail from '../plugin'
import FullMessageContent from './FullMessageContent.svelte'
export let message: SharedMessage
$: title = message.incoming ? message.sender : message.receiver
$: user = message.incoming ? message.receiver : message.sender
</script>
<div class="popup flex-col">
<div class="fs-title mb-4">
{message.subject}
</div>
<div>
<Label label={message.incoming ? gmail.string.From : gmail.string.To} />
{title}
</div>
<div>
<Label label={message.incoming ? gmail.string.To : gmail.string.From} />
{user}
</div>
{#if message.copy?.length}
<Label label={gmail.string.Copy} />: {message.copy.join(', ')}
{/if}
<div class="flex-col clear-mins mt-5">
<FullMessageContent content={message.content} />
</div>
</div>
<style lang="scss">
.popup {
padding: 1rem;
max-height: 500px;
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-button-border-enabled);
border-radius: .75rem;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2);
}
</style>

View File

@ -0,0 +1,48 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import contact, { Contact } from '@anticrm/contact'
import { SharedMessage } from '@anticrm/gmail'
import NewMessage from './NewMessage.svelte'
import FullMessage from './FullMessage.svelte'
import Chats from './Chats.svelte'
export let object: Contact
let newMessage: boolean = false
let currentMessage: SharedMessage | undefined = undefined
$: contactString = object.channels.find((p) => p.provider === contact.channelProvider.Email)
function back () {
if (newMessage) {
return (newMessage = false)
}
return (currentMessage = undefined)
}
function selectHandler (e: CustomEvent) {
currentMessage = e.detail
}
</script>
{#if contactString}
{#if newMessage}
<NewMessage {object} contact={contactString.value} {currentMessage} on:close={back} />
{:else if currentMessage}
<FullMessage {currentMessage} bind:newMessage on:close={back} />
{:else}
<Chats {object} contactString={contactString.value} bind:newMessage on:select={selectHandler} />
{/if}
{/if}

View File

@ -0,0 +1,68 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { SharedMessage } from '@anticrm/gmail'
import { formatName } from '@anticrm/contact'
import { CheckBox } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import { getTime } from '../utils'
export let message: SharedMessage
export let selected: boolean = false
export let selectable: boolean = false
const dispatch = createEventDispatcher()
</script>
<div
class="flex-row-center clear-mins message-conatiner"
on:click|preventDefault={() => {
dispatch('select', message)
}}
>
{#if selectable}
<div class="mr-4"><CheckBox circle primary bind:checked={selected} /></div>
{/if}
<div class="flex-col message" class:selected>
<div class="flex-between small-text mb-4">
<div class="content-trans-color overflow-label mr-4">From: <span class="content-accent-color">{formatName(message.sender)}</span></div>
<div class="content-trans-color">{getTime(message.modifiedOn)}</div>
</div>
<div class="fs-title overflow-label mb-1">
{message.subject}
</div>
<div class="overflow-label">
{message.textContent}
</div>
</div>
</div>
<style lang="scss">
.message-conatiner {
margin: 0 1.5rem;
cursor: pointer;
}
.message {
padding: 1rem;
min-width: 0;
background-color: var(--theme-incoming-msg);
border-radius: .75rem;
white-space: nowrap;
&.selected { background-color: var(--primary-button-enabled); }
}
</style>

View File

@ -0,0 +1,56 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { SharedMessage } from '@anticrm/gmail'
import { Grid } from '@anticrm/ui'
import MessageView from './Message.svelte'
import { Ref } from '@anticrm/core'
import { createEventDispatcher } from 'svelte'
export let messages: SharedMessage[] = []
export let selectable: boolean = false
export let selected: Set<Ref<SharedMessage>> = new Set<Ref<SharedMessage>>()
const dispatch = createEventDispatcher()
function select (id: Ref<SharedMessage>): void {
if (!selectable) {
const currentMessage = messages.find((m) => m._id === id)
dispatch('select', currentMessage)
} else {
if (selected.has(id)) {
selected.delete(id)
} else {
selected.add(id)
}
selected = selected
}
}
</script>
<Grid column={1} rowGap={0.5}>
{#if messages}
{#each messages as message (message._id)}
<MessageView
{message}
{selectable}
selected={selected.has(message._id)}
on:select={() => {
select(message._id)
}}
/>
{/each}
{/if}
</Grid>

View File

@ -0,0 +1,151 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { getMetadata } from '@anticrm/platform'
import login from '@anticrm/login'
import { NewMessage, SharedMessage } from '@anticrm/gmail'
import EditBox from '@anticrm/ui/src/components/EditBox.svelte'
import Button from '@anticrm/ui/src/components/Button.svelte'
import { createEventDispatcher } from 'svelte'
import { IconArrowLeft, Label } from '@anticrm/ui'
import { Contact, formatName } from '@anticrm/contact'
import { TextEditor } from '@anticrm/text-editor'
import gmail from '../plugin'
export let object: Contact
export let contact: string
export let currentMessage: SharedMessage | undefined
let editor: TextEditor
let copy: string = ''
const obj: NewMessage = {
subject: currentMessage ? 'RE: ' + currentMessage.subject : '',
content: '',
to: contact,
replyTo: currentMessage?.messageId
}
const url = getMetadata(login.metadata.GmailUrl) ?? ''
async function sendMsg () {
fetch(url + '/send', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken),
'Content-Type': 'application/json'
},
body: JSON.stringify({
...obj,
copy: copy.split(',').map((m) => m.trim())
})
})
dispatch('close')
}
const dispatch = createEventDispatcher()
</script>
<div class="flex-between clear-mins header">
<div
class="flex-center icon"
on:click={() => {
dispatch('close')
}}
>
<IconArrowLeft size="medium" />
</div>
<div class="flex-grow flex-col">
<div class="fs-title">Gmail</div>
<div class="small-text content-dark-color overflow-label">
<Label label={gmail.string.NewMessageTo} />
<span class="content-accent-color">{formatName(object.name)} ({contact})</span>
</div>
</div>
<div class="mr-3">
<Button label={gmail.string.Send} size={'small'} primary on:click={sendMsg} />
</div>
</div>
<div class="flex-col clear-mins right-content">
<div class="mb-2">
<EditBox label={gmail.string.Subject} bind:value={obj.subject} placeholder={'Message subject'} />
</div>
<div class="mb-4">
<EditBox label={gmail.string.Copy} bind:value={copy} placeholder={'Copy to'} />
</div>
<div class="input clear-mins">
<TextEditor bind:this={editor} bind:content={obj.content} on:blur={editor.submit} />
</div>
</div>
<style lang="scss">
.header {
flex-shrink: 0;
padding: 0 6rem 0 2.5rem;
height: 4rem;
color: var(--theme-content-accent-color);
border-bottom: 1px solid var(--theme-zone-bg);
.icon {
flex-shrink: 0;
margin-right: 1rem;
width: 2.25rem;
height: 2.25rem;
color: var(--theme-caption-color);
border-radius: 50%;
cursor: pointer;
}
}
.right-content {
flex-grow: 1;
padding: 1.5rem 2.5rem;
.input {
overflow: auto;
padding: 1rem;
background-color: #fff;
color: #1f212b;
height: 100%;
border-radius: .5rem;
:global(.ProseMirror) {
min-height: 0;
max-height: 100%;
height: 100%;
}
:global(a) {
font: inherit;
font-weight: 500;
text-decoration: initial;
color: initial;
outline: initial;
&:hover {
color: initial;
text-decoration: initial;
}
&:active {
color: initial;
text-decoration: initial;
}
&:visited {
color: initial;
}
}
}
}
</style>

View File

@ -0,0 +1,43 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { SharedMessages } from '@anticrm/gmail'
import { showPopup } from '@anticrm/ui'
import FullMessagePopup from './FullMessagePopup.svelte'
import Messages from './Messages.svelte'
export let value: SharedMessages
function selectHandler (e: CustomEvent) {
const message = e.detail
showPopup(FullMessagePopup, {
message
})
}
</script>
<div class="container">
<Messages messages={value.messages} on:select={selectHandler} />
</div>
<style lang="scss">
.container {
padding: 1.25rem 0 1.5rem;
background-color: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0.75rem;
}
</style>

View File

@ -0,0 +1,25 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { TxCreateDoc } from '@anticrm/core'
import { TxProcessor } from '@anticrm/core'
import { SharedMessages } from '@anticrm/gmail'
import SharedMessagesView from '../SharedMessages.svelte'
export let tx: TxCreateDoc<SharedMessages>
</script>
<SharedMessagesView value={TxProcessor.createDoc2Doc(tx)} />

View File

@ -1,15 +1,15 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,45 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { getMetadata, Resources } from '@anticrm/platform'
import login from '@anticrm/login'
import Main from './components/Main.svelte'
import Connect from './components/Connect.svelte'
import IconGmail from './components/icons/GmailColor.svelte'
import TxSharedCreate from './components/activity/TxSharedCreate.svelte'
export default async (): Promise<Resources> => ({
component: {
Main,
Connect,
IconGmail
},
activity: {
TxSharedCreate
},
handler: {
DisconnectHandler: async () => {
const url = getMetadata(login.metadata.GmailUrl) ?? ''
await fetch(url + '/signout', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + (getMetadata(login.metadata.LoginToken) ?? ''),
'Content-Type': 'application/json'
}
})
}
}
})

View File

@ -0,0 +1,39 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { IntlString, mergeIds } from '@anticrm/platform'
import gmail, { gmailId } from '@anticrm/gmail'
export default mergeIds(gmailId, gmail, {
string: {
From: '' as IntlString,
To: '' as IntlString,
Copy: '' as IntlString,
MessagesSelected: '' as IntlString,
PublishSelected: '' as IntlString,
YouAnd: '' as IntlString,
CreateMessage: '' as IntlString,
ShareMessages: '' as IntlString,
Connect: '' as IntlString,
RedirectGoogle: '' as IntlString,
ConnectGmai: '' as IntlString,
Reply: '' as IntlString,
Subject: '' as IntlString,
Send: '' as IntlString,
NewMessageTo: '' as IntlString
}
})

View File

@ -0,0 +1,35 @@
export function getTime (time: number): string {
let options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: 'numeric' }
if (!isCurrentYear(time)) {
options = {
year: '2-digit',
month: 'numeric',
day: 'numeric',
...options
}
} else if (!isToday(time)) {
options = {
month: 'numeric',
day: 'numeric',
...options
}
}
return new Date(time).toLocaleString('default', options)
}
export function isToday (time: number): boolean {
const current = new Date()
const target = new Date(time)
return (
current.getDate() === target.getDate() &&
current.getMonth() === target.getMonth() &&
current.getFullYear() === target.getFullYear()
)
}
export function isCurrentYear (time: number): boolean {
const current = new Date()
const target = new Date(time)
return current.getFullYear() === target.getFullYear()
}

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

4
plugins/gmail/.npmignore Normal file
View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,35 @@
{
"name": "@anticrm/gmail",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"@anticrm/core": "~0.6.11",
"@anticrm/ui": "~0.6.0",
"@anticrm/contact": "~0.6.0",
"@anticrm/setting": "~0.6.0"
}
}

View File

@ -0,0 +1,89 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { plugin } from '@anticrm/platform'
import type { Plugin } from '@anticrm/platform'
import type { Doc, Ref, Class, Space, AttachedDoc } from '@anticrm/core'
import type { AnyComponent } from '@anticrm/ui'
import type { IntegrationType, Handler } from '@anticrm/setting'
/**
* @public
*/
export interface Message extends NewMessage, Doc {
messageId: string
from: string
textContent: string
contact: string
}
/**
* @public
*/
export interface NewMessage {
replyTo?: string
to: string
subject: string
content: string
copy?: string[]
}
/**
* @public
*/
export interface SharedMessage extends Doc {
messageId: string
subject: string
content: string
sender: string
receiver: string
incoming: boolean
textContent: string
copy?: string[]
}
/**
* @public
*/
export interface SharedMessages extends AttachedDoc {
messages: SharedMessage[]
}
/**
* @public
*/
export const gmailId = 'gmail' as Plugin
export default plugin(gmailId, {
component: {
Main: '' as AnyComponent,
Connect: '' as AnyComponent,
IconGmail: '' as AnyComponent
},
integrationType: {
Gmail: '' as Ref<IntegrationType>
},
handler: {
DisconnectHandler: '' as Handler
},
class: {
Message: '' as Ref<Class<Message>>,
SharedMessages: '' as Ref<Class<SharedMessages>>,
SharedMessage: '' as Ref<Class<SharedMessage>>
},
space: {
Gmail: '' as Ref<Space>
}
})

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}

View File

@ -39,6 +39,7 @@ export default plugin(loginId, {
AccountsUrl: '' as Asset,
UploadUrl: '' as Asset,
TelegramUrl: '' as Asset,
GmailUrl: '' as Asset,
LoginToken: '' as Metadata<string>,
LoginEndpoint: '' as Metadata<string>,
LoginEmail: '' as Metadata<string>,

View File

@ -19,8 +19,6 @@ import ManageStatuses from './components/statuses/ManageStatuses.svelte'
import Support from './components/Support.svelte'
import Privacy from './components/Privacy.svelte'
import Terms from './components/Terms.svelte'
import ConnectEmail from './components/integrations/ConnectEmail.svelte'
import IconGmail from './components/icons/Gmail.svelte'
export default async () => ({
component: {
@ -29,11 +27,6 @@ export default async () => ({
Support,
Privacy,
Terms,
ConnectEmail,
IconGmail,
ManageStatuses
},
handler: {
EmailDisconnectHandler: async () => {}
}
})

View File

@ -61,12 +61,7 @@ export default plugin(settingId, {
ManageStatuses: '' as AnyComponent,
Support: '' as AnyComponent,
Privacy: '' as AnyComponent,
Terms: '' as AnyComponent,
ConnectEmail: '' as AnyComponent,
IconGmail: '' as AnyComponent
},
handler: {
EmailDisconnectHandler: '' as Handler
Terms: '' as AnyComponent
},
string: {
Setting: '' as IntlString,

View File

@ -213,7 +213,7 @@
padding: 0 6rem 0 2.5rem;
height: 4rem;
color: var(--theme-content-accent-color);
border-bottom: 1px solid var(--theme-card-divider);
border-bottom: 1px solid var(--theme-zone-bg);
.icon {
margin-right: 1rem;

View File

@ -119,7 +119,6 @@
</script>
<div class="card">
<div class="card-bg" />
<div class="flex-between header">
<div class="overflow-label fs-title"><Label label={'Connect Telegram account'} /></div>
<div
@ -159,7 +158,10 @@
width: 20rem;
min-width: 20rem;
max-width: 20rem;
border-radius: 1.25rem;
background-color: var(--theme-tooltip-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: .75rem;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2);
.header {
flex-shrink: 0;
@ -190,7 +192,7 @@
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
padding: 1rem 0.1rem;
padding: 1rem 0rem;
.link {
color: var(--theme-content-dark-color);
@ -203,18 +205,5 @@
}
}
}
.card-bg {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--theme-card-bg);
border-radius: 1.25rem;
backdrop-filter: blur(15px);
box-shadow: var(--theme-card-shadow);
z-index: -1;
}
}
</style>

View File

@ -891,5 +891,25 @@
"projectFolder": "dev/generator",
"shouldPublish": false
},
{
"packageName": "@anticrm/model-gmail",
"projectFolder": "models/gmail",
"shouldPublish": true
},
{
"packageName": "@anticrm/gmail",
"projectFolder": "plugins/gmail",
"shouldPublish": true
},
{
"packageName": "@anticrm/gmail-assets",
"projectFolder": "plugins/gmail-assets",
"shouldPublish": true
},
{
"packageName": "@anticrm/gmail-resources",
"projectFolder": "plugins/gmail-resources",
"shouldPublish": true
},
]
}