Merge pull request #147 from houdiniproject/remove_gmail
Remove gmail. Closes #145
This commit is contained in:
commit
ccd7d35f1f
8 changed files with 5 additions and 380 deletions
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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?')
|
||||
])
|
||||
}
|
||||
|
|
|
@ -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())}
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue