Gmail removal
This commit is contained in:
parent
fe7f5ff723
commit
a7281bd683
6 changed files with 0 additions and 324 deletions
|
@ -1,55 +0,0 @@
|
||||||
// License: LGPL-3.0-or-later
|
|
||||||
const modal = require('ff-core/modal')
|
|
||||||
const h = require('snabbdom/h')
|
|
||||||
const validated = require('ff-core/validated-form')
|
|
||||||
const button = require('ff-core/button')
|
|
||||||
const R = require('ramda')
|
|
||||||
|
|
||||||
module.exports = state =>
|
|
||||||
modal({
|
|
||||||
title: h('h4.modalTitle', 'Email')
|
|
||||||
, id$: state.modalID$
|
|
||||||
, thisID: 'composeGmailModal'
|
|
||||||
, body: body(state)
|
|
||||||
})
|
|
||||||
|
|
||||||
const fieldset = (name, placeholder, field) =>
|
|
||||||
h('fieldset.group.u-marginTop--10', [
|
|
||||||
h('label.u-inlineBlock.col-2.u-paddingTop--5', utils.capitalize(name) + ': ')
|
|
||||||
, h('div.col-right-10.u-margin--0', [
|
|
||||||
field(h('textarea.u-margin--0', {props: {rows: '1', name, placeholder}}))
|
|
||||||
])
|
|
||||||
])
|
|
||||||
|
|
||||||
const group = (labelText, spanContent) =>
|
|
||||||
h('div.group.u-marginBottom--10',[
|
|
||||||
h('label.u-inlineBlock.col-2', `${labelText}: `)
|
|
||||||
, h('span.col-right--10', [spanContent])
|
|
||||||
])
|
|
||||||
|
|
||||||
const body = state => {
|
|
||||||
var gmail = state.gmail
|
|
||||||
var to = state.supporter$().email
|
|
||||||
var from = gmail.from$()
|
|
||||||
var field = validated.field(gmail.composeForm)
|
|
||||||
return validated.form(gmail.composeForm, h('form', [
|
|
||||||
group('From'
|
|
||||||
, h('span', [
|
|
||||||
gmail.from$() + ''
|
|
||||||
, h('a.u-small.u-paddingLeft--10', {props: {href: '#'}, on: {click: gmail.newSignIn$}}
|
|
||||||
, '(Switch account)')
|
|
||||||
])
|
|
||||||
)
|
|
||||||
, group('To', to + '')
|
|
||||||
, h('input', {props: {type: 'hidden', name: 'from', value: gmail.from$()}})
|
|
||||||
, field(h('input', {props: {type: 'hidden', name: 'to', value: to}}))
|
|
||||||
, fieldset('cc', 'Cc (separate with commas)', field)
|
|
||||||
, fieldset('bcc', 'Bcc (separate with commas)', field)
|
|
||||||
, fieldset('subject', 'Subject (required)', field)
|
|
||||||
, field(h('textarea.u-marginTop--10', {props: {name: 'body', placeholder: 'Message', rows: '10'}}))
|
|
||||||
, h('div.u-marginTop--20.u-centered', [
|
|
||||||
button({buttonText: 'Send', loadingText: 'Sending...', loading$: state.loading$, error: state.error$})
|
|
||||||
])
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
// License: LGPL-3.0-or-later
|
|
||||||
const R = require('ramda')
|
|
||||||
const moment = require('moment')
|
|
||||||
|
|
||||||
const b64 = require('../../../../../components/b64')
|
|
||||||
const encode = require( '../../../../../components/encode-plain-email')
|
|
||||||
|
|
||||||
const format = {}
|
|
||||||
|
|
||||||
format.composeData = data => {
|
|
||||||
const cleanArray = s => s.replace(/ /g, '').split(',')
|
|
||||||
data.cc = data.cc ? cleanArray(data.cc) : false
|
|
||||||
data.bcc = data.bcc ? cleanArray(data.bcc) : false
|
|
||||||
return {'userId': 'me', 'resource': {'raw': encode(data)}}
|
|
||||||
}
|
|
||||||
|
|
||||||
format.replyData = data => {
|
|
||||||
return {'userId': 'me', 'resource': {'raw': encode(data), 'threadId': data.threadId}}
|
|
||||||
}
|
|
||||||
|
|
||||||
format.saveData = (resp, formData) => {
|
|
||||||
var data = R.omit(['cc', 'bcc'], formData)
|
|
||||||
return R.merge(data, {
|
|
||||||
supporter_id: appl.supporter_details.id
|
|
||||||
, nonprofit_id: app.nonprofit_id
|
|
||||||
, recipient_count: '1'
|
|
||||||
, gmail_thread_id: resp.threadId
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
format.thread = r => {
|
|
||||||
const value = (s, o) => {
|
|
||||||
var obj = R.find(R.propEq('name', s), o)
|
|
||||||
if (!obj) return false
|
|
||||||
return obj.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanReply = s => {
|
|
||||||
// remove all the replies within the message
|
|
||||||
var reply = R.take(1, s.split(/\n> /)).join('\n').trim()
|
|
||||||
// remove signature
|
|
||||||
return R.dropLast(1, reply.split(/\n/)).join('\n').trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstHeader = r.messages[0].payload.headers
|
|
||||||
|
|
||||||
var from = value('From', firstHeader)
|
|
||||||
|
|
||||||
var thread = {subject: value('Subject', firstHeader)}
|
|
||||||
|
|
||||||
thread.messages = R.map(m => {
|
|
||||||
var headers = m.payload.headers
|
|
||||||
return {
|
|
||||||
from: value('From', headers)
|
|
||||||
, to: value('To', headers)
|
|
||||||
, cc: value('Cc', headers)
|
|
||||||
, bcc: value('Bcc', headers)
|
|
||||||
, date: moment(value('Date', headers)).format("ddd, M/D/YYYY, h:mmA")
|
|
||||||
, body: m.payload.parts
|
|
||||||
? cleanReply(b64.decode(R.find(R.propEq('mimeType', 'text/plain'), m.payload.parts).body.data))
|
|
||||||
: b64.decode(m.payload.body.data)
|
|
||||||
}
|
|
||||||
}, r.messages)
|
|
||||||
|
|
||||||
return thread
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = format
|
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
// License: LGPL-3.0-or-later
|
|
||||||
// npm
|
|
||||||
const R = require('ramda')
|
|
||||||
const serialize = require('form-serialize')
|
|
||||||
const flyd = require('flyd')
|
|
||||||
const mergeAll = require('flyd/module/mergeall')
|
|
||||||
const filter = require('flyd/module/filter')
|
|
||||||
const keepWhen = require('flyd/module/keepwhen')
|
|
||||||
const takeUntil = require('flyd/module/takeuntil')
|
|
||||||
const sampleOn = require('flyd/module/sampleon')
|
|
||||||
const zip = require('flyd-zip')
|
|
||||||
const url$ = require('flyd-url')
|
|
||||||
const snabbdom = require('snabbdom')
|
|
||||||
const h = require('snabbdom/h')
|
|
||||||
const render = require('ff-core/render')
|
|
||||||
const validated = require('ff-core/validated-form')
|
|
||||||
const notification = require('ff-core/notification')
|
|
||||||
|
|
||||||
const flatMap = R.curry(require('flyd/module/flatmap'))
|
|
||||||
|
|
||||||
// common
|
|
||||||
const request = require('../../../../../common/request')
|
|
||||||
const googz = require('../../../../../common/google-api')
|
|
||||||
|
|
||||||
// from gmail dir
|
|
||||||
const format = require('./format')
|
|
||||||
|
|
||||||
var constraints = {
|
|
||||||
to: {email: true, required: true}
|
|
||||||
, cc: {email: true}
|
|
||||||
, bcc: {email: true}
|
|
||||||
, subject: {required: true}
|
|
||||||
, body: {required: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
function init(parentState) {
|
|
||||||
var state = {}
|
|
||||||
|
|
||||||
const isSignedInOnLoad$ = googz.init('https://mail.google.com/')
|
|
||||||
|
|
||||||
state.newSignIn$ = flyd.stream()
|
|
||||||
|
|
||||||
const signInClick$ = R.compose(
|
|
||||||
filter(R.not)
|
|
||||||
, sampleOn(R.__, isSignedInOnLoad$)
|
|
||||||
)(parentState.clickComposing$)
|
|
||||||
|
|
||||||
const isSignedInWithClick$ = flatMap(googz.signIn,
|
|
||||||
flyd.merge(state.newSignIn$, signInClick$))
|
|
||||||
|
|
||||||
const isSignedIn$ = flyd.merge(isSignedInOnLoad$, isSignedInWithClick$)
|
|
||||||
|
|
||||||
flyd.map(loadGmailApi, isSignedIn$)
|
|
||||||
|
|
||||||
state.from$ = R.compose(
|
|
||||||
flatMap(googz.email)
|
|
||||||
, filter(Boolean)
|
|
||||||
)(isSignedIn$)
|
|
||||||
|
|
||||||
const threadData$ = flatMap(getThreadData, parentState.threadId$)
|
|
||||||
|
|
||||||
state.formattedThreadData$ = flyd.map(format.thread, threadData$)
|
|
||||||
|
|
||||||
state.composeForm = validated.init({constraints})
|
|
||||||
|
|
||||||
state.replyForm = validated.init({constraints: {body: {required: true}}})
|
|
||||||
|
|
||||||
const formattedComposeData$ = flyd.map(format.composeData, state.composeForm.validData$)
|
|
||||||
|
|
||||||
const formattedReplyData$ = flyd.map(format.replyData, state.replyForm.validData$)
|
|
||||||
|
|
||||||
const sendData$ = flyd.merge(formattedReplyData$, formattedComposeData$)
|
|
||||||
|
|
||||||
state.sendResponse$ = flatMap(send, sendData$)
|
|
||||||
|
|
||||||
const formattedSaveData$ = flyd.map(R.apply(format.saveData), zip([state.sendResponse$, state.composeForm.validData$]))
|
|
||||||
|
|
||||||
state.saveResult$ = flatMap(save, formattedSaveData$)
|
|
||||||
|
|
||||||
const afterCompose$ = sampleOn(state.sendResponse$, state.composeForm.submit$)
|
|
||||||
|
|
||||||
const afterReply$ = sampleOn(state.sendResponse$, state.replyForm.submit$)
|
|
||||||
|
|
||||||
flyd.map(_ => state.composeForm.submit$().reset(), afterCompose$)
|
|
||||||
flyd.map(_ => state.replyForm.submit$().reset(), afterReply$)
|
|
||||||
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
const send = data => {
|
|
||||||
const result$ = flyd.stream()
|
|
||||||
gapi.client.gmail.users.messages.send(data).execute(result$)
|
|
||||||
return result$
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadGmailApi = _ => {
|
|
||||||
if(gapi.client.gmail) return
|
|
||||||
gapi.client.load('gmail', 'v1')
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = data => {
|
|
||||||
const path = `/nonprofits/${app.nonprofit_id}/supporter_emails/gmail`
|
|
||||||
var send = {gmail: data}
|
|
||||||
return flyd.map(req => req.body, request({method: 'post', path, send}).load)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getThreadData = id => {
|
|
||||||
const result$ = flyd.stream()
|
|
||||||
gapi.client.gmail.users.threads.get({
|
|
||||||
'userId': 'me'
|
|
||||||
, 'id': id
|
|
||||||
}).execute(result$)
|
|
||||||
return result$
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {init}
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
// License: LGPL-3.0-or-later
|
|
||||||
const modal = require('ff-core/modal')
|
|
||||||
const h = require('snabbdom/h')
|
|
||||||
const validated = require('ff-core/validated-form')
|
|
||||||
const button = require('ff-core/button')
|
|
||||||
const R = require('ramda')
|
|
||||||
|
|
||||||
module.exports = state => {
|
|
||||||
if(!state.gmail.formattedThreadData$()) return ''
|
|
||||||
return modal({
|
|
||||||
title: h('h4.modalTitle', state.gmail.formattedThreadData$().subject)
|
|
||||||
, id$: state.modalID$
|
|
||||||
, thisID: 'replyGmailModal'
|
|
||||||
, body: body(state)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const group = (labelText, spanText) =>
|
|
||||||
h('div.group.u-marginBottom--5',[
|
|
||||||
h('label.u-inlineBlock.col-2', `${labelText}: `)
|
|
||||||
, h('span.col-right--10', spanText + '')
|
|
||||||
])
|
|
||||||
|
|
||||||
const body = state =>
|
|
||||||
h('div', [
|
|
||||||
h('div', R.map(message, state.gmail.formattedThreadData$().messages))
|
|
||||||
, form(state)
|
|
||||||
])
|
|
||||||
|
|
||||||
const message = m =>
|
|
||||||
h('section.pastelBox--grey.u-padding--10.u-marginBottom--20.u-medPs', [
|
|
||||||
h('table.u-width--full', [
|
|
||||||
h('tr', [
|
|
||||||
h('td', [h('small.u-strong', m.from)])
|
|
||||||
, h('td', [h('small.u-textAlign--right.u-block', m.date)])
|
|
||||||
])
|
|
||||||
])
|
|
||||||
, h('div', R.map(greyP, R.filter(x => x.email,
|
|
||||||
[{label: 'to', email: m.to}, {label: 'cc', email: m.cc}, {label: 'bcc', email: m.bcc}]))
|
|
||||||
)
|
|
||||||
, h('p.u-marginTop--20', {style: {whiteSpace: 'pre-wrap'}}, m.body)
|
|
||||||
])
|
|
||||||
|
|
||||||
const greyP = x => h('p.u-margin--0', [h('small.u-color--grey', `${x.label} ${x.email}`)])
|
|
||||||
|
|
||||||
const form = state => {
|
|
||||||
var gmail = state.gmail
|
|
||||||
var to = state.supporter$().email
|
|
||||||
var from = gmail.from$()
|
|
||||||
var field = validated.field(gmail.replyForm)
|
|
||||||
return validated.form(gmail.replyForm, h('form.u-marginTop--30', [
|
|
||||||
group('From', from)
|
|
||||||
, group('To', to)
|
|
||||||
, h('input', {props: {type: 'hidden', name: 'from', value: from}})
|
|
||||||
, h('input', {props: {type: 'hidden', name: 'to', value: to}})
|
|
||||||
, h('input', {props: {type: 'hidden', name: 'subject', value: gmail.formattedThreadData$().subject}})
|
|
||||||
, h('input', {props: {type: 'hidden', name: 'threadId', value: state.threadId$()}})
|
|
||||||
, field(h('textarea.u-marginTop--10', {props: {name: 'body', placeholder: 'Reply', rows: '10'}}))
|
|
||||||
, h('div.u-marginTop--20.u-centered', [
|
|
||||||
button({buttonText: 'Send reply', loadingText: 'Sending reply...', loading$: state.loading$})
|
|
||||||
])
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,11 +14,8 @@ const notification = require('ff-core/notification')
|
||||||
const request = require('../../../../common/request')
|
const request = require('../../../../common/request')
|
||||||
const confirm = require('../../../../components/confirmation-modal')
|
const confirm = require('../../../../components/confirmation-modal')
|
||||||
|
|
||||||
const gmail = require('./gmail')
|
|
||||||
const actions = require('./supporter-actions')
|
const actions = require('./supporter-actions')
|
||||||
const activities = require('./supporter-activities')
|
const activities = require('./supporter-activities')
|
||||||
const replyModal = require('./gmail/reply-modal')
|
|
||||||
const composeModal = require('./gmail/compose-modal')
|
|
||||||
const offsiteDonationForm = require('./offsite-donation-form')
|
const offsiteDonationForm = require('./offsite-donation-form')
|
||||||
const supporterNoteForm = require('./supporter-note-form')
|
const supporterNoteForm = require('./supporter-note-form')
|
||||||
|
|
||||||
|
@ -53,7 +50,6 @@ const init = _ => {
|
||||||
|
|
||||||
state.composeOrReply$ = flyd.merge(state.clickComposing$, state.threadId$)
|
state.composeOrReply$ = flyd.merge(state.clickComposing$, state.threadId$)
|
||||||
|
|
||||||
state.gmail = gmail.init(state)
|
|
||||||
|
|
||||||
state.offsiteDonationForm = offsiteDonationForm.init(state)
|
state.offsiteDonationForm = offsiteDonationForm.init(state)
|
||||||
|
|
||||||
|
@ -77,7 +73,6 @@ const init = _ => {
|
||||||
// All streams that we want to trigger a refresh of the supporter timeline
|
// All streams that we want to trigger a refresh of the supporter timeline
|
||||||
const fetchActivitiesWith$ = mergeAll([
|
const fetchActivitiesWith$ = mergeAll([
|
||||||
state.pathPrefix$
|
state.pathPrefix$
|
||||||
, state.gmail.saveResult$
|
|
||||||
, state.offsiteDonationForm.saved$
|
, state.offsiteDonationForm.saved$
|
||||||
, state.supporterNoteForm.saved$
|
, state.supporterNoteForm.saved$
|
||||||
, deleteNoteResp$
|
, deleteNoteResp$
|
||||||
|
@ -92,22 +87,12 @@ const init = _ => {
|
||||||
state.activities = activities.init(state)
|
state.activities = activities.init(state)
|
||||||
|
|
||||||
state.modalID$ = mergeAll([
|
state.modalID$ = mergeAll([
|
||||||
flyd.map(()=> 'composeGmailModal', state.clickComposing$)
|
|
||||||
, flyd.map(()=> 'replyGmailModal', state.threadId$)
|
|
||||||
, flyd.map(()=> 'newSupporterNoteModal', state.editNoteData$)
|
, flyd.map(()=> 'newSupporterNoteModal', state.editNoteData$)
|
||||||
, flyd.map(()=> null, state.gmail.sendResponse$)
|
|
||||||
, flyd.map(()=> null, state.supporterNoteForm.saved$)
|
, flyd.map(()=> null, state.supporterNoteForm.saved$)
|
||||||
])
|
])
|
||||||
|
|
||||||
state.loading$ = mergeAll([
|
|
||||||
flyd.map(R.always(true), state.gmail.composeForm.validData$)
|
|
||||||
, flyd.map(R.always(true), state.gmail.replyForm.validData$)
|
|
||||||
, flyd.map(R.always(false), state.gmail.sendResponse$)
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
const message$ = mergeAll([
|
const message$ = mergeAll([
|
||||||
flyd.map(()=> 'Email sent', state.gmail.sendResponse$)
|
|
||||||
, flyd.map(()=> 'Successfully created a new offsite contribution', state.offsiteDonationForm.saved$)
|
, flyd.map(()=> 'Successfully created a new offsite contribution', state.offsiteDonationForm.saved$)
|
||||||
, flyd.map(()=> `Successfully ${noteMsg(state.noteAjaxMethod$)} supporter note`, state.supporterNoteForm.saved$)
|
, flyd.map(()=> `Successfully ${noteMsg(state.noteAjaxMethod$)} supporter note`, state.supporterNoteForm.saved$)
|
||||||
, flyd.map(()=> 'Successfully deleted supporter note', deleteNoteResp$)
|
, flyd.map(()=> 'Successfully deleted supporter note', deleteNoteResp$)
|
||||||
|
@ -141,11 +126,9 @@ const view = state => {
|
||||||
return h('div', [
|
return h('div', [
|
||||||
actions.view(state)
|
actions.view(state)
|
||||||
, activities.view(state)
|
, activities.view(state)
|
||||||
, composeModal(state)
|
|
||||||
, notification.view(state.notification)
|
, notification.view(state.notification)
|
||||||
, offsiteDonationForm.view(R.merge(state.offsiteDonationForm))
|
, offsiteDonationForm.view(R.merge(state.offsiteDonationForm))
|
||||||
, supporterNoteForm.view(R.merge(state.supporterNoteForm, {modalID$: state.modalID$}))
|
, supporterNoteForm.view(R.merge(state.supporterNoteForm, {modalID$: state.modalID$}))
|
||||||
, replyModal(state)
|
|
||||||
, confirm.view(state.confirmDelete, 'Are you sure you want to delete this note?')
|
, confirm.view(state.confirmDelete, 'Are you sure you want to delete this note?')
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ const button = (text, stream) =>
|
||||||
const view = state =>
|
const view = state =>
|
||||||
h('section.timeline-actions.u-padding--10', [
|
h('section.timeline-actions.u-padding--10', [
|
||||||
button('Note', state.newNote$)
|
button('Note', state.newNote$)
|
||||||
, button('Email', state.clickComposing$)
|
|
||||||
, button('Donation', () => appl.open_donation_modal(state.supporter$().id,
|
, button('Donation', () => appl.open_donation_modal(state.supporter$().id,
|
||||||
() => {state.offsiteDonationForm.saved$(Math.random())}
|
() => {state.offsiteDonationForm.saved$(Math.random())}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue