27b41a4523
This does two things:
1. If the TKs in the draft has changed, add/remove the corresponding
columns from the table
2. Either way, update the preview so it matches the draft text
All this only happens once you click the "Refresh" button, because
we don't want to be putting *too much* load on the app also 😜
303 lines
6.2 KiB
Svelte
303 lines
6.2 KiB
Svelte
<script>
|
|
import Importabular from "importabular"
|
|
import showdown from "showdown"
|
|
|
|
let converter = new showdown.Converter()
|
|
|
|
let editor
|
|
|
|
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]'
|
|
|
|
let sheetOne
|
|
let step = 0
|
|
let tkList = []
|
|
|
|
let cookiePreviewText = ''
|
|
let cookiePreviewRow = 0
|
|
|
|
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])
|
|
}
|
|
}
|
|
|
|
// remove duplicates
|
|
tkList = [...new Set(tkList)]
|
|
|
|
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,
|
|
onChange(data) {
|
|
cookiePreviewText = getPreview()
|
|
}
|
|
})
|
|
|
|
step = 2
|
|
|
|
// for debugging only
|
|
window.sheetOne = sheetOne
|
|
}
|
|
|
|
/**
|
|
* 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!
|
|
cookiePreviewText = getPreview()
|
|
}
|
|
|
|
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
|
|
let tkIndex = window.sheetOne.columns.findIndex(t => t.label == tk)
|
|
|
|
// replace it!
|
|
previewText = previewText.replace(new RegExp(`\\[TK ${tk}\\]`, 'g'), r[tkIndex])
|
|
}
|
|
|
|
return previewText
|
|
}
|
|
|
|
// automatically update
|
|
$: cookiePreviewText = getPreview(cookiePreviewRow)
|
|
|
|
function next() {
|
|
step += 1
|
|
}
|
|
</script>
|
|
|
|
<main>
|
|
<img src="/assets/email-baker.png" class="main-pic" alt="Email Oven"/>
|
|
<h1>Chip <span class="highlight">Choc</span></h1>
|
|
<h2>Cookie-cutter emails made easy.</h2>
|
|
|
|
{#if step != 0}
|
|
<h3 class="mt-3">Step {step}</h3>
|
|
{/if}
|
|
|
|
{#if step == 0}
|
|
<button on:click={showEmail}>Start Drafting</button>
|
|
{/if}
|
|
|
|
{#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>
|
|
</div>
|
|
<div class="email-content">
|
|
<textarea bind:value={emailContent}/>
|
|
<p>
|
|
<strong>Detected fields:</strong>
|
|
{#each tkList as tk (tk)}
|
|
<span class="tag">{tk}</span>
|
|
{/each}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if step >= 2}
|
|
<div class="instructions">
|
|
<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>
|
|
</div>
|
|
{/if}
|
|
|
|
{#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 && !!cookiePreviewText}
|
|
<h4 class="mt-3">Preview</h4>
|
|
<div class="cookie-preview">{@html converter.makeHtml(cookiePreviewText)}</div>
|
|
<button on:click={decrementPreviewRow}>< Prev</button>
|
|
<button on:click={incrementPreviewRow}>Next ></button>
|
|
{/if}
|
|
|
|
</main>
|
|
|
|
<style>
|
|
main {
|
|
text-align: center;
|
|
padding: 1em;
|
|
max-width: 240px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
h1 {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
h1 + h2 {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.main-pic {
|
|
max-width: 100%;
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
main {
|
|
max-width: none;
|
|
}
|
|
|
|
.main-pic {
|
|
max-width: 240px;
|
|
}
|
|
}
|
|
|
|
.instructions {
|
|
max-width: 640px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.email-content {
|
|
margin: 4em auto;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.cookie-preview {
|
|
text-align: left;
|
|
max-width: 640px;
|
|
margin: auto;
|
|
}
|
|
</style>
|