diff --git a/.gitignore b/.gitignore index c3d3dd0..5b03efd 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ config data src exports +block.json diff --git a/build.js b/build.js index 9eccfbc..fa218ee 100755 --- a/build.js +++ b/build.js @@ -3,6 +3,8 @@ import {exec} from 'child_process'; import config from 'config'; import Generator from "yeoman-generator"; +import yeoman from 'yeoman-environment'; +import {buildHubspot} from "./platforms/hubspot/hubspot-adapter.js"; const isDev = process.env.NODE_ENV === 'development' || (config.has('isDev') && config.get('isDev')); // Check README file in case you get "missing files" error. const modulePath = isDev ? '' : 'node_modules/create-block-dev-tool/'; @@ -30,8 +32,13 @@ class buildGenerator extends Generator { console.log(stdout); resolve(); }); + } else if (this.data.platform === 'Hubspot Email') { + buildHubspot(blockName) + .then(() => { + resolve(); + }); } else if (this.data.platform === 'Hubspot') { - console.log('Coming soon...'); + console.log('"Hubspot" Coming soon...'); resolve(); } else { resolve(); @@ -43,7 +50,6 @@ class buildGenerator extends Generator { } } -import yeoman from 'yeoman-environment'; const build = new buildGenerator([], {env: yeoman.createEnv()}, {}); build.run().then(() => null); diff --git a/generators/block/index.cjs b/generators/block/index.cjs index ae4d95c..0c44d7b 100644 --- a/generators/block/index.cjs +++ b/generators/block/index.cjs @@ -21,6 +21,20 @@ module.exports = class extends Generator { return !!str; } }, + { + type: "input", + name: "group", + message: "Company/Organization Name", + validate: (str) => { + const matches = str.match(/\d+/g); + + if (matches != null) { + return false; + } + + return !!str; + } + }, { type: "list", name: "baseView", @@ -45,9 +59,11 @@ module.exports = class extends Generator { writing() { const title = capitalize(this.data.name); + const group = capitalize(this.data.group); const data = Object.assign(this.data, { title, blockFilename: title.toLowerCase().replace(/ /ig, '-'), + blockGroupName: group.toLowerCase().replace(/ /ig, '-'), blockClassName: title.toLowerCase().replace(/ /ig, '_'), }); @@ -85,6 +101,12 @@ module.exports = class extends Generator { data ); + this.fs.copyTpl( + this.templatePath('block.json'), + this.destinationPath(path.join(pathDist, 'block.json')), + data + ); + this.fs.copyTpl( this.templatePath('package.json'), this.destinationPath(path.join(pathDist, 'package.json')), @@ -123,7 +145,8 @@ module.exports = class extends Generator { } }; -function capitalize(str) { +// TODO: Same function is located in "/helpers.js" file. Find a way to remove cjs files. +export function capitalize(str) { if (typeof str !== 'string') { return ''; } diff --git a/generators/block/templates/block.json b/generators/block/templates/block.json new file mode 100644 index 0000000..5adaaad --- /dev/null +++ b/generators/block/templates/block.json @@ -0,0 +1,34 @@ +{ + "name": "<%= blockGroupName %>/<%= blockFilename %>", + "version": "1.0.0", + "title": "<%= title %>", + "categories": [], + "icon": "shortcode", + "preview_image": "", + "field_groups": [ + { + "name": "content", + "label": "Content", + "fields": { + "title": { + "type": "text", + "label": "Title" + }, + "content": { + "type": "wysiwyg", + "label": "Content" + }, + "cta": { + "type": "link", + "label": "CTA" + } + } + }, + { + "name": "styling", + "label": "Styling", + "fields": { + } + } + ] +} diff --git a/helpers.js b/helpers.js new file mode 100644 index 0000000..a2419f3 --- /dev/null +++ b/helpers.js @@ -0,0 +1,15 @@ +// TODO: Same function is located in "/generators/block/index.cjs" file. Find a way to remove cjs files. +export function capitalize(str) { + if (typeof str !== 'string') { + return ''; + } + + return str + .toLowerCase() + .split(/[ -_]/g) + .filter((word) => !!word) + .map((word) => { + return word.charAt(0).toUpperCase() + word.slice(1); + }) + .join(' '); +} diff --git a/platforms/hubspot/hubspot-adapter.js b/platforms/hubspot/hubspot-adapter.js new file mode 100644 index 0000000..360d38c --- /dev/null +++ b/platforms/hubspot/hubspot-adapter.js @@ -0,0 +1,226 @@ +import {readFile, writeFile, mkdir, copyFile} from "fs/promises"; +import {capitalize} from "../../helpers.js"; + +export async function buildHubspot(blockName) { + const distPath = `./exports/hubspot/${blockName}.module`; + await mkdir(distPath, {recursive: true}) + await copyFile(`./src/${blockName}.template.hbs`, `${distPath}/module.html`) + + const metaData = { + global: false, + host_template_types: ["EMAIL"], + label: capitalize(blockName), + is_available_for_new_content: true + } + + await writeFile(`${distPath}/meta.json`, JSON.stringify(metaData, null, 4)); + + const blockJSON = await readFile(`./block.json`, "utf8"); + const block = JSON.parse(blockJSON); + + const fields = getBlockFields(block, 'content'); + + // Styling TAB. + const stylingFields = getBlockFields(block, 'styling'); + + if (stylingFields.length) { + const stylingFieldsByName = {}; + stylingFields.forEach(field => stylingFieldsByName[field.name] = field); + + const stylingGroup = convertToHubspotField({ + type: 'group', + name: 'style', + label: "Style", + sub_fields: stylingFieldsByName, + }); + stylingGroup.tab = "STYLE"; + + fields.push(stylingGroup); + } + + // Export JSON file. + await writeFile(`${distPath}/fields.json`, JSON.stringify(fields, null, 4)); +} + +function getBlockFields(block = {}, type = 'content') { + const fields_group = block['field_groups'].find((group) => group.name === type); + const fields = []; + + if (!fields_group) { + return fields; + } + + Object.keys(fields_group['fields']).forEach((key) => { + const field = fields_group['fields'][key]; + field['name'] = key; + + fields.push(field); + }); + + return fields.map((field) => { + return convertToHubspotField(field); + }); +} + +function convertToHubspotField(field = {}) { + const data = { + id: field.name, + name: field.name, + label: field.label, + display_width: null, + validation_regex: "", + required: false, + locked: false, + default: field.default + }; + + let sub_fields = []; + + switch (field.type) { + case 'text': + return Object.assign({}, data, { + type: "text", + allow_new_line: true, + show_emoji_picker: false, + }); + case 'wysiwyg': + return Object.assign({}, data, { + type: "richtext" + }); + case 'number': + return Object.assign({}, data, { + type: "number", + display: "text", + step: 1, + }); + case 'range': + return Object.assign({}, data, { + type: "number", + display: "slider", + min: 0, + max: 100, + step: 3, + }); + case 'boolean': + return Object.assign({}, data, { + type: "boolean", + display: "toggle", + }); + case 'checkbox': + return Object.assign({}, data, { + type: "boolean", + display: "checkbox", + }); + case 'select': + const choices = []; + Object.keys(data.choices).forEach(value => choices.push([value, data.choices[value]])); + + return Object.assign({}, data, { + type: "select", + choices: [choices] + }); + case 'link': + return Object.assign({}, data, { + type: "url", + supported_types: [ + "EXTERNAL" + ], + default: { + content_id: null, + href: "https://www.twitter.com/...", + type: "EXTERNAL" + } + }); + case 'image': + return Object.assign({}, data, { + type: "image", + responsive: true, + resizable: false, + show_loading: false, + default: { + src: "", + alt: null, + loading: "lazy" + } + }); + case 'file': + return Object.assign({}, data, { + type: "file", + picker: "file", + }); + case 'stringList': + return Object.assign({}, data, { + type: "text", + occurrence: { + min: null, + max: null, + sorting_label_field: null, + default: null, + }, + allow_new_line: false, + show_emoji_picker: false, + }); + case 'gallery': + return Object.assign({}, data, { + type: "image", + occurrence: { + min: null, + max: null, + sorting_label_field: null, + default: null + }, + responsive: true, + resizable: false, + show_loading: false, + default: { + src: "", + alt: null, + loading: "lazy" + } + }); + case 'group': + field.sub_fields = field.sub_fields || {}; + sub_fields = Object.keys(field.sub_fields).map(name => { + const sub_field = Object.assign({}, field.sub_fields[name], {name: `${field.name}_${name}`}); + return convertToHubspotField(sub_field); + }) + + return Object.assign({}, data, { + type: "group", + children: sub_fields, + tab: "CONTENT", + expanded: false, + default: {} + }); + case 'repeater': + field.sub_fields = field.sub_fields || {}; + sub_fields = Object.keys(field.sub_fields).map(name => { + const sub_field = Object.assign({}, field.sub_fields[name], {name: `${field.name}_${name}`}); + return convertToHubspotField(sub_field); + }) + + return Object.assign({}, data, { + type: "group", + children: sub_fields, + occurrence: { + min: null, + max: null, + sorting_label_field: null, + default: null, + }, + tab: "CONTENT", + expanded: false, + default: {} + }); + + // case 'YOUR_FIELD': + // return Object.assign({}, data, {}); + default: + // type === 'string' + return Object.assign({}, data, { + type: "text", + allow_new_line: false, + show_emoji_picker: true, + }); + } +}