Created
February 26, 2020 21:45
This script enhanced the Content Calendar template with common functionality
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***** | |
* Title: Enhanced Content Calendar | |
* License: MIT | |
* Author: Openside (Team behind On2Air products and BuiltOnAir community) | |
* Sites: | |
* https://openside.com - Openside Consulting Services | |
* https://openside.com/#products - On2Air Products | |
* https://builtonair.com - All things Airtable Community | |
* | |
* Reach out for all your Airtable needs | |
* | |
* Description: This script will enhance the Content Calendar Base by providing 2 common tasks: | |
* 1) Create new Content - this easier approach to creating new content makes it quick to get content added to your table | |
* 2) Publish Content - once you have created content, you want to publish it quickly to multiple platforms. This makes it easy | |
* to track which platforms you are publishing it to (Twitter, Instagram, or Facebook) and automatically links it back to the primary content. | |
* | |
* NOTE: This does not actually publish to Social media platforms, it simply tracks where you have published the content to and how. | |
* | |
* TEMPLATE: https://airtable.com/templates/featured/exp3FNmOkdHZvprXB/content-calendar | |
* | |
* No changes are required to be made, simply run script and select one of the two options. | |
* | |
*/ | |
//----------------------------------- | |
//Function: mkdLink | |
//Description: Create markdown for a link | |
//----------------------------------- | |
const mkdLink = (label, link) => { | |
return `[${label}](${link})` | |
} | |
//----------------------------------- | |
//Function: getCollablId | |
//Description: looks up the collaborator id based on email | |
//----------------------------------- | |
const getCollabId = (email) => { | |
email = email.trim().toLowerCase() | |
let collabs = base.activeCollaborators | |
for(let collab of collabs){ | |
if(collab.email.toLowerCase() === email){ | |
return collab.id | |
} | |
} | |
return null | |
} | |
//----------------------------------- | |
//Function: cast | |
//Params: | |
//table - The table model that is being updated (needed to get the field) | |
//field - The field name or id that will be updated | |
//value - the string value to be converted based on the field type | |
//Description: Converts a String value to the | |
//corresponding value needed to perform an update | |
//----------------------------------- | |
const cast = ( table, field, value ) => { | |
let fieldMeta = table.getField(field) | |
let type = fieldMeta.type | |
return castType( type, value ) | |
} | |
//----------------------------------- | |
//Function: castType | |
//Params: | |
//type - the fieldType retrieved from table.getField(field).type | |
//value - the string value to be converted based on the field type | |
//Description: Converts a String value to the | |
//corresponding value needed to perform an update | |
//----------------------------------- | |
const castType = ( type, value ) => { | |
if(type === 'singleSelect'){ | |
value = {name:value} | |
}else if(type === 'multipleSelects'){ | |
let items = value.split(',') | |
let values = [] | |
items.forEach( i => { | |
values.push({name: i.trim()}) | |
}) | |
value = values | |
}else if(type === 'number' || type === 'percent' || type === 'currency'){ | |
value = parseFloat(value) | |
}else if(type === 'multipleRecordLinks'){ | |
let items = value.split(',') | |
let values = [] | |
items.forEach( i => { | |
values.push({id: i.trim()}) | |
}) | |
value = values | |
}else if(type === 'singleRecordLink'){ | |
value = {id:value} | |
}else if(type === 'multipleAttachments'){ | |
let items = value.split(',') | |
let values = [] | |
items.forEach( i => { | |
values.push({url: i.trim()}) | |
}) | |
value = values | |
}else if(type === 'checkbox'){ | |
value = value === 1 || value === true || value === 'true' || value === '1' || value === 'yes' || value === 'on' | |
}else if(type === 'barcode'){ | |
value = {text: value} | |
}else if(type === 'rating' || type === 'duration'){ | |
value = parseInt(value) | |
}else if(type === 'singleCollaborator'){ | |
value = {id: getCollabId(value)} | |
}else if(type === 'multipleCollaborators'){ | |
let items = value.split(',') | |
let values = [] | |
items.forEach( i => { | |
values.push({id: getCollabId(i)}) | |
}) | |
value = values | |
}else if(type === 'date' || type === 'dateTime'){ | |
value = value.trim().toLowerCase() === 'now' ? new Date().toISOString() : value | |
} | |
return value | |
} | |
let header = (msg = null) => { | |
output.clear() | |
output.markdown('# Content Tasks') | |
if(msg){ | |
output.markdown(msg) | |
} | |
} | |
const contentTable = base.tables[0] | |
const socialTable = base.tables[1] | |
const timesheetsTable = base.tables[2] | |
const optionsToButtons = async( label, field, selected = '', defualtVariant = 'secondary', optionValueKey = 'name', optionLabelKey = 'name' ) => { | |
let options = field.options.choices | |
let buttons = [] | |
for(let o=0; o<options.length; o++){ | |
let option = options[o] | |
buttons.push({ | |
label: option[optionLabelKey], | |
value: option[optionValueKey], | |
variant: ( option.name === selected || option.id === selected ) ? 'primary' : defualtVariant | |
}) | |
} | |
return input.buttonsAsync(label, buttons ) | |
} | |
const quickCreate = async( ) => { | |
output.markdown('## Quick Create Content Calendar') | |
output.markdown('Enter a blank space to leave any item blank') | |
let headline = await input.textAsync('Enter the Headline') | |
let subheadline = await input.textAsync('Enter the Sub-headline') | |
let image = await input.textAsync('Enter the URL for the Header Image') | |
let draft = await input.textAsync('Enter the Draft due date') | |
let publish = await input.textAsync('Enter the Publish date') | |
let link = await input.textAsync('Enter the Link to Content') | |
let status = await optionsToButtons('Select the Status', contentTable.getField('Status'),'Planned') | |
let section = await optionsToButtons('Select the Section', contentTable.getField('Section'),'') | |
let author = await optionsToButtons('Select the Author', contentTable.getField('Author'),'', 'secondary', 'email') | |
let data = { | |
'Headline': headline.trim(), | |
'Sub-headline': subheadline.trim(), | |
'Header image': cast( contentTable, 'Header image', image), | |
'Draft due': cast( contentTable, 'Draft due', draft), | |
'Publish date': cast( contentTable, 'Publish date', publish), | |
'Link': cast( contentTable, 'Link', link), | |
'Status': cast( contentTable, 'Status', status), | |
'Section': cast( contentTable, 'Section', section), | |
'Author': cast( contentTable, 'Author', author) | |
} | |
console.log(data) | |
let recId = await contentTable.createRecordAsync(data) | |
let recLink = `https://airtable.com/${contentTable.id}/${recId}` | |
return 'New Record Created. ' + mkdLink('Click Here to Review', recLink) | |
} | |
const publish = async( ) => { | |
let selected = await input.recordAsync( 'Select Content to be published', contentTable, { | |
shouldAllowCreatingRecord: false | |
}) | |
let done = false | |
let DONE_TEXT = 'DONE PUBLISHING' | |
let published_to = [] | |
let selected_name = selected.getCellValueAsString('Headline') | |
let selected_details = | |
selected.getCellValueAsString('Sub-headline') | |
+ " \nAuthor: " + selected.getCellValueAsString('Author') | |
+ " \nPublished: " + selected.getCellValueAsString('Publish date') | |
+ " \nStatus: " + selected.getCellValueAsString('Status') | |
+ " \nSection: " + selected.getCellValueAsString('Section') | |
let details = { | |
'Twitter': {length: 120}, | |
'Facebook': {length: 80}, | |
'Instagram': {length: 150} | |
} | |
while(!done){ | |
header('## ' + selected_name) | |
output.markdown(selected_details) | |
let platforms_text = ['Twitter','Instagram','Facebook',DONE_TEXT] | |
let platforms = [] | |
for(let p=0;p<platforms_text.length;p++){ | |
let plat = platforms_text[p] | |
platforms.push({ | |
label: plat, | |
value: plat, | |
variant: plat === DONE_TEXT ? 'secondary' : (published_to.includes(plat) ? 'danger' : 'default') | |
}) | |
} | |
let platform = await input.buttonsAsync('Select Platform to publish to',platforms) | |
if(platform === DONE_TEXT){ | |
done = true | |
//update original to set status | |
await contentTable.updateRecordAsync( selected.id, { | |
Status: {name: 'Published'} | |
}) | |
}else{ | |
output.markdown('### ' + platform + ' Details') | |
let post = await input.textAsync('Post (character limit: ' + details[platform]['length'] + ')') | |
let date = await input.textAsync('Published Date (use \'now\' for today)') | |
let link = await input.textAsync('Published Link') | |
let doit = await input.buttonsAsync('Publish?', [{label:'PUBLISH',variant:'primary'},{label:'CANCEL',variant:'default'}]) | |
if(doit === 'PUBLISH'){ | |
published_to.push(platform) | |
await socialTable.createRecordAsync({ | |
'Social post': post, | |
'Platform': cast( socialTable,'Platform','Voyager ' + platform), | |
"Date": cast( socialTable, 'Date', date), | |
"Published link": cast(socialTable,'Published link',link), | |
"Related story": cast( socialTable,"Related story", selected.id) | |
}) | |
} | |
} | |
} | |
return published_to.length ? '##### Published to: ' + published_to.join(', ') : '' | |
} | |
let start = async(msg = null) => { | |
header(msg) | |
let selected = await input.buttonsAsync('Perform Task',[{ | |
label:'Quick Create', | |
value: 'create', | |
},{ | |
label:'Publish Content', | |
value:'publish' | |
}]) | |
let response = '' | |
if(selected === 'create'){ | |
response = await quickCreate() | |
}else if(selected === 'publish'){ | |
response = await publish() | |
} | |
await start(response) | |
} | |
await start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment