Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70d89f567a | |||
| ab0fe727b3 | |||
| 40c67a0bd8 | |||
| e735f2d141 | |||
| 804c504c06 | |||
| 50501b05c6 | |||
| 5fcdc36b09 | |||
| 2a5daa2445 | |||
| e184c3483a | |||
| ee197d24c4 | |||
| 30a3e964bd | |||
| 93c39981f9 | |||
| 019e51ab1c | |||
| 760825be2d | |||
| 6b3ed38e04 | |||
| 190800cc80 | |||
| 33dbe48a2c | |||
| c7e492ae8a | |||
| ae68a88fd1 | |||
| 7b7a8c101f | |||
| 3aa5370c2b | |||
| 92e4ca76f4 | |||
| 2917cb7332 | |||
| 0d4baf6b11 | |||
| 50d7117609 | |||
| 28ecfa281c | |||
| 9173efe9c9 | |||
| a7e51803cf | |||
| de8d78ea31 | |||
| 29734a6b94 | |||
| d791edda3e | |||
| 0ea8f8b443 | |||
| 718e6b32a9 |
@@ -10,3 +10,8 @@ deploy-*.sh
|
||||
|
||||
# Custom
|
||||
blocks
|
||||
config
|
||||
data
|
||||
src
|
||||
exports
|
||||
block.json
|
||||
|
||||
@@ -20,3 +20,11 @@ environment.
|
||||
|
||||
Generated blocks are including this repository.
|
||||
This project is running a nodejs script with `browsersync` and `gulp` which improves the development process.
|
||||
|
||||
## Development / Testing the tool
|
||||
|
||||
Run `npm run generate-block` command and give the `development` name to the block, it will generate the `/blocks` folder.
|
||||
|
||||
Copy the `/development/data` and `/development/src` folders to root folder of project.
|
||||
Now you're ready to run development process: `npm run dev`.
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
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/';
|
||||
|
||||
const blockName = config.has('blockName') ? config.get('blockName') : 'development';
|
||||
|
||||
class buildGenerator extends Generator {
|
||||
async prompting() {
|
||||
this.data = await this.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "platform",
|
||||
message: "Choose Platform",
|
||||
choices: ['WordPress', 'Hubspot', 'Hubspot Email', 'JavaScript', 'PHP'],
|
||||
default: 'WordPress'
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
writing() {
|
||||
new Promise((resolve => {
|
||||
if (['WordPress', 'PHP'].includes(this.data.platform)) {
|
||||
const backPath = modulePath ? modulePath.substr(-1).split('/').map(() => '..').join('/') : '';
|
||||
exec(`cd ${modulePath}platforms/php && composer install && php build.php '${blockName}' '${backPath}'`, function (error, stdout) {
|
||||
console.log(stdout);
|
||||
resolve();
|
||||
});
|
||||
} else if (this.data.platform === 'Hubspot Email') {
|
||||
buildHubspot(blockName)
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
} else if (this.data.platform === 'Hubspot') {
|
||||
console.log('"Hubspot" Coming soon...');
|
||||
resolve();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}))
|
||||
.then(() => {
|
||||
console.log('--------------------\nDone!');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const build = new buildGenerator([], {env: yeoman.createEnv()}, {});
|
||||
build.run().then(() => null);
|
||||
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import {Command} from 'commander';
|
||||
import path from 'path';
|
||||
import fetch from "node-fetch";
|
||||
import fs from "fs";
|
||||
import http from "http";
|
||||
import https from "https";
|
||||
import unzipper from "unzipper";
|
||||
import {createTechnicalFiles, defaultGitRepo} from "./generators/block/index.js";
|
||||
import {fileURLToPath} from 'url';
|
||||
import config from 'config';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development' || (config.has('isDev') && config.get('isDev')); // Check README file in case you get "missing files" error.
|
||||
const blocksRegistry = isDev ? 'http://localhost:3020' : 'https://axe-web-blocks-registry.captain.devdevdev.life';
|
||||
const blocksDirectory = isDev ? 'blocks/' : '';
|
||||
|
||||
try {
|
||||
const blockName = await init();
|
||||
console.log(`🎉 Done! \n\nCheck the "${blocksDirectory}${blockName}" directory. \n`);
|
||||
} catch (e) {
|
||||
console.log('Fail.');
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('create-block')
|
||||
.description('AXE-WEB Platform Blocks');
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
program.command('pull')
|
||||
.argument('<string>', 'Provide a full name of required block, for example: @axe-web/hero-block')
|
||||
.action(async (blockName, options) => {
|
||||
console.log('📦 Block to download:', blockName)
|
||||
|
||||
const status = await getBlockSourceFiles(blockName);
|
||||
if (status) {
|
||||
resolve(blockName);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
program.parse();
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function getBlockSourceFiles(blockName) {
|
||||
const queryString = new URLSearchParams();
|
||||
queryString.append('blockName', blockName);
|
||||
queryString.append('includeDevConfig', 'true');
|
||||
|
||||
const response = await fetch(`${blocksRegistry}?${queryString.toString()}`);
|
||||
const responseData = await response.json();
|
||||
if (!responseData || !responseData.name) {
|
||||
console.log("⚠️ Block not found, please contact administrator.");
|
||||
return;
|
||||
}
|
||||
|
||||
const zipFile = await downloadFile(responseData.downloadUrl, responseData.name + '.zip');
|
||||
await fs.createReadStream(zipFile)
|
||||
.pipe(unzipper.Extract({path: `${blocksDirectory}${responseData.name}/src`}))
|
||||
.promise();
|
||||
|
||||
// Remove downloaded file.
|
||||
try {
|
||||
await fs.promises.access(zipFile, fs.constants.W_OK);
|
||||
await fs.promises.unlink(zipFile);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
if (responseData.statusCode && responseData.statusCode !== 200) {
|
||||
console.log(responseData);
|
||||
console.log("⚠️ [ERROR]", responseData.message || "Server side error.");
|
||||
return;
|
||||
}
|
||||
|
||||
await createTechnicalFiles({
|
||||
title: responseData.title,
|
||||
name: responseData.name,
|
||||
blockFilename: responseData.name,
|
||||
blockGroupName: responseData.project,
|
||||
version: responseData.version,
|
||||
devToolSource: defaultGitRepo,
|
||||
}, __dirname, `${blocksDirectory}${responseData.name}`);
|
||||
|
||||
if (responseData.config.design_files) {
|
||||
await downloadDesignFiles(responseData.name, responseData.config.design_files);
|
||||
}
|
||||
|
||||
await createDataFiles(responseData.name, responseData.config.data_sources);
|
||||
|
||||
await createConfigFile(responseData.name, responseData.config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function createConfigFile(blockName, config = {}) {
|
||||
const obj = {
|
||||
blockName,
|
||||
baseView: config.baseView,
|
||||
remToPx: config.remToPx,
|
||||
};
|
||||
|
||||
const cssAssets = config.assets.filter(item => item.type === 'css').map(item => item.url);
|
||||
if (cssAssets.length) {
|
||||
obj.cssUrl = cssAssets;
|
||||
}
|
||||
|
||||
const jsAssets = config.assets.filter(item => item.type === 'js').map(item => item.url);
|
||||
if (jsAssets.length) {
|
||||
obj.jsUrl = jsAssets;
|
||||
}
|
||||
|
||||
await setupPath(`${blocksDirectory}${blockName}/config`);
|
||||
await fs.promises.writeFile(`${blocksDirectory}${blockName}/config/default.json`, JSON.stringify(obj, null, 2));
|
||||
}
|
||||
|
||||
async function downloadDesignFiles(blockName = '', files) {
|
||||
for (let url of files) {
|
||||
const fileName = url.split('/').pop();
|
||||
await setupPath(`${blocksDirectory}${blockName}/design`);
|
||||
await downloadFile(url, `${blocksDirectory}${blockName}/design/${fileName}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function createDataFiles(blockName, dataSources = []) {
|
||||
for (let source of dataSources) {
|
||||
await setupPath(`${blocksDirectory}${blockName}/data`);
|
||||
|
||||
await fs.promises.writeFile(`${blocksDirectory}${blockName}/data/${source.name}.json`, JSON.stringify(source.data, null, 2));
|
||||
|
||||
if (typeof source.preview_images !== 'undefined' && source.preview_images.length) {
|
||||
for (let preview_image of source.preview_images) {
|
||||
await setupPath(`${blocksDirectory}${blockName}/design`);
|
||||
await downloadFile(preview_image.url, `${blocksDirectory}${blockName}/design/${source.name}-${preview_image.width}.${preview_image.extension}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
async function setupPath(path) {
|
||||
if (!fs.existsSync(path)) {
|
||||
await fs.promises.mkdir(path);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFile(url, fileName) {
|
||||
const file = fs.createWriteStream(fileName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocol = url.startsWith('https://') ? https : http;
|
||||
protocol.get(url, function (response) {
|
||||
response.pipe(file);
|
||||
|
||||
// Loading Indicator.
|
||||
const loadingInterval = setInterval(() => console.log('🕐 Download in progress...'), 3000);
|
||||
|
||||
// after download completed close filestream
|
||||
file.on("finish", () => {
|
||||
clearInterval(loadingInterval);
|
||||
|
||||
file.close();
|
||||
resolve(fileName);
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
const path = require('path');
|
||||
const Generator = require('yeoman-generator');
|
||||
// const exec = require('child_process').exec;
|
||||
|
||||
const baseDir = path.join(__dirname, '../../');
|
||||
|
||||
module.exports = class extends Generator {
|
||||
async prompting() {
|
||||
this.data = await this.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "Block Name",
|
||||
validate: (str) => {
|
||||
const matches = str.match(/\d+/g);
|
||||
|
||||
if (matches != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!str;
|
||||
}
|
||||
},
|
||||
// {
|
||||
// type: "input",
|
||||
// name: "name",
|
||||
// message: "Project ID"
|
||||
// },
|
||||
{
|
||||
type: "list",
|
||||
name: "baseView",
|
||||
message: "View Template",
|
||||
default: 'container',
|
||||
choices: ['container', 'alignfull'],
|
||||
},
|
||||
// {
|
||||
// type: "confirm",
|
||||
// name: "include_script",
|
||||
// default: false,
|
||||
// message: "Include script.js File?"
|
||||
// },
|
||||
]);
|
||||
}
|
||||
|
||||
writing() {
|
||||
const title = capitalize(this.data.name);
|
||||
const data = Object.assign(this.data, {
|
||||
title,
|
||||
blockFilename: title.toLowerCase().replace(/ /ig, '-'),
|
||||
blockClassName: title.toLowerCase().replace(/ /ig, '_'),
|
||||
});
|
||||
|
||||
const pathDist = path.join(baseDir, 'blocks', data.blockFilename);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/template.template.hbs'),
|
||||
this.destinationPath(path.join(pathDist, 'src', data.blockFilename + '.template.hbs')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/styles/template.scss'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'styles', data.blockFilename + '.scss')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/scripts/template.mjs'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'scripts', data.blockFilename + '.mjs')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/images/demo.jpeg'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'images', 'demo.jpeg')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('config/default.cjs'),
|
||||
this.destinationPath(path.join(pathDist, 'config', 'default.cjs')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('package.json'),
|
||||
this.destinationPath(path.join(pathDist, 'package.json')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('data/default.json'),
|
||||
this.destinationPath(path.join(pathDist, 'data', 'default.json')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('data/advanced.json'),
|
||||
this.destinationPath(path.join(pathDist, 'data', 'advanced.json')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('README.md'),
|
||||
this.destinationPath(path.join(pathDist, 'README.md')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('.editorconfig'),
|
||||
this.destinationPath(path.join(pathDist, '.editorconfig')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('.gitignore'),
|
||||
this.destinationPath(path.join(pathDist, '.gitignore')),
|
||||
data
|
||||
);
|
||||
|
||||
// Run BUILD script
|
||||
// var cmd = exec("npm run build", function (err, stdout, stderr) {
|
||||
// if (err) {
|
||||
// console.log('Issue with running - "npm run build"\n\n', err);
|
||||
// }
|
||||
// });
|
||||
|
||||
// console.log(`\n\nDon't forget to connect the Component in your functions.php file ;)\n\n`);
|
||||
}
|
||||
};
|
||||
|
||||
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(' ');
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import path from 'path';
|
||||
import Generator from "yeoman-generator";
|
||||
import mkdirp from "mkdirp";
|
||||
import {fileURLToPath} from 'url';
|
||||
import {capitalize} from "../../helpers.js";
|
||||
import memFs from 'mem-fs';
|
||||
import editor from 'mem-fs-editor';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const baseDir = path.join(__dirname, '../../');
|
||||
|
||||
export const defaultGitRepo = 'git+https://git.devdevdev.life/axe-web-public/create-block.git#master';
|
||||
|
||||
export default class extends Generator {
|
||||
async prompting() {
|
||||
this.data = await this.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "Block Name",
|
||||
validate: (str) => {
|
||||
const matches = str.match(/\d+/g);
|
||||
|
||||
if (matches != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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",
|
||||
message: "View Template",
|
||||
default: 'container',
|
||||
choices: ['container', 'alignfull'],
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "remToPx",
|
||||
message: "Provide declaration of 1rem:",
|
||||
default: 16
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'devToolSource',
|
||||
message: 'DevTool Version/Source (master)',
|
||||
default: defaultGitRepo
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
writing() {
|
||||
const title = capitalize(this.data.name);
|
||||
const group = capitalize(this.data.group);
|
||||
const data = Object.assign(this.data, {
|
||||
title,
|
||||
version: '1.0.0',
|
||||
blockFilename: title.toLowerCase().replace(/ /ig, '-'),
|
||||
blockGroupName: group.toLowerCase().replace(/ /ig, '-'),
|
||||
blockClassName: title.toLowerCase().replace(/ /ig, '_'),
|
||||
});
|
||||
|
||||
const pathDist = path.join(baseDir, 'blocks', data.blockFilename);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('config/default.cjs'),
|
||||
this.destinationPath(path.join(pathDist, 'config', 'default.cjs')),
|
||||
data
|
||||
);
|
||||
|
||||
|
||||
// SRC Template files
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/template.template.hbs'),
|
||||
this.destinationPath(path.join(pathDist, 'src', data.blockFilename + '.template.hbs')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/styles/template.scss'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'styles', data.blockFilename + '.scss')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/scripts/template.js'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'scripts', data.blockFilename + '.js')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/images/demo.jpeg'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'images', 'demo.jpeg')),
|
||||
data
|
||||
);
|
||||
|
||||
// Design Directory
|
||||
mkdirp.sync(path.join(pathDist, 'design'));
|
||||
|
||||
// Data Files
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('data/default.json'),
|
||||
this.destinationPath(path.join(pathDist, 'data', 'default.json')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('data/advanced.json'),
|
||||
this.destinationPath(path.join(pathDist, 'data', 'advanced.json')),
|
||||
data
|
||||
);
|
||||
|
||||
// Technical Project files.
|
||||
createTechnicalFiles(data, baseDir, `blocks/${data.name}`).then();
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTechnicalFiles(data, baseDir, distPath) {
|
||||
const pathDist = distPath; //path.join(baseDir, distPath);
|
||||
const generatorsPath = path.join(baseDir, 'generators/block/templates');
|
||||
|
||||
const store = memFs.create();
|
||||
const filesystem = editor.create(store);
|
||||
|
||||
const files = ['package.json', 'README.md', '.editorconfig', {from: 'gitignore', to: '.gitignore'}, 'block.json'];
|
||||
|
||||
for (let file of files) {
|
||||
const from = typeof file !== 'string' ? `${generatorsPath}/${file.from}` : `${generatorsPath}/${file}`;
|
||||
const to = typeof file !== 'string' ? `${pathDist}/${file.to}` : `${pathDist}/${file}`;
|
||||
|
||||
await filesystem.copyTplAsync(from, to, data);
|
||||
}
|
||||
|
||||
return filesystem.commit(); // Promise
|
||||
}
|
||||
@@ -116,6 +116,18 @@ Check the [Video Guide](https://www.loom.com/share/1c707a4ea14e48b7a35a49d7b0a6f
|
||||
|
||||
Use development toolbar to switch between data sources.
|
||||
|
||||
### Conditional Statements
|
||||
Make sure the data is available in Handlebars template.
|
||||
If there is a chance that some data/property will be missing - wrap the HTML layout with conditional verification:
|
||||
|
||||
Example:
|
||||
Render the link only if URL exists
|
||||
```
|
||||
{{#if link_cta_url }}
|
||||
<a href="{{link_cta_url}}">Click Here</a>
|
||||
{{ endif }}
|
||||
```
|
||||
|
||||
### Hover/Focus States ✔️
|
||||
|
||||
Make sure all "clickable" elements have `&:hover` & `&:focus` states in CSS rules.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "<%= blockGroupName %>/<%= blockFilename %>",
|
||||
"version": "<%= version %>",
|
||||
"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": {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
module.exports = {
|
||||
cssUrl: "https://",
|
||||
jsUrl: "https://",
|
||||
blockName: "<%= blockFilename %>",
|
||||
baseView: "<%= baseView %>",
|
||||
remToPx: <%= remToPx %>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"title": "Option Two",
|
||||
"content": "<p><b>Delivering top results to industry leaders:</b></p>",
|
||||
"image_url": "https://fakeimg.pl/400x400/",
|
||||
"cta_text": "Our Success Stories",
|
||||
"url": "https://google.com"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"title": "As a Global Salesforce Partner,\n we deliver Service Cloud solutions\n and complex Field Service Management\n in diverse industries worldwide",
|
||||
"content": "<p><b>Delivering top results to industry leaders:</b></p>",
|
||||
"image_url": "https://fakeimg.pl/400x400/",
|
||||
"cta_text": "Our Success Stories",
|
||||
"url": "https://google.com"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Basic
|
||||
.idea
|
||||
.DS_Store
|
||||
node_modules
|
||||
vendor
|
||||
|
||||
# Custom
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "<%= blockFilename %>",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "component-dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"create-block-dev-tool": "git+https://roman-axe-web@bitbucket.org/axeweb/create-block-dev-tool.git#master"
|
||||
}
|
||||
"name": "<%= blockFilename %>",
|
||||
"version": "<%= version %>",
|
||||
"scripts": {
|
||||
"start": "component-dev",
|
||||
"build": "component-build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"create-block-dev-tool": "<%= devToolSource %>"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Use "rem" instead of pixels. 1rem = 16pixels
|
||||
* Use "rem" instead of pixels. 1rem = <%= remToPx %>pixels
|
||||
* No need to use rem in situations of "1px". (usually used for borders width).
|
||||
*
|
||||
* Use BEM naming system for class names: http://getbem.com/naming/
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
&__visual {
|
||||
&-image {
|
||||
// Example of BEM usage.
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<section class="<%= blockClassName %>">
|
||||
<section class="<%= blockClassName %> <%= blockClassName %>--{{ config.activeDataFile }}">
|
||||
|
||||
{{!
|
||||
Remove Everything Below:
|
||||
@@ -13,6 +13,7 @@
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,10 +33,9 @@
|
||||
|
||||
<h2>Image Example</h2>
|
||||
<div>
|
||||
<img src="images/demo.jpeg" alt="">
|
||||
<img src="/images/demo.jpeg" alt="">
|
||||
<br>
|
||||
<code>
|
||||
<img src="images/demo.jpeg" alt="">
|
||||
<code><img src="/images/demo.jpeg" alt="">
|
||||
</code>
|
||||
</div>
|
||||
|
||||
@@ -47,8 +47,7 @@
|
||||
<div>
|
||||
<a href="#" class="btn btn--primary">Primary</a>
|
||||
<br>
|
||||
<code>
|
||||
<a href="#" class="btn btn--primary">Primary</a>
|
||||
<code><a href="#" class="btn btn--primary">Primary</a>
|
||||
</code>
|
||||
</div>
|
||||
|
||||
@@ -57,8 +56,7 @@
|
||||
<div>
|
||||
<a href="#" class="btn btn--secondary">Secondary</a>
|
||||
<br>
|
||||
<code>
|
||||
<a href="#" class="btn btn--secondary">Secondary</a>
|
||||
<code><a href="#" class="btn btn--secondary">Secondary</a>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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(' ');
|
||||
}
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
<body>
|
||||
|
||||
{{> (include_partial "layouts/partials/toolbar") }}
|
||||
|
||||
<main>
|
||||
{{> (include_block_template) }}
|
||||
</main>
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
<body>
|
||||
|
||||
{{> (include_partial "layouts/partials/toolbar") }}
|
||||
|
||||
<main>
|
||||
<div class="container">
|
||||
{{> (include_block_template) }}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/styles/page--main.css">
|
||||
<title>Block Development Tool</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{{> (include_partial "layouts/partials/toolbar") }}
|
||||
|
||||
<script>
|
||||
window.devTool = {
|
||||
previewFrameUrl: 'http://localhost:{{ port }}/view/{{ baseView }}',
|
||||
};
|
||||
</script>
|
||||
<iframe id="preview_frame" src="http://localhost:{{ port }}/view/{{ baseView }}" class="breakpoint"></iframe>
|
||||
|
||||
<script src="/scripts/dist/index.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,8 +2,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="{{ config.cssUrl }}">
|
||||
<link rel="stylesheet" href="styles/page--main.css">
|
||||
<link rel="stylesheet" href="styles/{{ config.blockName }}.min.css">
|
||||
{{#if config.cssUrl }}
|
||||
{{#each config.cssUrl }}
|
||||
<link rel="stylesheet" href="{{ this }}">
|
||||
{{/each}}
|
||||
{{/if}}<link rel="stylesheet" href="/styles/page--view.css">{{# if config.blockName}}
|
||||
<link rel="stylesheet" href="/styles/{{ config.blockName }}.min.css">{{/if}}
|
||||
<link rel="stylesheet" href="https://unpkg.com/swiper@8/swiper-bundle.min.css"/>
|
||||
</head>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
<script src="https://unpkg.com/swiper@8/swiper-bundle.min.js"></script>
|
||||
<script src="scripts/page--toolbar.js"></script>
|
||||
<script src="scripts/{{ config.blockName }}.min.js"></script>
|
||||
<script src="https://unpkg.com/swiper@8/swiper-bundle.min.js"></script>{{#if config.jsUrl }}
|
||||
{{#each config.jsUrl }}<script src="{{ this }}"></script>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{#if config.blockName }}
|
||||
<script src="/scripts/{{ config.blockName }}.min.js"></script>
|
||||
{{/if}}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
<header class="page_toolbar">
|
||||
<div class="page_toolbar__data_options">
|
||||
<label for="data-options">Data Options: </label>
|
||||
|
||||
<select name="data" id="data-options">
|
||||
{{#each config.dataFiles }}
|
||||
<option value="{{ name }}" {{#if active }}selected="selected"{{/if}}>{{ name }}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<div class="page_toolbar__left">#</div>
|
||||
|
||||
<div class="page_toolbar__middle">
|
||||
<div class="page_toolbar__data_options">
|
||||
<label for="data-options">Data Options: </label>
|
||||
|
||||
<select name="data" id="data-options">
|
||||
{{#each config.dataFiles }}
|
||||
<option value="{{ name }}" {{#if active }}selected="selected"{{/if}}>{{ name }}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Sizes: <b>1rem = {{ config.remToPx }}px</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Sizes: <b>1rem = {{ config.remToPx }}px</b>
|
||||
</div>
|
||||
<div class="page_toolbar__right"></div>
|
||||
</header>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M15 20H9" stroke="url(#paint0_linear_17102_17357)" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<path
|
||||
d="M19 5H5C4.44772 5 4 5.44772 4 6V16C4 16.5523 4.44772 17 5 17H19C19.5523 17 20 16.5523 20 16V6C20 5.44772 19.5523 5 19 5Z"
|
||||
stroke="url(#paint1_linear_17102_17357)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17102_17357" x1="9" y1="21" x2="11.3077" y2="17.5385"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17102_17357" x1="4" y1="17" x2="22" y2="11" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 970 B |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M16 3H8C7.44772 3 7 3.44772 7 4V20C7 20.5523 7.44772 21 8 21H16C16.5523 21 17 20.5523 17 20V4C17 3.44772 16.5523 3 16 3Z"
|
||||
stroke="url(#paint0_linear_17102_17362)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 18H12.002V18.002H12V18Z" stroke="url(#paint1_linear_17102_17362)" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17102_17362" x1="7" y1="21" x2="19.2634" y2="19.2967"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17102_17362" x1="12" y1="18.002" x2="12.0024" y2="18.0014"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48">
|
||||
<path
|
||||
d="M22.55 42.65q-6.45-.55-10.85-5.3Q7.3 32.6 7.3 26.1q0-3.95 1.775-7.4t4.975-5.75l2.8 2.85q-2.6 1.6-4.1 4.375-1.5 2.775-1.5 5.925 0 4.9 3.225 8.45 3.225 3.55 8.075 4.1Zm3 0v-4q4.9-.6 8.075-4.125Q36.8 31 36.8 26.1q0-5.2-3.55-8.875T24.5 13.35h-1.1l3.15 3.15-2.4 2.4-7.65-7.65 7.65-7.7 2.4 2.4-3.45 3.4h1.1q6.95 0 11.775 4.9T40.8 26.1q0 6.5-4.4 11.25t-10.85 5.3Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 446 B |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M20 3H4C3.44772 3 3 3.44772 3 4V20C3 20.5523 3.44772 21 4 21H20C20.5523 21 21 20.5523 21 20V4C21 3.44772 20.5523 3 20 3Z"
|
||||
stroke="url(#paint0_linear_17102_17360)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 18H12.002V18.002H12V18Z" stroke="url(#paint1_linear_17102_17360)" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17102_17360" x1="3" y1="21" x2="24.1765" y2="15.7059"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17102_17360" x1="12" y1="18.002" x2="12.0024" y2="18.0014"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
import {setupResponsiveness} from './toolbar/responsive.jsx';
|
||||
import {setupPublish} from "./toolbar/publish.jsx";
|
||||
|
||||
const rootAttributes = {
|
||||
previewFrame: document.getElementById('preview_frame'),
|
||||
}
|
||||
|
||||
setupResponsiveness(rootAttributes);
|
||||
setupPublish(rootAttributes)
|
||||
|
||||
// const responsiveness = connectResponsiveness(rootAttributes);
|
||||
// setTimeout(() => responsiveness.selectMode('tablet'), 5000)
|
||||
// setTimeout(() => responsiveness.selectMode('mobile'), 10000)
|
||||
|
||||
const previewFrame = rootAttributes.previewFrame;
|
||||
initDataOptions();
|
||||
|
||||
/**
|
||||
* Functions
|
||||
*/
|
||||
|
||||
function initDataOptions() {
|
||||
const dataOptionsSelect = document.getElementById('data-options');
|
||||
if (!dataOptionsSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataOptionsSelect.addEventListener('change', function () {
|
||||
previewFrame.src = window.devTool.previewFrameUrl + '?data=' + this.value;
|
||||
});
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
(function () {
|
||||
|
||||
const dataOptionsSelect = document.getElementById('data-options');
|
||||
if (!dataOptionsSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataOptionsSelect.addEventListener('change', function () {
|
||||
console.log(this.value)
|
||||
window.location = '?data=' + this.value;
|
||||
})
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M15 20H9" stroke="url(#paint0_linear_17102_17357)" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<path
|
||||
d="M19 5H5C4.44772 5 4 5.44772 4 6V16C4 16.5523 4.44772 17 5 17H19C19.5523 17 20 16.5523 20 16V6C20 5.44772 19.5523 5 19 5Z"
|
||||
stroke="url(#paint1_linear_17102_17357)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17102_17357" x1="9" y1="21" x2="11.3077" y2="17.5385"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17102_17357" x1="4" y1="17" x2="22" y2="11" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 970 B |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M16 3H8C7.44772 3 7 3.44772 7 4V20C7 20.5523 7.44772 21 8 21H16C16.5523 21 17 20.5523 17 20V4C17 3.44772 16.5523 3 16 3Z"
|
||||
stroke="url(#paint0_linear_17102_17362)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 18H12.002V18.002H12V18Z" stroke="url(#paint1_linear_17102_17362)" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17102_17362" x1="7" y1="21" x2="19.2634" y2="19.2967"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17102_17362" x1="12" y1="18.002" x2="12.0024" y2="18.0014"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48">
|
||||
<path
|
||||
d="M22.55 42.65q-6.45-.55-10.85-5.3Q7.3 32.6 7.3 26.1q0-3.95 1.775-7.4t4.975-5.75l2.8 2.85q-2.6 1.6-4.1 4.375-1.5 2.775-1.5 5.925 0 4.9 3.225 8.45 3.225 3.55 8.075 4.1Zm3 0v-4q4.9-.6 8.075-4.125Q36.8 31 36.8 26.1q0-5.2-3.55-8.875T24.5 13.35h-1.1l3.15 3.15-2.4 2.4-7.65-7.65 7.65-7.7 2.4 2.4-3.45 3.4h1.1q6.95 0 11.775 4.9T40.8 26.1q0 6.5-4.4 11.25t-10.85 5.3Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 446 B |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M20 3H4C3.44772 3 3 3.44772 3 4V20C3 20.5523 3.44772 21 4 21H20C20.5523 21 21 20.5523 21 20V4C21 3.44772 20.5523 3 20 3Z"
|
||||
stroke="url(#paint0_linear_17102_17360)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 18H12.002V18.002H12V18Z" stroke="url(#paint1_linear_17102_17360)" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17102_17360" x1="3" y1="21" x2="24.1765" y2="15.7059"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17102_17360" x1="12" y1="18.002" x2="12.0024" y2="18.0014"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14181F"/>
|
||||
<stop offset="1" stop-color="#272A31"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,47 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
|
||||
function Publish(props = {}) {
|
||||
const [state, setState] = useState({loading: false});
|
||||
const updateState = (update) => setState(Object.assign({}, state, update));
|
||||
|
||||
return <div>
|
||||
{state.loading &&
|
||||
<div className="overlay overlay--loading">Loading, Please wait...</div>
|
||||
}
|
||||
<button onClick={submit} disabled={state.loading} className="btn btn--primary">Publish</button>
|
||||
</div>;
|
||||
|
||||
async function submit() {
|
||||
const ready = confirm('Are you ready to submit the code?');
|
||||
if (!ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateState({loading: true});
|
||||
|
||||
try {
|
||||
const response = await fetch(`/publish`);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
alert('Your code is successfully sent to project manager! Thank you!');
|
||||
} else {
|
||||
alert('Can\'t send your code, please try again or contact project manager.');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Something went wrong, please try again or contact project manager.');
|
||||
}
|
||||
|
||||
updateState({loading: false});
|
||||
}
|
||||
}
|
||||
|
||||
export function setupPublish(rootAttributes) {
|
||||
// INIT
|
||||
const wrapper = document.createElement('div');
|
||||
document.querySelector('.page_toolbar__right').prepend(wrapper)
|
||||
|
||||
const root = ReactDOM.createRoot(wrapper);
|
||||
const html = (<Publish rootAttributes={rootAttributes}/>);
|
||||
root.render(html);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {ButtonStyling, ResponsiveButtonStyle, ResponsiveOptionsDropdown} from "./responsive-button.style.js";
|
||||
|
||||
const responsiveOptions = {
|
||||
desktop: [1920, 1800, 1680, 1440, 1360, 1280, 1024],
|
||||
tablet: [992, 768, 600],
|
||||
mobile: [480, 414, 375, 360],
|
||||
}
|
||||
|
||||
const defaultResponsiveOptions = {
|
||||
reset: '100%',
|
||||
desktop: responsiveOptions.desktop[0],
|
||||
tablet: responsiveOptions.tablet[1],
|
||||
mobile: responsiveOptions.mobile[2],
|
||||
}
|
||||
|
||||
export function ResponsiveButton({mode, active, onSelect}) {
|
||||
const [state, setState] = useState({open: true, activeBreakpoint: defaultResponsiveOptions[mode]});
|
||||
const updateState = (update) => setState(Object.assign({}, state, update));
|
||||
|
||||
useEffect(() => {
|
||||
const closeDropdown = (e) => isEscHit(e) ? updateState({open: false}) : null;
|
||||
|
||||
if (state.open) {
|
||||
document.addEventListener("keydown", closeDropdown);
|
||||
}
|
||||
|
||||
// Unsubscribe from ESC listener.
|
||||
return () => {
|
||||
document.removeEventListener("keydown", closeDropdown);
|
||||
}
|
||||
});
|
||||
|
||||
// Blur event / Outside click
|
||||
const handleBlur = async (e) => await isClickOutside(e) ? updateState({open: false}) : null;
|
||||
|
||||
return <ResponsiveButtonStyle tabIndex='0' onBlur={handleBlur}>
|
||||
<ButtonStyling data-mode={mode} data-active={active} onClick={() => select()}>{mode}</ButtonStyling>
|
||||
{active && state.open && responsiveOptions[mode] &&
|
||||
<ResponsiveOptionsDropdown>
|
||||
{responsiveOptions[mode].map((breakpoint) => {
|
||||
return <li>
|
||||
<a className={state.activeBreakpoint === breakpoint ? 'active' : ''} onClick={() => select(breakpoint)}>{breakpoint}</a>
|
||||
</li>;
|
||||
})}
|
||||
</ResponsiveOptionsDropdown>
|
||||
}
|
||||
</ResponsiveButtonStyle>;
|
||||
|
||||
//
|
||||
// Actions
|
||||
//
|
||||
|
||||
function select(activeBreakpoint = null) {
|
||||
// Click on option in Dropdown.
|
||||
if (activeBreakpoint) {
|
||||
updateState({open: false, activeBreakpoint});
|
||||
onSelect(activeBreakpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
// Click on device button.
|
||||
if (!active) {
|
||||
onSelect(state.activeBreakpoint)
|
||||
updateState({open: false});
|
||||
} else {
|
||||
updateState({open: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function update(state, update) {
|
||||
return Object.assign({}, state, update);
|
||||
}
|
||||
|
||||
export async function isClickOutside(e) {
|
||||
const currentTarget = e.currentTarget;
|
||||
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => resolve(!currentTarget.contains(document.activeElement)));
|
||||
})
|
||||
}
|
||||
|
||||
export function isEscHit(event) {
|
||||
return event.key === 'Escape'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
export const ButtonStyling = styled.button`
|
||||
--size: 1.5rem;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-desktop.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: calc(var(--size) - 0.15rem);
|
||||
background-position: center;
|
||||
background-color: initial;
|
||||
font-size: 1px;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
line-height: 1;
|
||||
display: block;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 0.25rem;
|
||||
outline: none;
|
||||
|
||||
&[data-mode='tablet'] {
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-tablet.svg");
|
||||
}
|
||||
|
||||
&[data-mode='mobile'] {
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-mobile.svg");
|
||||
}
|
||||
|
||||
&[data-mode='reset'] {
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-reset.svg");
|
||||
}
|
||||
|
||||
&[data-active='true'] {
|
||||
background-color: #CBD5E0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ResponsiveOptionsDropdown = styled.ul`
|
||||
list-style: none;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.25);
|
||||
box-shadow: 2px 2px 4px 0 #ccc;
|
||||
border-radius: 4px;
|
||||
transform: translateX(-50%);
|
||||
left: 50%;
|
||||
margin: 0.25rem 0 0;
|
||||
|
||||
li {
|
||||
margin-bottom: 2px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
color: #14181F;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: #EDF2F7;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #cbd5e0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const ResponsiveButtonStyle = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
@@ -0,0 +1,81 @@
|
||||
// export function connectResponsiveness(rootAttributes) {
|
||||
// // API
|
||||
// return {
|
||||
// selectMode: (mode) => selectMode(mode),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
import {WrapperStyling} from "./responsive.style.js";
|
||||
import {ResponsiveButton} from "./responsive-button/ResponsiveButton.jsx";
|
||||
|
||||
const modes = [
|
||||
'reset',
|
||||
'desktop',
|
||||
'tablet',
|
||||
'mobile'
|
||||
];
|
||||
|
||||
function Responsive(props = {}) {
|
||||
props.rootAttributes = props.rootAttributes ?? {};
|
||||
|
||||
const initialState = {mode: 'default', breakpoint: '100%'}
|
||||
|
||||
const [state, setState] = useState(initialState);
|
||||
const updateState = (update) => setState(Object.assign({}, state, update));
|
||||
|
||||
useEffect(() => {
|
||||
// Update the document title using the browser API
|
||||
updateController(state);
|
||||
});
|
||||
|
||||
const previewFrame = props.rootAttributes.previewFrame;
|
||||
return render();
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
function render() {
|
||||
return <WrapperStyling>
|
||||
{modes.map((mode) => {
|
||||
return <ResponsiveButton mode={mode}
|
||||
active={isActive(mode)}
|
||||
onSelect={(breakpoint) => selectMode(mode, breakpoint)}/>
|
||||
})}
|
||||
</WrapperStyling>;
|
||||
}
|
||||
|
||||
function selectMode(mode, breakpoint) {
|
||||
if (mode === 'reset') {
|
||||
updateState(initialState);
|
||||
return;
|
||||
}
|
||||
|
||||
updateState({mode, breakpoint})
|
||||
}
|
||||
|
||||
|
||||
function isActive(mode) {
|
||||
return mode === state.mode;
|
||||
}
|
||||
|
||||
function updateController() {
|
||||
const unit = typeof state.breakpoint === 'string' ? '' : 'px';
|
||||
previewFrame.style.setProperty('--breakpoint', state.breakpoint + unit);
|
||||
previewFrame.classList.add('has-breakpoint');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function setupResponsiveness(rootAttributes) {
|
||||
// INIT
|
||||
const wrapper = document.createElement('div');
|
||||
document.querySelector('.page_toolbar__middle').prepend(wrapper)
|
||||
|
||||
const root = ReactDOM.createRoot(wrapper);
|
||||
const html = (<Responsive rootAttributes={rootAttributes}/>);
|
||||
root.render(html);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
export const WrapperStyling = styled.div`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
@@ -0,0 +1,25 @@
|
||||
.btn {
|
||||
--btn-color: #333;
|
||||
--btn-background-color: white;
|
||||
display: inline-block;
|
||||
padding: calc(0.375rem - 2px) 0.75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: var(--btn-color);
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid var(--btn-background-color);
|
||||
background-color: var(--btn-background-color);
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.75;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
--btn-color: white;
|
||||
--btn-background-color: #333;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#preview_frame {
|
||||
display: block;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
|
||||
--top_spacing: 0px;
|
||||
--breakpoint_top_spacing: 30px;
|
||||
|
||||
margin-top: var(--top_spacing);
|
||||
height: calc(100% - var(--top_panel_height) - var(--top_spacing));
|
||||
background-color: white;
|
||||
|
||||
border: 1px solid #E2E8F0;
|
||||
|
||||
transition: max-width .3s ease-in-out, width .3s ease-in-out, margin-top .3s ease-in-out;
|
||||
|
||||
&.has-breakpoint {
|
||||
--breakpoint: 100%;
|
||||
|
||||
width: 100%;
|
||||
max-width: var(--breakpoint);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +1,78 @@
|
||||
:root {
|
||||
--top_panel_vertical_height: 0.5rem;
|
||||
--top_panel_height: calc(36px + var(--top_panel_vertical_height) * 2);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
background-color: #F7FAFC;
|
||||
}
|
||||
|
||||
header.page_toolbar {
|
||||
.btn {
|
||||
--btn-color: #333;
|
||||
--btn-background-color: white;
|
||||
display: inline-block;
|
||||
padding: calc(0.375rem - 2px) 0.75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: var(--btn-color);
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid var(--btn-background-color);
|
||||
background-color: var(--btn-background-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn:disabled {
|
||||
opacity: 0.75;
|
||||
cursor: default;
|
||||
}
|
||||
.btn--primary {
|
||||
--btn-color: white;
|
||||
--btn-background-color: #333;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.page_toolbar {
|
||||
--gap: 2.5rem;
|
||||
|
||||
font-size: 14px;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #ccc;
|
||||
padding: 1rem;
|
||||
|
||||
background-color: #EDF2F7;
|
||||
padding: var(--top_panel_vertical_height) 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.page_toolbar .page_toolbar__middle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--gap);
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
header.page_toolbar > div {
|
||||
.page_toolbar .page_toolbar__middle > div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
header.page_toolbar > div:not(:first-child):after {
|
||||
content: '|';
|
||||
@media (max-width: 1024px) {
|
||||
.page_toolbar .page_toolbar__middle > div {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.page_toolbar .page_toolbar__middle > div:not(:first-child):after {
|
||||
content: "|";
|
||||
position: absolute;
|
||||
left: calc(var(--gap) / 2 * -1);
|
||||
top: 40%;
|
||||
@@ -30,18 +80,38 @@ header.page_toolbar > div:not(:first-child):after {
|
||||
line-height: 1;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
header.page_toolbar select {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.25rem;
|
||||
.page_toolbar .page_toolbar__middle > div select {
|
||||
font-size: 0.85em;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
border-radius: initial;
|
||||
min-width: initial;
|
||||
width: initial !important;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
header.page_toolbar > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header.page_toolbar .page_toolbar__data_options {
|
||||
.page_toolbar .page_toolbar__middle__data_options {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
#preview_frame {
|
||||
display: block;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
--top_spacing: 0px;
|
||||
--breakpoint_top_spacing: 30px;
|
||||
margin-top: var(--top_spacing);
|
||||
height: calc(100% - var(--top_panel_height) - var(--top_spacing));
|
||||
background-color: white;
|
||||
border: 1px solid #E2E8F0;
|
||||
transition: max-width 0.3s ease-in-out, width 0.3s ease-in-out, margin-top 0.3s ease-in-out;
|
||||
}
|
||||
#preview_frame.has-breakpoint {
|
||||
--breakpoint: 100%;
|
||||
width: 100%;
|
||||
max-width: var(--breakpoint);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=page--main.css.map */
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["page--main.scss","_buttons.scss","_overlay.scss","_page--breakpoints.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA;EAEA;;;ACTF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;ACtBJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AFIF;EACE;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKF;EADF;IAEI;;;;AG/DR;EACE;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EAEA;EAEA;;AAEA;EACE;EAEA;EACA;EACA","file":"page--main.css"}
|
||||
@@ -0,0 +1,72 @@
|
||||
:root {
|
||||
--top_panel_vertical_height: 0.5rem;
|
||||
--top_panel_height: calc(36px + var(--top_panel_vertical_height) * 2);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
//overflow: none;
|
||||
background-color: #F7FAFC;
|
||||
}
|
||||
|
||||
@import "buttons";
|
||||
@import "overlay";
|
||||
|
||||
.page_toolbar {
|
||||
--gap: 2.5rem;
|
||||
font-size: 14px;
|
||||
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #EDF2F7;
|
||||
padding: var(--top_panel_vertical_height) 1rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.page_toolbar__middle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--gap);
|
||||
align-items: center;
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:not(:first-child):after {
|
||||
content: '|';
|
||||
position: absolute;
|
||||
left: calc(var(--gap) / 2 * -1);
|
||||
top: 40%;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
select {
|
||||
font-size: 0.85em;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
border-radius: initial;
|
||||
min-width: initial;
|
||||
width: initial !important;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__data_options {
|
||||
@media (max-width: 1024px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@import "page--breakpoints";
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1920px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=page--view.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["page--view.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA","file":"page--view.css"}
|
||||
@@ -0,0 +1,17 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1920px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
@@ -1,17 +1,30 @@
|
||||
{
|
||||
"name": "create-block-dev-tool",
|
||||
"version": "1.0.2",
|
||||
"name": "@axe-web/create-block",
|
||||
"version": "1.0.11",
|
||||
"author": {
|
||||
"name": "AXE-WEB",
|
||||
"email": "office@axe-web.com",
|
||||
"url": "https://axe-web.com/"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "component-dev",
|
||||
"dev": "NODE_ENV=development node server.js",
|
||||
"generate-block": "yo ./generators/block/index.cjs"
|
||||
"generate-block": "yo ./generators/block/index.js",
|
||||
"create-block": "node ./create-block.js pull",
|
||||
"build": "rollup --config rollup.config.js",
|
||||
"build-platform": "NODE_ENV=development yo ./build.cjs",
|
||||
"build-platform-cli": "component-build",
|
||||
"dev-js": "rollup --config rollup.config.js --watch"
|
||||
},
|
||||
"license": "ISC",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^6.0.0",
|
||||
"archiver": "^5.3.1",
|
||||
"browser-sync": "^2.27.9",
|
||||
"commander": "^9.4.1",
|
||||
"config": "^3.3.7",
|
||||
"escape-html": "^1.0.3",
|
||||
"express": "^4.17.3",
|
||||
"express-handlebars": "^6.0.4",
|
||||
"fs-extra": "^10.0.1",
|
||||
@@ -21,11 +34,42 @@
|
||||
"gulp-sass": "^5.1.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mem-fs": "^2.2.1",
|
||||
"mem-fs-editor": "^9.5.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"node-fetch": "^3.2.10",
|
||||
"open": "^8.4.0",
|
||||
"sanitize-html": "^2.7.1",
|
||||
"sass": "^1.50.1",
|
||||
"unzipper": "^0.10.11",
|
||||
"yeoman-environment": "^3.10.0",
|
||||
"yeoman-generator": "^5.6.1",
|
||||
"yo": "^4.3.0"
|
||||
"yo": "4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@modular-css/rollup": "^28.2.2",
|
||||
"@rollup/plugin-babel": "^5.3.1",
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rollup": "^2.77.2",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-jsx": "^1.0.3",
|
||||
"styled-components": "^5.3.5"
|
||||
},
|
||||
"bin": {
|
||||
"component-dev": "./server.js"
|
||||
}
|
||||
"create-block": "./create-block.js",
|
||||
"component-dev": "./server.js",
|
||||
"component-build": "./build.js"
|
||||
},
|
||||
"files": [
|
||||
"generators/block/templates/.gitignore",
|
||||
"generators/block/**/*",
|
||||
"layouts/**/*",
|
||||
"helpers.js"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
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}`});
|
||||
const sub_field = Object.assign({}, field.sub_fields[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});
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
// Composer - Autoloader
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use LightnCandy\Flags;
|
||||
use LightnCandy\LightnCandy;
|
||||
|
||||
trait Custom_Handlebars {
|
||||
public array $custom_handlebars = [];
|
||||
|
||||
public function register_default_handlebar_helpers() {
|
||||
$this->add_handlebar( 'esc_url', function ( $context ) {
|
||||
if ( function_exists( 'esc_url' ) ) {
|
||||
return esc_url( $context );
|
||||
}
|
||||
|
||||
return $context;
|
||||
} );
|
||||
|
||||
$this->add_handlebar( 'esc_attr', function ( $context ) {
|
||||
if ( function_exists( 'esc_attr' ) ) {
|
||||
return esc_attr( $context );
|
||||
}
|
||||
|
||||
return $context;
|
||||
} );
|
||||
|
||||
$this->add_handlebar( 'esc_html', function ( $context ) {
|
||||
if ( function_exists( 'esc_html' ) ) {
|
||||
return esc_html( $context );
|
||||
}
|
||||
|
||||
return $context;
|
||||
} );
|
||||
|
||||
$this->add_handlebar( 'safe_html', function ( $context ) {
|
||||
if ( function_exists( 'wp_kses_post' ) ) {
|
||||
return wp_kses_post( $context );
|
||||
}
|
||||
|
||||
return $context;
|
||||
} );
|
||||
}
|
||||
|
||||
public function add_handlebar( $key, $func ) {
|
||||
$this->custom_handlebars[ $key ] = $func;
|
||||
}
|
||||
}
|
||||
|
||||
class Component_Builder {
|
||||
use Custom_Handlebars;
|
||||
|
||||
public string $component_name = '';
|
||||
private string $module_path = '';
|
||||
|
||||
function __construct( $component_name, $module_path ) {
|
||||
$this->module_path = $module_path;
|
||||
$this->component_name = $component_name;
|
||||
$this->register_default_handlebar_helpers();
|
||||
}
|
||||
|
||||
function build(): void {
|
||||
$root_path = __DIR__ . '/' . $this->module_path . '/../..';
|
||||
$file_name = $this->get_handlebars_template( "$root_path/src/$this->component_name.template.hbs" );
|
||||
|
||||
$output_folder = $root_path . '/exports/wordpress/templates';
|
||||
foreach ( [ 'styles', 'scripts', 'images' ] as $dir ) {
|
||||
$output_dir = "$output_folder/$dir";
|
||||
if ( is_dir( $output_dir ) === false ) {
|
||||
mkdir( $output_dir, 0777, true );
|
||||
}
|
||||
}
|
||||
|
||||
rename( $file_name, "$output_folder/$this->component_name.template.php" );
|
||||
copy( "$root_path/src/styles/$this->component_name.min.css", "$output_folder/styles/$this->component_name.min.css" );
|
||||
copy( "$root_path/src/scripts/$this->component_name.min.js", "$output_folder/scripts/$this->component_name.min.js" );
|
||||
shell_exec( "cp -r $root_path/src/images $output_folder" );
|
||||
|
||||
echo "Generated '$file_name'.";
|
||||
}
|
||||
|
||||
private function get_handlebars_template( $path = '' ): string {
|
||||
$template = file_get_contents( $path );
|
||||
$phpStr = LightnCandy::compile( $template,
|
||||
[
|
||||
'flags' => Flags::FLAG_NOESCAPE | Flags::FLAG_PARENT | Flags::FLAG_SPVARS | Flags::FLAG_ELSE | Flags::FLAG_JSLENGTH | Flags::FLAG_JSTRUE,
|
||||
'helpers' => $this->custom_handlebars ?? [],
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* NOTE:
|
||||
* PHP 8.0.0 has problems with the LightCandy lib.
|
||||
* If you're running the exact php8.0.0 version, try to downgrade to LightCandy@1.2.5.
|
||||
*/
|
||||
|
||||
return self::create_cache_file( $path, $phpStr );
|
||||
}
|
||||
|
||||
private static function create_cache_file( string $file_path, string $content ): string {
|
||||
$file_path_parts = explode( ".", $file_path );
|
||||
array_pop( $file_path_parts ); // remove ".hbs" format.
|
||||
$file_path_parts[] = 'php';
|
||||
$file_path = join( '.', $file_path_parts );
|
||||
|
||||
$comment = "
|
||||
/**
|
||||
* FILE INFO:
|
||||
* This file was generated by LightCandy::compile function.
|
||||
* The original source is the .HBS(handlebars) file that is located next to this generated PHP file.
|
||||
*/
|
||||
|
||||
";
|
||||
|
||||
$t = file_put_contents( $file_path, '<?php ' . $comment . $content . ' ?>' );
|
||||
if ( $t === false ) {
|
||||
die( "Error: Can't generate HBS template to PHP file. Cache folder is not accessible." );
|
||||
}
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
}
|
||||
|
||||
( new Component_Builder( $argv[1], $argv[2] ) )->build();
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "8.0.0"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"zordius/lightncandy": "^1.2.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1d9b4e7a02be366b48383c185193727f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "zordius/lightncandy",
|
||||
"version": "v1.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/zordius/lightncandy.git",
|
||||
"reference": "b451f73e8b5c73e62e365997ba3c993a0376b72a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/zordius/lightncandy/zipball/b451f73e8b5c73e62e365997ba3c993a0376b72a",
|
||||
"reference": "b451f73e8b5c73e62e365997ba3c993a0376b72a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2.5-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"LightnCandy\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Zordius Chen",
|
||||
"email": "zordius@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).",
|
||||
"homepage": "https://github.com/zordius/lightncandy",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"logicless",
|
||||
"mustache",
|
||||
"php",
|
||||
"template"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/zordius/lightncandy/issues",
|
||||
"source": "https://github.com/zordius/lightncandy/tree/v1.2.6"
|
||||
},
|
||||
"time": "2021-07-11T04:52:41+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=8.0"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "8.0.0"
|
||||
},
|
||||
"plugin-api-version": "2.1.0"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import css from "@modular-css/rollup";
|
||||
import copy from 'rollup-plugin-copy'
|
||||
|
||||
export default {
|
||||
input: 'layouts/scripts/index.js',
|
||||
output: {
|
||||
file: 'layouts/scripts/dist/index.min.js',
|
||||
sourcemap: true
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve({
|
||||
extensions: [".js"],
|
||||
}),
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
preventAssignment: true,
|
||||
}),
|
||||
css(),
|
||||
babel({
|
||||
compact: false,
|
||||
babelHelpers: 'bundled',
|
||||
presets: ["@babel/preset-react"],
|
||||
}),
|
||||
commonjs(),
|
||||
copy({
|
||||
targets: [
|
||||
{ src: 'layouts/scripts/toolbar/images/**/*', dest: 'layouts/scripts/dist/toolbar/images' }
|
||||
]
|
||||
})
|
||||
],
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fetch from "node-fetch";
|
||||
import express from 'express';
|
||||
import {create} from 'express-handlebars';
|
||||
import fsExtra from 'fs-extra';
|
||||
@@ -13,15 +14,20 @@ import dartSass from 'sass';
|
||||
import gulpSass from 'gulp-sass';
|
||||
import sourcemaps from "gulp-sourcemaps";
|
||||
import fs from "fs/promises";
|
||||
import open from "open";
|
||||
import {sanitizeUrl} from "@braintree/sanitize-url";
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import {escape} from "lodash-es";
|
||||
import archiver from 'archiver';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const isDev = config.has('mode') && config.get('mode') === 'development';
|
||||
const modulePath = 'node_modules/create-block-dev-tool/';
|
||||
const projectDir = isDev ? '' : modulePath;
|
||||
console.log('Development Mode:', isDev);
|
||||
const isDev = process.env.NODE_ENV === 'development' || (config.has('isDev') && config.get('isDev')); // Check README file in case you get "missing files" error.
|
||||
const blocksRegistry = isDev ? 'http://localhost:3020' : 'https://axe-web-blocks-registry.captain.devdevdev.life';
|
||||
const modulePath = isDev ? '' : 'node_modules/create-block-dev-tool/';
|
||||
const projectDir = modulePath;
|
||||
|
||||
const sass = gulpSass(dartSass);
|
||||
|
||||
@@ -32,10 +38,22 @@ buildScriptFiles()
|
||||
* Init server
|
||||
*/
|
||||
|
||||
let port = 3000;
|
||||
const app = express();
|
||||
|
||||
const hbs = create({
|
||||
extname: '.hbs', defaultLayout: false, partialsDir: ['.'],
|
||||
extname: '.hbs', defaultLayout: false, partialsDir: ['.'], helpers: {
|
||||
esc_attr(attr) {
|
||||
return escape(attr);
|
||||
}, esc_url(url) {
|
||||
return sanitizeUrl(url);
|
||||
}, esc_html(html) {
|
||||
// TODO: Check if we can remove this helper.
|
||||
return html;
|
||||
}, safe_html(html) {
|
||||
return sanitizeHtml(html);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.engine('.hbs', hbs.engine);
|
||||
@@ -45,43 +63,89 @@ app.set('views', projectDir + 'layouts');
|
||||
const dataFiles = prepareListOfDataFiles(await fs.readdir('./data'));
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
let jsonFileName = (req.query.data) ? req.query.data : 'default';
|
||||
if (!jsonFileName || !await fsExtra.exists(`./data/${jsonFileName}.json`)) {
|
||||
jsonFileName = 'default';
|
||||
let jsonFileName = req.query.data ? req.query.data : 'default';
|
||||
const data = await getBlockConfigs(jsonFileName);
|
||||
if (data.error && data.errorMessage) {
|
||||
return res.send(data.errorMessage);
|
||||
}
|
||||
|
||||
const data = await fsExtra.readJson(`./data/${jsonFileName}.json`);
|
||||
data.helpers = {
|
||||
port: port,
|
||||
include_partial: (path) => projectDir + path,
|
||||
baseView: config.has('baseView') ? config.get('baseView') : 'container',
|
||||
}
|
||||
|
||||
Object.assign(data, {
|
||||
config: {
|
||||
projectDir,
|
||||
dataFiles: dataFiles.map((name) => {
|
||||
return {
|
||||
name,
|
||||
active: jsonFileName === name,
|
||||
};
|
||||
}),
|
||||
remToPx: config.has('remToPx') ? config.get('remToPx') : 16,
|
||||
cssUrl: config.get('cssUrl'),
|
||||
blockName: config.get('blockName')
|
||||
}
|
||||
});
|
||||
res.render('index', data);
|
||||
});
|
||||
|
||||
app.get('/view/:baseView', async (req, res) => {
|
||||
let jsonFileName = req.query.data ? req.query.data : 'default';
|
||||
const data = await getBlockConfigs(jsonFileName);
|
||||
if (data.error && data.errorMessage) {
|
||||
return res.send(data.errorMessage);
|
||||
}
|
||||
|
||||
data.helpers = {
|
||||
include_partial: (path) => projectDir + path,
|
||||
include_block_template: (path) => 'src/' + config.get('blockName') + '.template',
|
||||
include_block_template: (path) => 'src/' + (config.has('blockName') ? config.get('blockName') : 'development') + '.template',
|
||||
}
|
||||
|
||||
const baseView = config.has('baseView') ? config.get('baseView') : 'container';
|
||||
const baseView = req.params.baseView ?? 'container';
|
||||
|
||||
res.render(baseView, data);
|
||||
res.render(baseView, data)
|
||||
});
|
||||
|
||||
app.get('/publish', async (req, res) => {
|
||||
const data = await readJSONFile('./block.json');
|
||||
let responseData;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${blocksRegistry}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
});
|
||||
|
||||
responseData = await response.json();
|
||||
} catch (e) {
|
||||
res.json({success: false, message: 'Blocks Registry server is not available.'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseData.statusCode !== 200) {
|
||||
res.json({success: false, message: 'Error on registry level.'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseData.uploadUrl) {
|
||||
await zipProject();
|
||||
const body = await fs.readFile('./dist.zip');
|
||||
const response = await fetch(`${responseData.uploadUrl}`, {
|
||||
method: 'PUT',
|
||||
body,
|
||||
headers: {'Content-Type': 'application/zip'}
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
res.json({success: false, message: "Can't upload the archive, permissions error."});
|
||||
// TODO: Need to update the registry server.
|
||||
await fs.unlink('./dist.zip');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
res.json({success: true});
|
||||
|
||||
await fs.unlink('./dist.zip');
|
||||
});
|
||||
|
||||
app.use(express.static('src'));
|
||||
app.use(express.static(projectDir + 'layouts'));
|
||||
|
||||
const listener = app.listen(0, () => {
|
||||
const listener = app.listen(0, async () => {
|
||||
const PORT = listener.address().port;
|
||||
await open(`http://localhost:${PORT}`);
|
||||
|
||||
console.log(`The web server has started on port ${PORT}`);
|
||||
|
||||
@@ -97,7 +161,6 @@ const listener = app.listen(0, () => {
|
||||
return cb();
|
||||
}]));
|
||||
|
||||
|
||||
// bs.watch("src/**/*.js", function (event, file) {});
|
||||
// bs.watch("src/**/*.css", function (event, file) {});
|
||||
|
||||
@@ -106,8 +169,11 @@ const listener = app.listen(0, () => {
|
||||
});
|
||||
|
||||
bs.init({
|
||||
proxy: `http://localhost:${PORT}`,
|
||||
proxy: `http://localhost:${PORT}`, open: false
|
||||
}, (err, bs) => {
|
||||
port = bs.server._connectionKey.split(':').pop();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -160,7 +226,88 @@ function prepareListOfDataFiles(dataFiles) {
|
||||
.sort();
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Breakpoints and data options will come from backend server.
|
||||
// [v] Top Panel with options to switch data (select input)
|
||||
// - Options to resize the page and test Responsiveness (select input)
|
||||
async function readJSONFile(jsonFile) {
|
||||
let data = {};
|
||||
|
||||
try {
|
||||
data = await fsExtra.readJson(jsonFile);
|
||||
} catch (e) {
|
||||
return {
|
||||
error: true, errorMessage: getErrorHtml("JSON Syntax error. Please make sure the dataFile is valid.", e),
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function getBlockConfigs(jsonFileName = 'default') {
|
||||
let data = await readJSONFile(`./data/${jsonFileName}.json`);
|
||||
if (data.error) {
|
||||
return data;
|
||||
}
|
||||
|
||||
Object.assign(data, {
|
||||
config: Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object.
|
||||
{
|
||||
projectDir, activeDataFile: jsonFileName, dataFiles: dataFiles.map((name) => {
|
||||
return {
|
||||
name, active: jsonFileName === name,
|
||||
};
|
||||
}), remToPx: config.has('remToPx') ? config.get('remToPx') : 16,
|
||||
})
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function getErrorHtml(message = '', errorMessage = '') {
|
||||
return `<div style="padding: 10px 15px; font-family: Arial, sans-serif">
|
||||
<p>${message}</p>
|
||||
<pre style="padding: 10px 15px; background-color: #ffd0d0; border: 1px solid red;">${errorMessage}</pre>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function zipProject() {
|
||||
// create a file to stream archive data to.
|
||||
const output = await fsExtra.createWriteStream('dist.zip');
|
||||
const archive = archiver('zip', {});
|
||||
|
||||
// listen for all archive data to be written
|
||||
// 'close' event is fired only when a file descriptor is involved
|
||||
output.on('close', function () {
|
||||
console.log(archive.pointer() + ' total bytes');
|
||||
console.log('archiver has been finalized and the output file descriptor has closed.');
|
||||
});
|
||||
|
||||
// This event is fired when the data source is drained no matter what was the data source.
|
||||
// It is not part of this library but rather from the NodeJS Stream API.
|
||||
// @see: https://nodejs.org/api/stream.html#stream_event_end
|
||||
output.on('end', function () {
|
||||
console.log('Data has been drained');
|
||||
});
|
||||
|
||||
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
||||
archive.on('warning', function (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
// log warning
|
||||
} else {
|
||||
// throw error
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
// good practice to catch this error explicitly
|
||||
archive.on('error', function (err) {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// pipe archive data to the file
|
||||
archive.pipe(output);
|
||||
|
||||
// append files from a sub-directory, putting its contents at the root of archive
|
||||
archive.directory('src/', false);
|
||||
|
||||
// finalize the archive (ie we are done appending files but streams have to finish yet)
|
||||
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
|
||||
await archive.finalize();
|
||||
}
|
||||
|
||||