35 Commits

Author SHA1 Message Date
roman 20c5a68d81 1.0.12 2022-10-05 15:49:50 +03:00
roman 35afd615f8 Update package.json file. 2022-10-05 15:49:44 +03:00
roman 70d89f567a 1.0.11 2022-10-05 15:42:19 +03:00
roman ab0fe727b3 Update repository URL & production registry host. 2022-10-05 15:39:04 +03:00
roman 40c67a0bd8 Add production blocks-registry 2022-10-05 14:18:29 +03:00
roman e735f2d141 Return Error if registry server is not available. 2022-10-05 13:10:27 +03:00
roman 804c504c06 Return Error is the archive is not uploaded correctly. 2022-10-05 13:04:04 +03:00
roman 50501b05c6 Version Upgrade 2022-10-05 00:18:12 +03:00
roman 5fcdc36b09 - Push repo to npm registry.
- Remove unnecessary log prints.
- Download and create all required files.
- Generate technical repo files.
#Create Block from registry.
2022-10-05 00:04:49 +03:00
roman 2a5daa2445 - Push repo to npm registry.
- Remove unnecessary log prints.
- Download and create all required files.
- Generate technical repo files.
#Create Block from registry.
2022-10-05 00:00:26 +03:00
roman e184c3483a - Remove unnecessary log prints.
- Download and create all required files.
- Generate technical repo files.
#Create Block from registry.
2022-10-04 18:13:39 +03:00
roman ee197d24c4 - Download and create all required files.
- Generate technical repo files.
#Create Block from registry.
2022-10-04 18:11:08 +03:00
roman 30a3e964bd - Generate technical repo files.
#Create Block from registry.
2022-10-04 05:19:55 +03:00
roman 93c39981f9 - Generate technical repo files.
#Create Block from registry.
2022-10-02 09:41:31 +03:00
roman 019e51ab1c Convert Yeoman generators to ES module 2022-10-01 19:15:13 +03:00
roman 760825be2d Added basic "publish" block logic. 2022-09-30 14:03:48 +03:00
roman 6b3ed38e04 Fix Group field issue - remove prefix in children's names. 2022-09-04 13:07:10 +03:00
roman 190800cc80 Add block.json file support + fields config. 2022-09-04 10:29:16 +03:00
roman 33dbe48a2c Fix path typo. 2022-09-02 07:13:54 +03:00
roman c7e492ae8a Updated "export" logic on WordPress/PHP platforms. 2022-09-02 07:09:50 +03:00
roman ae68a88fd1 Improved Responsiveness controllers - "remember" latest state of selected breakpoint. 2022-08-28 11:06:24 +03:00
roman 7b7a8c101f Added comments to WordPress/PHP built output. 2022-08-18 12:23:30 +03:00
roman 3aa5370c2b Fix Path issue. 2022-08-18 06:51:25 +03:00
roman 92e4ca76f4 Version upgrade 2022-08-18 06:21:12 +03:00
roman 2917cb7332 Added WordPress adaptation. 2022-08-18 05:46:55 +03:00
roman 0d4baf6b11 Added Handlebar Sanitization helpers. 2022-08-17 18:27:04 +03:00
roman 50d7117609 Update yeo-man package to latest. 2022-08-11 11:40:01 +03:00
roman 28ecfa281c Added options to control different ranges of breakpoints. 2022-08-11 10:51:56 +03:00
roman 9173efe9c9 Added the logic of wrapper + responsiveness controllers. 2022-07-19 18:43:16 +03:00
roman a7e51803cf Update the hbs template with ActiveDataFile parameter 2022-05-08 09:45:17 +03:00
roman de8d78ea31 Update Toolbar styling. 2022-05-06 08:45:33 +03:00
roman 29734a6b94 Added "activeDataFile" parameter in hbs templates. 2022-05-06 07:49:00 +03:00
roman d791edda3e Added Image_URL property to default data.json files. 2022-05-02 18:57:26 +03:00
roman 0ea8f8b443 Update the generator template.
Add remToPx parameter + design folder.
2022-05-02 18:55:55 +03:00
roman 718e6b32a9 Added delivery test to README file. 2022-05-02 13:16:40 +03:00
57 changed files with 19046 additions and 9613 deletions
+5
View File
@@ -10,3 +10,8 @@ deploy-*.sh
# Custom
blocks
config
data
src
exports
block.json
+8
View File
@@ -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`.
Executable
+55
View File
@@ -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);
+179
View File
@@ -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);
});
});
})
}
-145
View File
@@ -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(' ');
}
+152
View File
@@ -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
}
+12
View File
@@ -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.
+34
View File
@@ -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"
}
+7
View File
@@ -0,0 +1,7 @@
# Basic
.idea
.DS_Store
node_modules
vendor
# Custom
+4 -3
View File
@@ -1,10 +1,11 @@
{
"name": "<%= blockFilename %>",
"version": "1.0.0",
"version": "<%= version %>",
"scripts": {
"start": "component-dev"
"start": "component-dev",
"build": "component-build"
},
"devDependencies": {
"create-block-dev-tool": "git+https://roman-axe-web@bitbucket.org/axeweb/create-block-dev-tool.git#master"
"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>
&lt;img src=&quot;images/demo.jpeg&quot; alt=&quot;&quot;&gt;
<code>&lt;img src=&quot;/images/demo.jpeg&quot; alt=&quot;&quot;&gt;
</code>
</div>
@@ -47,8 +47,7 @@
<div>
<a href="#" class="btn btn--primary">Primary</a>
<br>
<code>
&lt;a href=&quot;#&quot; class=&quot;btn btn--primary&quot;&gt;Primary&lt;/a&gt;
<code>&lt;a href=&quot;#&quot; class=&quot;btn btn--primary&quot;&gt;Primary&lt;/a&gt;
</code>
</div>
@@ -57,8 +56,7 @@
<div>
<a href="#" class="btn btn--secondary">Secondary</a>
<br>
<code>
&lt;a href=&quot;#&quot; class=&quot;btn btn--secondary&quot;&gt;Secondary&lt;/a&gt;
<code>&lt;a href=&quot;#&quot; class=&quot;btn btn--secondary&quot;&gt;Secondary&lt;/a&gt;
</code>
</div>
</div>
+14
View File
@@ -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(' ');
}
-2
View File
@@ -4,8 +4,6 @@
<body>
{{> (include_partial "layouts/partials/toolbar") }}
<main>
{{> (include_block_template) }}
</main>
-2
View File
@@ -4,8 +4,6 @@
<body>
{{> (include_partial "layouts/partials/toolbar") }}
<main>
<div class="container">
{{> (include_block_template) }}
+23
View File
@@ -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>
+6 -3
View File
@@ -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>
+7 -3
View File
@@ -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}}
+7
View File
@@ -1,4 +1,8 @@
<header class="page_toolbar">
<div class="page_toolbar__left">#</div>
<div class="page_toolbar__middle">
<div class="page_toolbar__data_options">
<label for="data-options">Data Options: </label>
@@ -12,4 +16,7 @@
<div>
Sizes: <b>1rem = {{ config.remToPx }}px</b>
</div>
</div>
<div class="page_toolbar__right"></div>
</header>
+11406
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+18
View File
@@ -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

+19
View File
@@ -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

+4
View File
@@ -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

+19
View File
@@ -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

+33
View File
@@ -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;
});
}
-13
View File
@@ -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

+47
View File
@@ -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;
`;
+81
View File
@@ -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;
`;
+25
View File
@@ -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;
}
}
+13
View File
@@ -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;
}
+25
View File
@@ -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;
}
}
+91 -21
View File
@@ -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 */
+1
View File
@@ -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"}
+72
View File
@@ -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";
+19
View File
@@ -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 */
+1
View File
@@ -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"}
+17
View File
@@ -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;
}
+5680 -9355
View File
File diff suppressed because it is too large Load Diff
+50 -8
View File
@@ -1,17 +1,29 @@
{
"name": "create-block-dev-tool",
"version": "1.0.2",
"name": "@axe-web/create-block",
"version": "1.0.12",
"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"
"create-block": "node ./create-block.js pull",
"build": "rollup --config rollup.config.js",
"build-platform": "NODE_ENV=development node ./build.js",
"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 +33,41 @@
"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",
"yeoman-generator": "^5.6.1",
"yo": "^4.3.0"
"unzipper": "^0.10.11"
},
"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",
"yeoman-environment": "^3.10.0",
"yeoman-generator": "^5.6.1"
},
"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"
]
}
+227
View File
@@ -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,
});
}
}
+125
View File
@@ -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();
+11
View File
@@ -0,0 +1,11 @@
{
"config": {
"platform": {
"php": "8.0.0"
}
},
"require": {
"php": ">=8.0",
"zordius/lightncandy": "^1.2.6"
}
}
+80
View File
@@ -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"
}
+35
View File
@@ -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' }
]
})
],
}
+179 -32
View File
@@ -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`);
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')
data.helpers = {
port: port,
include_partial: (path) => projectDir + path,
baseView: config.has('baseView') ? config.get('baseView') : 'container',
}
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();
}