Merge pull request #147 from houdiniproject/remove_gmail

Remove gmail. Closes #145
This commit is contained in:
Eric Schultz 2019-02-15 13:41:40 -06:00 committed by GitHub
commit ccd7d35f1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 5 additions and 380 deletions

View file

@ -1,49 +0,0 @@
// License: LGPL-3.0-or-later
const flyd = require('flyd')
const filter = require('flyd/module/filter')
const R = require('ramda')
var googz = {}
// loads client API and returns a true stream if user is already signed in
googz.init = scope => {
if(document.getElementById('googzAuthApi')) return
var script = document.createElement('script')
script.type = 'text/javascript'
script.id = 'googzAuthApi'
document.body.appendChild(script)
script.src = 'https://apis.google.com/js/api.js?onload=loadGoogleClientLib'
const isSignedIn$ = flyd.stream()
window.loadGoogleClientLib = _ => {
gapi.load('client:auth2', _ => {
gapi.client.setApiKey(app.google_api)
gapi.auth2.init(
{'client_id': app.google_auth_client_id
, 'scope': scope}
).then(_ => {
isSignedIn$(gapi.auth2.getAuthInstance().isSignedIn.get())
})
})
}
return isSignedIn$
}
// returns a stream that will have a value once the user has signed in
googz.signIn = _ => {
const isSigningIn$ = flyd.stream()
gapi.auth2.getAuthInstance().signIn().then(isSigningIn$)
return isSigningIn$
}
// returns a stream with the email of the signed in user
googz.email = _ => {
const email$ = flyd.stream()
var profile = gapi.auth2.getAuthInstance()
var email = profile.currentUser.get().getBasicProfile().getEmail()
return email$(email)
}
module.exports = googz

View file

@ -100,16 +100,15 @@ exports.SupporterNote = (data, state) => {
exports.SupporterEmail = (data, state) => {
var jd = data.json_data
var canView = jd.from === state.gmail.from$()
var canView = false
var body = [h('span', `Subject: ${jd.subject}`), h('br')]
var thread = h('a', {props: {href: '#'}, on: {click: [state.threadId$, jd.gmail_thread_id]}}, 'View thread')
var signIn = h('small', [
h('a', {props: {href: '#'}, on: {click: state.gmail.newSignIn$}}, 'Sign in')
, ` as ${jd.from} to view thread`])
return {
title: `Email thread started by ${jd.from}`
, icon: 'fa-envelope'
, body: canView ? R.concat(body, thread) : R.concat(body, signIn)
, body: body
// , body: canView ? R.concat(body, thread) : R.concat(body, signIn)
}
}

View file

@ -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$})
])
]))
}

View file

@ -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

View file

@ -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}

View file

@ -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$})
])
]))
}

View file

@ -14,11 +14,8 @@ const notification = require('ff-core/notification')
const request = require('../../../../common/request')
const confirm = require('../../../../components/confirmation-modal')
const gmail = require('./gmail')
const actions = require('./supporter-actions')
const activities = require('./supporter-activities')
const replyModal = require('./gmail/reply-modal')
const composeModal = require('./gmail/compose-modal')
const offsiteDonationForm = require('./offsite-donation-form')
const supporterNoteForm = require('./supporter-note-form')
@ -51,9 +48,6 @@ const init = _ => {
state.supporter$ = flyd.merge(supporterResp$, flyd.stream({}))
state.composeOrReply$ = flyd.merge(state.clickComposing$, state.threadId$)
state.gmail = gmail.init(state)
state.offsiteDonationForm = offsiteDonationForm.init(state)
@ -77,7 +71,6 @@ const init = _ => {
// All streams that we want to trigger a refresh of the supporter timeline
const fetchActivitiesWith$ = mergeAll([
state.pathPrefix$
, state.gmail.saveResult$
, state.offsiteDonationForm.saved$
, state.supporterNoteForm.saved$
, deleteNoteResp$
@ -92,22 +85,12 @@ const init = _ => {
state.activities = activities.init(state)
state.modalID$ = mergeAll([
flyd.map(()=> 'composeGmailModal', state.clickComposing$)
, flyd.map(()=> 'replyGmailModal', state.threadId$)
, flyd.map(()=> 'newSupporterNoteModal', state.editNoteData$)
, flyd.map(()=> null, state.gmail.sendResponse$)
, 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([
flyd.map(()=> 'Email sent', state.gmail.sendResponse$)
, 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 deleted supporter note', deleteNoteResp$)
@ -141,11 +124,9 @@ const view = state => {
return h('div', [
actions.view(state)
, activities.view(state)
, composeModal(state)
, notification.view(state.notification)
, offsiteDonationForm.view(R.merge(state.offsiteDonationForm))
, supporterNoteForm.view(R.merge(state.supporterNoteForm, {modalID$: state.modalID$}))
, replyModal(state)
, confirm.view(state.confirmDelete, 'Are you sure you want to delete this note?')
])
}

View file

@ -13,7 +13,7 @@ const button = (text, stream) =>
const view = state =>
h('section.timeline-actions.u-padding--10', [
button('Note', state.newNote$)
, button('Email', state.clickComposing$)
, button('Email', () => { window.open(`mailto:${state.supporter$().email}`)})
, button('Donation', () => appl.open_donation_modal(state.supporter$().id,
() => {state.offsiteDonationForm.saved$(Math.random())}
)