2021-04-29 05:51:32 -04:00
< script >
2021-05-14 00:00:32 -04:00
import Importabular from "importabular"
2021-05-14 09:33:20 -04:00
import showdown from "showdown"
2021-09-14 05:18:22 -04:00
let converter = new showdown.Converter({
simplifiedAutoLink: true, // automatically convert links...
excludeTrailingPunctuationFromURLs: true, // ...but not the trailing punctuation
openLinksInNewWindow: true, // don't forget to open in new window!
simpleLineBreaks: true, // use two line breaks to start a new paragraph
emoji: true, // because why not 😉
strikethrough: true, // ~~that also~~
})
2021-05-14 00:00:32 -04:00
let editor
2021-05-14 09:33:20 -04:00
let emailContent = 'Hi [TK name],\n\nIt\'s been a while since we heard from you. How are you doing? Your last article [TK article] did quite well, and we were wondering if you\'d be interested in writing a sequel?\n\nCheers, \nThe Snipette Team[TK PS]'
2021-05-14 00:00:32 -04:00
let sheetOne
let step = 0
let tkList = []
2021-05-28 12:07:04 -04:00
let fromEmail = ''
2021-06-04 13:45:52 -04:00
let subject = ''
2021-05-28 12:07:04 -04:00
let cookiePreviewData = { text : '' , email : '' }
2021-05-14 09:17:06 -04:00
let cookiePreviewRow = 0
2021-05-14 00:00:32 -04:00
function showEmail() {
step = 1
}
function detectFields(value) {
if (!value) return []
let matches = value.match(/\[TK (\w+?)\]/gm)
let tkList = []
if (!!matches) {
let propertyRegex = new RegExp(/\[TK (\w+)\]/)
for (let m of matches) {
tkList.push(propertyRegex.exec(m)[1])
}
}
2021-05-28 10:46:01 -04:00
// remove duplicates
tkList = [...new Set(tkList)]
2021-05-14 00:00:32 -04:00
return tkList
}
$: tkList = detectFields(emailContent)
function showSheet() {
// Figure out columns
let columns = []
if (!tkList.includes('email')) {
columns.push({
label: 'email',
description: 'Email address of the recipient',
placeholder: 'someone@members.snipettemag.com',
})
}
for (let tk of tkList) {
columns.push({
label: tk,
})
}
// First sheet
sheetOne = new Importabular({
node: editor,
columns: columns,
2021-05-14 09:17:06 -04:00
onChange(data) {
2021-05-28 12:07:04 -04:00
cookiePreviewData = getPreview()
2021-05-14 09:17:06 -04:00
}
2021-05-14 00:00:32 -04:00
})
step = 2
// for debugging only
window.sheetOne = sheetOne
}
2021-05-28 11:15:56 -04:00
/**
* Refresh sheet
*
* Destroys the current sheet, but saves the data first so if
* there are any columns in the new sheet that matches the old
* ones, it'll add them back. (Columns meaning TKs, of course)
*/
function refreshSheet() {
let oldData = sheetOne.getData()
let oldColumns = sheetOne.columns.map(c => c.label)
sheetOne.destroy()
// now, make it up again with the new TKs
showSheet()
// figure out the new data
let newData = []
let tkOrder = []
// first, we figure out how the old column order corresponds to
// the new one
for (let col of sheetOne.columns.map(c => c.label)) {
tkOrder.push(oldColumns.indexOf(col))
}
// now we fill this, row by row
for (let oldRow of oldData) {
// skip empty rows
if (oldRow.every(v => !v)) continue
// now, we figure out what to put for the new row
let newRow = []
for (let tkIndex of tkOrder) {
if (tkIndex < = -1) {
// new TK; leave it blank
newRow.push('')
} else {
// old TK; keep it filled!
newRow.push(oldRow[tkIndex])
}
}
// and finally, when all is done, we save it.
newData.push(newRow)
}
// fill it with the new data
sheetOne.setData(newData)
// don't forget to refresh the preview!
2021-05-28 12:07:04 -04:00
cookiePreviewData = getPreview()
2021-05-28 11:15:56 -04:00
}
2021-05-14 09:17:06 -04:00
function incrementPreviewRow() {
let sheetLength = sheetOne.getData().length
// make sure it's not too big...
if (cookiePreviewRow >= sheetLength-1) {
cookiePreviewRow = sheetLength - 1
} else if (cookiePreviewRow < 0 ) {
// ...and not too small
cookiePreviewRow = 0
} else {
// if not, then make it "just right"! :)
cookiePreviewRow++
}
}
function decrementPreviewRow() {
let sheetLength = sheetOne.getData().length
// make sure it's not too small...
if (cookiePreviewRow >= sheetLength) {
// ...and not too big
cookiePreviewRow = sheetLength - 1
} else if (cookiePreviewRow < = 0) {
// if not, then make it "just right"! :)
cookiePreviewRow = 0
} else {
cookiePreviewRow--
}
}
function getPreview(row) {
if (!row) row = cookiePreviewRow
let previewText = emailContent
// get the row we're working on
if (!sheetOne) return '' // no sheet to get data from :(
let data = sheetOne.getData()
if (row < 0 | | data . length < row ) return previewText / / no row to apply : (
let r = data[row]
for (let tk of tkList) {
// figure out which column holds values for this TK
2021-05-28 12:07:04 -04:00
let tkIndex = sheetOne.columns.findIndex(t => t.label == tk)
2021-05-14 09:17:06 -04:00
// replace it!
previewText = previewText.replace(new RegExp(`\\[TK ${ tk } \\]`, 'g'), r[tkIndex])
}
2021-05-28 12:07:04 -04:00
// save the email
let previewEmail = r[sheetOne.columns.findIndex(t => t.label == 'email')]
return { text : previewText , email : previewEmail }
2021-05-14 09:17:06 -04:00
}
// automatically update
2021-05-28 12:07:04 -04:00
$: cookiePreviewData = getPreview(cookiePreviewRow)
2021-05-14 09:17:06 -04:00
2021-05-14 00:00:32 -04:00
function next() {
step += 1
}
2021-05-28 12:07:22 -04:00
function prev() {
step -= 1
}
2021-04-29 05:51:32 -04:00
2021-05-28 14:41:07 -04:00
function stepOne() {
step = 1
}
function stepTwo() {
step = 2
}
// This is to display which email is sending next
2021-05-28 14:40:16 -04:00
let nowSending
2021-05-14 00:00:32 -04:00
2021-05-28 14:41:07 -04:00
// And this is for the progress bar, mainly
let emailsSoFar = 0
let emailsToSend = 0
2021-05-28 14:40:16 -04:00
/**
* Hit Send
*
* collates all the data for sending, and passes it
* on to the backend for actual processing. Note that here we
* cookie-cut the emails on the frontend itself; the backend
* just blindly takes what it gets. We're doing this so that we
* can use the same getPreview() function and be consistent: we
* don't want the final to suddenly end up looking very different
* from the preview!
*/
function hitSend() {
step = 4
let emails = []
// process the 'i'th row of the sheet, one by one
for (let i=0; i< sheetOne.getData (). length ; i ++) {
let previewData = getPreview(i)
// skip if email is blank
if (!previewData.email) continue
// add it to the list of emails to be sent
emails.push({
from: fromEmail,
to: previewData.email,
2021-06-04 13:45:52 -04:00
subject: subject,
2021-05-28 14:40:16 -04:00
text: previewData.text,
html: converter.makeHtml(previewData.text),
})
}
2021-05-14 00:00:32 -04:00
2021-05-28 14:41:07 -04:00
// keep track of how many we had to start witth
emailsToSend = emails.length
emailsSoFar = 0
2021-05-14 00:00:32 -04:00
2021-05-28 14:40:16 -04:00
// okay, now let's open a socket to the server!
let socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
let socketUrl = socketProtocol + '//' + window.location.host + window.location.pathname + 'hit-send/'
let socket = new WebSocket(socketUrl)
2021-05-28 12:07:04 -04:00
2021-05-28 14:40:16 -04:00
socket.onopen = function() {
2021-05-28 14:41:07 -04:00
nowSending = emails.shift()
2021-05-28 14:40:16 -04:00
socket.send(JSON.stringify(nowSending))
}
2021-05-14 00:00:32 -04:00
2021-05-28 14:40:16 -04:00
socket.onmessage = (message) => {
try {
message = JSON.parse(message.data)
} catch (err) {
console.error(`Received malformed response from server: ${ message . data } `)
return
}
2021-05-14 00:00:32 -04:00
2021-05-28 14:40:16 -04:00
if (message.success) {
console.log(`${ message . to } 's email sent successfully!`)
2021-05-14 09:17:06 -04:00
2021-05-28 14:40:16 -04:00
// send the next one
2021-05-28 14:41:07 -04:00
nowSending = emails.shift()
2021-05-28 14:40:16 -04:00
if (!!nowSending) {
socket.send(JSON.stringify(nowSending))
console.log(`Sending: ${ nowSending . to } `)
2021-05-28 14:41:07 -04:00
// update the counter for the progress bar
emailsSoFar += 1
2021-05-28 14:40:16 -04:00
} else {
socket.close()
2021-05-28 14:41:07 -04:00
step = 5
2021-05-28 14:40:16 -04:00
}
} else {
console.error('Something went wrong :(')
step = 3
}
}
}
< / script >
< main >
{ #if step < 4 }
2021-06-04 14:39:03 -04:00
< img src = "assets/email-baker.png" class = "main-pic" alt = "Email Oven" / >
2021-05-28 14:40:16 -04:00
< h1 > Chip < span class = "highlight" > Choc< / span > < / h1 >
< h2 > Cookie-cutter emails made easy.< / h2 >
2021-05-28 11:15:56 -04:00
2021-05-28 14:40:16 -04:00
{ #if step >= 1 }
< h3 class = "mt-3" > Step 1< / h3 >
{ /if }
2021-05-28 11:15:56 -04:00
2021-05-28 14:40:16 -04:00
{ #if step == 0 }
< button on:click = { showEmail } > Start Drafting </ button >
{ /if }
2021-05-28 12:07:22 -04:00
2021-05-28 14:40:16 -04:00
{ #if step >= 1 }
< div class = "instructions" >
< p > Compose your email below, leaving [TK stuff] to be replaced in the table. Make sure each TK is a single word: no spaces allowed!< / p >
2021-05-28 12:07:22 -04:00
< / div >
2021-05-28 14:40:16 -04:00
< div class = "email-content" >
2021-06-04 13:45:52 -04:00
< div class = "header" >
< div class = "form-group" >
From
< input type = "text" placeholder = "chipchoc@example.com" bind:value = { fromEmail } / >
< / div >
< div class = "form-group" >
Subject
< input type = "text" placeholder = "Hi there" bind:value = { subject } / >
< / div >
< / div >
2021-05-28 14:40:16 -04:00
< textarea bind:value = { emailContent } / >
< p >
< strong > Detected fields:< / strong >
{ #each tkList as tk ( tk )}
< span class = "tag" > { tk } </ span >
{ /each }
< / p >
< / div >
{ /if }
2021-05-28 12:07:22 -04:00
2021-05-28 14:40:16 -04:00
{ #if step >= 2 }
< h3 class = "mt-3" > Step 2< / h3 >
2021-05-28 12:07:22 -04:00
< div class = "instructions" >
2021-05-28 14:40:16 -04:00
< p > Now, fill in the fields for each user. (You can also copy-paste rows and columns directly from Air, if the ordering is right!)< / p >
2021-05-28 12:07:22 -04:00
< / div >
{ /if }
2021-05-28 14:40:16 -04:00
{ #if step == 1 }
< button on:click = { showSheet } > Next</button >
{ /if }
{ #if step > 1 }
< button on:click = { refreshSheet } > Refresh</button >
{ /if }
< div id = "editor" bind:this = { editor } > </div >
{ #if step >= 2 && !! cookiePreviewData }
< h4 class = "mt-3" > Preview< / h4 >
< div class = "cookie-preview" >
< div class = "preview-header" >
< p > From: { fromEmail || '[NOBODY!!!]' } </ p >
< p > To: { cookiePreviewData . email || '[NOBODY!!!]' } </ p >
2021-06-04 13:45:52 -04:00
< p > Subject: { subject || '[NO SUBJECT #notgood]' } </ p >
2021-05-28 14:40:16 -04:00
< hr / >
< / div >
{ @html converter . makeHtml ( cookiePreviewData . text )}
2021-05-28 12:07:22 -04:00
< / div >
2021-05-28 14:40:16 -04:00
< button on:click = { decrementPreviewRow } > < Prev </ button >
< button on:click = { incrementPreviewRow } > Next & gt ;</ button >
< h3 > Step 3< / h3 >
{ #if step == 2 }
< div class = "instructions" >
< p > If everything looks okay, then we're done! Click on the button below to put the cutter in action.< / p >
< / div >
< button on:click = { next } > Yup, send ' em out !</ button >
{ /if }
{ #if step == 3 }
< div class = "instructions" >
< p > Are you < strong > sure< / strong > you want to send them out?< / p >
< p > Are you ready?< / p >
< p > Did you look through all the previews?< / p >
< p > Have you double-checked the names and email IDs to make sure they match?< / p >
< / div >
< button on:click = { hitSend } > Hit send !</ button > < button on:click = { prev } > Ummmm..</button >
{ /if }
2021-05-28 12:07:22 -04:00
{ /if }
2021-05-28 14:40:16 -04:00
{ /if }
2021-05-28 12:07:22 -04:00
2021-05-28 14:40:16 -04:00
{ #if step == 4 }
2021-06-04 14:39:03 -04:00
< img src = "assets/email-baking.png" class = "main-pic" alt = "Email Oven with cookies inside" / >
2021-05-28 14:41:07 -04:00
< h1 >< span class = { emailsSoFar % 2 == 0 ? 'highlight' : '' } > Chip</span > < span class = { emailsSoFar % 2 == 0 ? '' : 'highlight' } > Choc</span ></ h1 >
< h2 > Your cookies are being cut...< / h2 >
< progress value = { emailsSoFar } max= { emailsToSend } > { emailsToSend ? emailsSoFar / emailsToSend * 100 : 0 } %</ progress >
{ #if !! nowSending }
< div class = "cookie-preview" >
< div class = "preview-header" >
< p > From: { nowSending . from || '[NOBODY!!!]' } </ p >
< p > To: { nowSending . to || '[NOBODY!!!]' } </ p >
2021-06-04 13:45:52 -04:00
< p > Subject: { subject || '[NO SUBJECT #notgood]' } </ p >
2021-05-28 14:41:07 -04:00
< hr / >
{ @html nowSending . html }
2021-05-28 14:40:16 -04:00
< / div >
< / div >
2021-05-14 09:17:06 -04:00
{ /if }
2021-05-28 14:41:07 -04:00
{ /if }
{ #if step == 5 }
2021-06-04 14:39:03 -04:00
< img src = "assets/cookies.png" class = "main-pic" alt = "A Pile of Cookies" / >
2021-05-28 14:41:07 -04:00
< h1 > Wheee< span class = "highlight" > !< / span > < / h1 >
< h2 > All emails sent successfully.< / h2 >
< button on:click = { stepTwo } > Back to Draft </ button >
< button on:click = { stepOne } > Start Over </ button >
{ /if }
2021-05-14 09:17:06 -04:00
2021-04-29 05:51:32 -04:00
< / main >
< style >
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
h1 {
2021-05-14 00:00:32 -04:00
margin-bottom: 0;
}
h1 + h2 {
margin-top: 0;
2021-04-29 05:51:32 -04:00
}
2021-04-29 07:14:42 -04:00
.main-pic {
max-width: 100%;
}
2021-04-29 05:51:32 -04:00
@media (min-width: 640px) {
main {
max-width: none;
}
2021-04-29 07:14:42 -04:00
.main-pic {
max-width: 240px;
}
2021-04-29 05:51:32 -04:00
}
2021-05-14 00:00:32 -04:00
.instructions {
max-width: 640px;
margin: 0 auto;
}
.email-content {
margin: 4em auto;
}
2021-06-04 13:45:52 -04:00
.email-content .header {
max-width: 640px;
text-align: left;
margin: auto;
}
.email-content .form-group {
display: flex;
align-items: center;
}
.email-content .form-group input {
margin-left: 1em;
width: 100%;
border: 0
}
2021-05-14 00:00:32 -04:00
.email-content textarea {
width: 100%;
max-width: 640px;
font-family: inherit;
min-height: 15em;
}
.tag {
margin: 0.2em;
padding: 0.5em 1em 0.5em 1em;
background: #ffe3ff;
border-radius: 1em;
}
2021-05-14 09:33:20 -04:00
.cookie-preview {
text-align: left;
max-width: 640px;
margin: auto;
2021-05-28 12:07:04 -04:00
padding: 1em;
border: 1px solid black;
2021-05-14 09:33:20 -04:00
}
2021-05-28 14:41:07 -04:00
progress {
width: 100%;
max-width: 640px;
margin: 1em;
color: #800020;
}
progress::-moz-progress-bar,
progress::-webkit-progress-value {
background-color: #800020;
}
2021-04-29 07:14:42 -04:00
< / style >