43 Commits

Author SHA1 Message Date
roman 20778999a2 1.0.18 2022-11-04 11:55:08 +02:00
roman 3c39f49a84 Merge pull request 'build-wp-block' (#1) from build-wp-block into master
Reviewed-on: AXE-WEB/block-dev-tool#1
2022-11-04 09:49:36 +00:00
roman 344ce55e67 Provided a way to Generate basic WordPress Class. 2022-10-27 09:56:52 +03:00
roman 73be6c050b Fixed: Errors in JS file (in src dir) crash the server while “build” process. 2022-10-27 07:22:44 +03:00
roman f23c745a15 Add more template files to WordPress generator. 2022-10-25 06:54:01 +03:00
roman 6ddc9b9910 Add missing files to package.json. 2022-10-24 20:33:12 +03:00
roman b5d23d612f Added WordPress files generation to build process - really fast and dirty coding. 2022-10-24 19:17:26 +03:00
roman 7be17a28fa Make sure the preview option is working on localhost without local network or internet connection. 2022-10-24 11:10:11 +03:00
roman c5502e2be2 Update logic of CSS minifier, use {compress: true} option. 2022-10-24 06:32:14 +03:00
roman a9597684de Set Height limiter. 2022-10-23 20:21:22 +03:00
roman d1bbffce2e Ignore Width Changes of the iframe, listen only to width changes. 2022-10-23 18:43:44 +03:00
roman 12ef5e0570 Update resizing logic, listen to <main> tag changes and not <body>. 2022-10-22 13:25:37 +03:00
roman c48420249b All development block files moved to blocks directory. 2022-10-22 12:43:43 +03:00
roman 82d4f1b755 Added "Copy to Clipboard" option to Sidebar with data options. 2022-10-22 11:42:14 +03:00
roman f8a46b8148 Handle syntax errors. 2022-10-21 19:16:03 +03:00
roman 8c75133759 Add short delay after before resizing message. 2022-10-21 15:32:41 +03:00
roman 1d214ca60f Update Frame resizing logic. 2022-10-21 14:53:24 +03:00
roman ff33974b78 Open devTool only after browserSync is ready. 2022-10-21 11:13:41 +03:00
roman 212e0a414b Added base_url parameter. 2022-10-21 10:42:46 +03:00
roman e407dcd0ca Added section_class parameter. 2022-10-21 10:36:53 +03:00
roman 7d168b27ee Fixed Responsive Sizes issues - 414px is not really 414 because of scrollbars. 2022-10-21 08:52:23 +03:00
roman c570cd9c75 Disable container css rules. 2022-10-18 08:23:40 +03:00
roman 86f64e8b4b Move yeoman packages to prod list. 2022-10-18 07:16:12 +03:00
roman 9ac3a19b2f Update URLs to "external" so developer can share with multiple devices. 2022-10-17 21:36:24 +03:00
roman 89e24c9285 Add required files to package json. 2022-10-17 19:46:27 +03:00
roman f9d30546ca Version Upgrade 2022-10-17 19:43:31 +03:00
roman 7201d360ee Include FromBorderSize & Scrollbars in Frame width. To fit responsive sizes properly. 2022-10-17 19:39:22 +03:00
roman 7eeafdbd31 Disable preview(iframe) width limit. 2022-10-17 18:16:04 +03:00
roman 1d3aec4dff Remove unused files that were migrated to create-block package. 2022-10-17 16:05:58 +03:00
roman 1132790fd8 Rename them package 2022-10-17 14:50:48 +03:00
roman 5672912f04 Display relevant PreviewImages on ResponsiveModes changed. 2022-10-17 14:22:51 +03:00
roman fe95eb0ef3 Update PreviewOptionImage on DataOptions changed. 2022-10-17 13:07:28 +03:00
roman 86602b0ab8 Fix middle alignment of preview design. 2022-10-16 21:55:42 +03:00
roman 49cc9f685c Version Upgrade 2022-10-15 23:19:35 +03:00
roman 0ace3de83e Added logic of creating block from scratch. 2022-10-15 23:07:41 +03:00
roman 38629e7fbb Add missing icons to repo. 2022-10-15 13:37:43 +03:00
roman 44dc99c641 Added PreviewDesign functionality - for perfect pixel test. 2022-10-15 13:30:49 +03:00
roman dfcd9681bc Added PreviewFiles to devTool server side. 2022-10-13 06:33:08 +03:00
roman 29a75a6e26 Provide option to open the demo page in new tab. 2022-10-11 10:31:38 +03:00
roman 0ec37c4c5b Added DataOptions sidebar. 2022-10-11 09:25:45 +03:00
roman 56839fee2e Disable unused stuff. 2022-10-05 18:22:42 +03:00
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
62 changed files with 5281 additions and 12937 deletions
-4
View File
@@ -10,8 +10,4 @@ deploy-*.sh
# Custom
blocks
config
data
src
exports
block.json
+27 -8
View File
@@ -5,9 +5,11 @@ import config from 'config';
import Generator from "yeoman-generator";
import yeoman from 'yeoman-environment';
import {buildHubspot} from "./platforms/hubspot/hubspot-adapter.js";
import {getConfigs} from "./helpers.js";
import {buildWordPress} from "./platforms/wordpress/wordpress-adapter.js";
import path from "path";
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 {modulesPath, projectPath} = getConfigs();
const blockName = config.has('blockName') ? config.get('blockName') : 'development';
@@ -18,7 +20,7 @@ class buildGenerator extends Generator {
type: "list",
name: "platform",
message: "Choose Platform",
choices: ['WordPress', 'Hubspot', 'Hubspot Email', 'JavaScript', 'PHP'],
choices: ['WordPress', 'WordPress Block', 'Hubspot', 'Hubspot Email', 'JavaScript', 'PHP'],
default: 'WordPress'
}
])
@@ -27,11 +29,29 @@ class buildGenerator extends Generator {
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();
const backPath = modulesPath ? modulesPath.split('/').map(() => '..').join('/') : '';
return new Promise((resolve, reject) => {
const phpGeneratorPath = path.join(modulesPath, 'platforms', 'php');
exec(`cd ${phpGeneratorPath} && composer install && php build.php '${blockName}' '${backPath}' '${projectPath}'`, function (error, stdout) {
if (error) {
console.log('Error:', error)
reject(error);
}
console.log(stdout);
resolve();
});
}).then(() => {
if (this.data.platform === 'WordPress') {
return buildWordPress();
}
if (this.data.platform === 'WordPress Block') {
return buildWordPress(true);
}
});
} else if (this.data.platform === 'Hubspot Email') {
buildHubspot(blockName)
.then(() => {
@@ -50,6 +70,5 @@ class buildGenerator extends Generator {
}
}
const build = new buildGenerator([], {env: yeoman.createEnv()}, {});
build.run().then(() => null);
-179
View File
@@ -1,179 +0,0 @@
#!/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);
});
});
})
}
-152
View File
@@ -1,152 +0,0 @@
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
}
-23
View File
@@ -1,23 +0,0 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
# WordPress Coding Standards
# https://make.wordpress.org/core/handbook/coding-standards/
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.json]
indent_style = tab
indent_size = 4
[*.md]
trim_trailing_whitespace = false
-7
View File
@@ -1,7 +0,0 @@
# Basic
.idea
.DS_Store
node_modules
vendor
# Custom
-167
View File
@@ -1,167 +0,0 @@
# Development Setup
Install required modules:
```sh
npm install
```
Run development environment:
```sh
npm start
```
## How it works ⚙️
The project runs `gulp` and `browsersync` beyond the scenes.
Each change in template, styling or script files will reload the page. The idea is to have a comfortable development
environment.
# Block Structure
You will find all the block files in `/src` folder.
```
/src/images/
*.png, *jpg, *.svg
/src/scritps/
*.mjs, *js
/src/styles/
*.scss
/src/*.template.hbs
```
# Block rules
We have a “blocks system”, means you will have to follow strict rules and use listed technologies:
## Template Layout & Data
### Data
You will find multiple `*.json` files in `/data` folder after the first run of `npm start`.
These files are used as data sources in layout. By reviewing these files you can understand what parameters you have and
how the data is stored.
There are multiple data sources since the block can be reused in different situations with different data. We have to
make sure that the block is always rendered properly.
> Don't change or edit these files - use the data as it is!
### Handlebars
[Handlebars](https://handlebarsjs.com/guide/ "What is Handlebars") used as a template engine in the project.
All the template layout must be in a single `*.hbs` file that located in `/src` folder.
> Use the data that is available in `/data` folder.
> Avoid using any kind of "freestyle" input/content. Your template file must contain only HTML tags and Handlebars parameters.
### Static Media Files 🖼️
Use the `/src/images` folder in case you need to upload images or video files to the template.
These files will be available by the next URL: http://localhost:3000/images/${filename}
### Class Naming Convention
Use BEM naming system for class names: http://getbem.com/naming/
## Styling rules 🎨
### Mobile First 📱
Use Mobile First approach. Start with mobile device styling and then focus on other screen sizes by using @media (
min-width: ...px).
### CSS Units
Use `rem` units instead of pixels.
For example, `1rem = 16pixels` depends on project.
No need to use rem in situations of `1px`, `2px` or `3px`. (usually used for borders width).
It's important to mention that the block is "elastic". By using `rem` units we are able to scale the layout and keep the
aspect ratio.
### Global Styling
The development environment includes CSS rules of the original project. This is including fonts, CSS variables,
container logic etc...
The CSS file is already embedded in served HTML.
# JavaScript
## Sliders
For any kind of slides animations - use [SwiperJS](https://swiperjs.com/get-started).
The lib is included in the project and ready for use. Test with `window.Swiper` in browser console.
# Summary
## Short Video Review
Check the [Video Guide](https://www.loom.com/share/1c707a4ea14e48b7a35a49d7b0a6f1e0)
> PRO TIP
> Use x2 speed in the player 😜
## Testing ✅
### Development Toolbar ✔️
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.
This might be related to links, buttons or any other interactive elements.
### Responsiveness ✔️
Make sure the layout is rendered correctly on all standard breakpoint size:
1. 1920px in width...
2. 1680px
3. 1440px
4. 1360px
5. 1280px
6. 1024px
7. 980px
8. 768px
9. 600px
10. 414px
11. 375px
If you follow the rules of REM unit - your layout must be "elastic" and most of the breakpoints should be rendered
properly.
### Pixel Perfect Test ✔️
Compare the final result with provided screenshots.
You can use
available [Browser Extensions](https://chrome.google.com/webstore/detail/perfectpixel-by-welldonec/dkaagdgjmgdmbnecmcefdhjekcoceebi?hl=en)
or any other tool/approach that you're familiar with.
The idea is to have "pretty close" result to requested design.
## Delivery
Once you finished with the task, before letting me know - please review the next [Google Form link](https://forms.gle/w4sJjAyRDSSBRuGLA](https://forms.gle/w4sJjAyRDSSBRuGLA)
If everything is OK - send a Pull Request to the git project or share a ZIP file with me directly.
-34
View File
@@ -1,34 +0,0 @@
{
"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,7 +0,0 @@
module.exports = {
cssUrl: "https://",
jsUrl: "https://",
blockName: "<%= blockFilename %>",
baseView: "<%= baseView %>",
remToPx: <%= remToPx %>,
}
@@ -1,15 +0,0 @@
{
"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"
}
/**
* Tips for good data.json example:
* - Provide Short and Long version of text (title/content).
* - Put <a> and <strong> elements in content.
* - Upload different sizes and ratios of images.
* - Provide options with and without CTA to test conditional logic.
*/
@@ -1,7 +0,0 @@
{
"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
@@ -1,7 +0,0 @@
# Basic
.idea
.DS_Store
node_modules
vendor
# Custom
-11
View File
@@ -1,11 +0,0 @@
{
"name": "<%= blockFilename %>",
"version": "<%= version %>",
"scripts": {
"start": "component-dev",
"build": "component-build"
},
"devDependencies": {
"create-block-dev-tool": "<%= devToolSource %>"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

@@ -1,7 +0,0 @@
(function ($) {
console.log('Ready!');
console.log('jQuery =', $);
console.log('Swiper =', window.Swiper);
})(window.jQuery);
@@ -1,40 +0,0 @@
/**
* 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/
*
* Use Mobile First approach.
* Start with mobile device styling and then focus on other screen sizes by using @media (min-width: ...px).
*
*/
.<%= blockClassName %> {
&__header {
// Header Mobile.
@media (min-width: 1024px) {
// Header Desktop.
}
&-heading {
// Child Element.
}
}
&__visual {
&-image {
}
}
&__content {
}
&__footer {
}
}
@@ -1,68 +0,0 @@
<section class="<%= blockClassName %> <%= blockClassName %>--{{ config.activeDataFile }}">
{{!
Remove Everything Below:
}}
<style>
code {
background-color: #eee;
color: #333;
padding: 1rem 1.5rem;
border: 2px solid #d8d8d8;
display: inline-block;
border-radius: 2px;
font-size: 14px;
white-space: pre-line;
}
</style>
<header class="<%= blockClassName %>__header">
<h1 class="<%= blockClassName %>__header-heading">Ready!</h1>
</header>
<div class="<%= blockClassName %>__content">
<p>
Review the `/data` folder with JSON data files.<br>
Don't change data JSON files - use the data as it is.
</p>
<p>Build the layout based on provided data structure.</p>
<br><br>
<hr>
<h2>Image Example</h2>
<div>
<img src="/images/demo.jpeg" alt="">
<br>
<code>&lt;img src=&quot;/images/demo.jpeg&quot; alt=&quot;&quot;&gt;
</code>
</div>
<br><br>
<hr>
<h2>Available Buttons Styling</h2>
<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>
</div>
<br>
<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>
</div>
</div>
{{!
Remove END
}}
</section>
+95
View File
@@ -1,3 +1,98 @@
import path from 'path';
import config from 'config';
import {fileURLToPath} from 'url';
import memFs from 'mem-fs';
import editor from 'mem-fs-editor';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export function getConfigs() {
const isDev = process.env.NODE_ENV === 'development'; // Check README file in case you get "missing files" error.
const developmentBlockName = process.env.BLOCK_NAME;
return {
isDev,
developmentBlockName,
modulesPath: isDev ? '' : 'node_modules/block-dev-tool',
projectPath: isDev ? path.join('blocks', developmentBlockName) : '',
};
}
import fsExtra from "fs-extra";
export 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;
}
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>`;
}
export async function getBlockConfigs(jsonFileName = 'default',
{includeConfigs, projectPath, modulesPath, dataFiles} = {}) {
let data = await readJSONFile(path.join(projectPath, 'data', `${jsonFileName}.json`));
if (data.error) {
return data;
}
if (includeConfigs) {
Object.assign(data, {
config: Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object.
{
projectDir: modulesPath, activeDataFile: jsonFileName, dataFiles: dataFiles.map((name) => {
return {
name, active: jsonFileName === name,
};
}), remToPx: config.has('remToPx') ? config.get('remToPx') : 16,
})
});
}
return data;
}
export function getBlockName(name = '') {
if (name.startsWith('@')) {
name = name.substring(1);
}
const arr = name.split('/');
return {
project: arr[0],
name: arr[1],
};
}
export async function createFiles(data, files = [], {pathDist, generatorsPath}) {
generatorsPath = generatorsPath ?? path.join(__dirname, 'generators/block/templates');
const store = memFs.create();
const filesystem = editor.create(store);
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
}
export function capitalize(str) {
if (typeof str !== 'string') {
return '';
+17
View File
@@ -0,0 +1,17 @@
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
<body>
<main>
<div class="container" style="max-width: 1280px; margin: 1rem auto;">
<h1>Syntax Error:</h1>
<pre style="padding: 10px 15px; background-color: #ffe6e6; border: 1px solid red; border-radius: 0.25rem; overflow: auto">{{err}}</pre>
</div>
</main>
{{> (include_partial "layouts/partials/scripts") }}
</body>
</html>
+5 -2
View File
@@ -13,10 +13,13 @@
<script>
window.devTool = {
previewFrameUrl: 'http://localhost:{{ port }}/view/{{ baseView }}',
previewFrameUrl: '{{ previewFrameUrl }}',
};
</script>
<iframe id="preview_frame" src="http://localhost:{{ port }}/view/{{ baseView }}" class="breakpoint"></iframe>
<div class="preview">
<iframe id="preview_frame" src="{{ previewFrameUrl }}" class="breakpoint"></iframe>
</div>
<script src="/scripts/dist/index.min.js"></script>
</body>
+1
View File
@@ -1,3 +1,4 @@
<script src="/scripts/frame-index.js"></script>
<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>{{#if config.jsUrl }}
{{#each config.jsUrl }}<script src="{{ this }}"></script>
+4 -12
View File
@@ -1,22 +1,14 @@
<header class="page_toolbar">
<div class="page_toolbar__left">#</div>
<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 class="page_toolbar__right"></div>
<div class="page_toolbar__right">
<a href="{{ previewFrameUrl }}" target="_blank" class="open_in_new_tab"></a>
</div>
</header>
+1760 -3113
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
<path
d="M12.45 38.7 9.3 35.55 20.85 24 9.3 12.5l3.15-3.2L24 20.8 35.55 9.3l3.15 3.2L27.2 24l11.5 11.55-3.15 3.15L24 27.2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 218 B

@@ -0,0 +1,7 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px"
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
<path id="XMLID_225_" d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,0.001l-139.39,139.393L25.607,79.393
c-5.857-5.857-15.355-5.858-21.213,0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,4.393,10.606,4.393
s7.794-1.581,10.606-4.394l149.996-150C331.465,94.749,331.465,85.251,325.607,79.393z"/>
</svg>

After

Width:  |  Height:  |  Size: 553 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
<path
d="M28.6 40.75V36.2h4.75q1.2 0 2.05-.825.85-.825.85-2.075v-3.85q0-1.8 1.075-3.225T40.15 24.2v-.4q-1.75-.55-2.825-2-1.075-1.45-1.075-3.3v-3.85q0-1.2-.85-2.05-.85-.85-2.05-.85H28.6V7.2h5.95q2.6 0 4.425 1.825Q40.8 10.85 40.8 13.5v3.85q0 1.2.825 2.05.825.85 2.075.85h1.1v7.5h-1.1q-1.25 0-2.075.85-.825.85-.825 2.05v3.85q0 2.65-1.825 4.45-1.825 1.8-4.425 1.8Zm-15.1 0q-2.7 0-4.475-1.8-1.775-1.8-1.775-4.45v-3.85q0-1.2-.85-2.05-.85-.85-2.05-.85h-1.1v-7.5h1.1q1.2 0 2.05-.85.85-.85.85-2.05V13.5q0-2.65 1.8-4.475Q10.85 7.2 13.5 7.2h5.95v4.55H14.7q-1.25 0-2.075.85-.825.85-.825 2.05v3.85q0 1.85-1.1 3.3-1.1 1.45-2.85 2v.4q1.75.6 2.85 2.025 1.1 1.425 1.1 3.225v3.85q0 1.25.825 2.075.825.825 2.075.825h4.75v4.55Z"/>
</svg>

After

Width:  |  Height:  |  Size: 803 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
<path
d="M9.5 43.05q-1.85 0-3.2-1.35t-1.35-3.2v-29q0-1.9 1.35-3.25T9.5 4.9h13.35v4.6H9.5v29h29V25.15h4.6V38.5q0 1.85-1.35 3.2t-3.25 1.35ZM20.3 30.9l-3.15-3.2 18.2-18.2h-9.5V4.9H43.1v17.25h-4.6V12.7Z"/>
</svg>

After

Width:  |  Height:  |  Size: 294 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="m31.7 26.4-3.85-3.8q1.05-2.2-.625-3.45t-3.125-.3l-3.5-3.55q.75-.4 1.65-.6.9-.2 1.75-.2 3.55 0 6.025 2.475Q32.5 19.45 32.5 23q0 .8-.2 1.8t-.6 1.6Zm7.4 7.5-2.75-2.8q2.2-1.75 3.825-3.85T42.8 23q-2.6-5.5-7.525-8.75Q30.35 11 24.5 11q-2.1 0-4.1.35-2 .35-2.95.8l-3.3-3.3q1.75-.8 4.625-1.4 2.875-.6 5.475-.6 7.35 0 13.6 4.25t9.3 11.9q-1.25 3.3-3.375 6.075Q41.65 31.85 39.1 33.9Zm1.25 11.35-7.7-7.6q-1.75.7-4 1.1-2.25.4-4.65.4-7.55 0-13.825-4.275Q3.9 30.6.85 23q.9-2.55 2.75-5.15 1.85-2.6 4.3-5L1.75 6.8l2.5-2.6L42.7 42.65ZM10.8 15.7Q9 17.15 7.5 19.125 6 21.1 5.2 23q2.6 5.55 7.65 8.775Q17.9 35 24.4 35q1.35 0 2.775-.15 1.425-.15 2.225-.55l-3.2-3.2q-.4.2-1.025.3-.625.1-1.175.1-3.5 0-6-2.45T15.5 23q0-.55.075-1.15.075-.6.225-1.05Zm16.15 6.4Zm-6.85 3.45Z"/></svg>

After

Width:  |  Height:  |  Size: 825 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M24 31.5q3.55 0 6.025-2.475Q32.5 26.55 32.5 23q0-3.55-2.475-6.025Q27.55 14.5 24 14.5q-3.55 0-6.025 2.475Q15.5 19.45 15.5 23q0 3.55 2.475 6.025Q20.45 31.5 24 31.5Zm0-3.7q-2 0-3.4-1.4T19.2 23q0-2 1.4-3.4t3.4-1.4q2 0 3.4 1.4t1.4 3.4q0 2-1.4 3.4T24 27.8Zm0 11.35q-7.7 0-13.9-4.5T.85 23q3.05-7.15 9.25-11.65T24 6.85q7.7 0 13.9 4.5T47.15 23q-3.05 7.15-9.25 11.65T24 39.15ZM24 23Zm0 12q6 0 11.05-3.275Q40.1 28.45 42.75 23q-2.65-5.45-7.675-8.725Q30.05 11 24 11q-6 0-11.05 3.275Q7.9 17.55 5.2 23q2.7 5.45 7.725 8.725Q17.95 35 24 35Z"/></svg>

After

Width:  |  Height:  |  Size: 604 B

+27
View File
@@ -0,0 +1,27 @@
'use strict';
const heightLimit = window.outerHeight * 2;
let height = getCurrentHeight();
setupResizeListener();
///
function setupResizeListener() {
const resizeObserver = new ResizeObserver(handleHeightChange);
resizeObserver.observe(document.body);
}
function handleHeightChange(entries) {
const updatedHeight = getCurrentHeight();
if (height === updatedHeight || updatedHeight > heightLimit) {
return;
}
const RESIZE_CODE = 'resize:';
window.parent.postMessage(RESIZE_CODE + JSON.stringify({height}), '*');
height = updatedHeight;
}
function getCurrentHeight() {
return document.querySelector('body > main').scrollHeight;
}
+10 -19
View File
@@ -2,32 +2,23 @@
import {setupResponsiveness} from './toolbar/responsive.jsx';
import {setupPublish} from "./toolbar/publish.jsx";
import {setupDataOptions} from "./toolbar/data-options/DataOptions.jsx";
const previewFrame = document.getElementById('preview_frame');
const rootAttributes = {
previewFrame: document.getElementById('preview_frame'),
previewFrame,
}
setupResponsiveness(rootAttributes);
setupDataOptions(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) {
window.addEventListener('message', function (e) {
const RESIZE_CODE = 'resize:';
if (typeof e.data !== 'string' || !e.data.startsWith(RESIZE_CODE)) {
return;
}
dataOptionsSelect.addEventListener('change', function () {
previewFrame.src = window.devTool.previewFrameUrl + '?data=' + this.value;
});
}
const data = JSON.parse(e.data.substring(RESIZE_CODE.length))
previewFrame.style.height = data.height + 'px';
});
@@ -0,0 +1,173 @@
import React, {useCallback, useEffect, useState} from 'react';
import * as ReactDOM from 'react-dom/client';
import {
SidebarButtonToggleStyle,
SidebarCloseButtonStyle, SidebarDataOptionsStyle,
SidebarHeaderStyle,
SidebarStyle,
} from "./data-options.style.js";
import {isClickOutside, isEscHit} from "../responsive-button/ResponsiveButton.jsx";
import {DesignPreview} from "./DesignPreview.jsx";
function DataOptions(props = {}) {
props.rootAttributes = props.rootAttributes ?? {};
const initialState = {dataName: 'default', data: {}, dataOptions: [], designPreview: []};
const [state, setState] = useState(initialState);
const [previewOption, setPreviewOption] = useState(getDesignPreviewImage(state.dataName, state.designPreview));
const updateState = (update) => setState(Object.assign({}, state, update));
const [sidebarOpen, setSidebarOpen] = useState(false);
useEffect(async () => {
const data = await fetchDataOptions(state.dataName);
updateState(data);
}, []);
const handleCloseSidebarEscEvent = useCallback((e) => {
if (isEscHit(e)) {
(() => {
closeSidebar()
})();
}
}, []);
useEffect(() => {
document.addEventListener("keydown", handleCloseSidebarEscEvent);
// Unsubscribe from ESC listener.
return () => {
document.removeEventListener("keydown", handleCloseSidebarEscEvent);
}
}, [handleCloseSidebarEscEvent]);
useEffect(() => {
window.addEventListener('message', responsiveModeChangesHandler);
return () => window.removeEventListener('message', responsiveModeChangesHandler);
}, [state]);
useEffect(() => {
setPreviewOption(getDesignPreviewImage(state.dataName, state.designPreview));
}, [state.designPreview]);
const handleBlur = async (e) => await isClickOutside(e) ? closeSidebar() : null;
return <div style={{display: 'flex', gap: '1rem', alignItems: 'center'}}>
<SidebarButtonToggleStyle onClick={() => openSidebar()} title="Open a Sidebar with Data Options"/>
<DesignPreview previewOption={previewOption}/>
<SidebarStyle className={sidebarOpen ? 'active sidebar-active' : ''} tabIndex='0' onBlur={handleBlur}>
<SidebarHeaderStyle>
<SidebarCloseButtonStyle onClick={() => closeSidebar()}></SidebarCloseButtonStyle>
</SidebarHeaderStyle>
{state.dataOptions && !!state.dataOptions.length &&
<SidebarDataOptionsStyle>
<label htmlFor="data-options">Data Options</label>
<select name="data" id="data-options" onChange={(e) => changeDataOption(e)} value={state.dataName}>
{state.dataOptions.map((item) => {
const isSelected = state.dataName === item;
return <option value={item} selected={isSelected}>{item}</option>
})}
</select>
</SidebarDataOptionsStyle>
}
{state.data &&
<pre>{JSON.stringify(state.data, null, 2)}</pre>
}
<button className='btn btn--secondary' onClick={(e) => copyToClipboard(e, state.data)}>Copy to Clipboard</button>
</SidebarStyle>
</div>;
//
// Functions
//
function openSidebar() {
setSidebarOpen(true);
setTimeout(() => document.querySelector('.sidebar-active').focus());
}
function closeSidebar() {
setSidebarOpen(false);
}
async function changeDataOption(e) {
const dataName = e.target.value;
props.rootAttributes.previewFrame.src = window.devTool.previewFrameUrl + '?data=' + dataName;
const dataOption = await fetchDataOptions(dataName);
updateState(Object.assign({}, dataOption, {dataName}));
}
async function fetchDataOptions(name = 'default') {
const queryParameters = new URLSearchParams({name});
const response = await fetch(`/data?${queryParameters}`);
return await response.json();
}
function getDesignPreviewImage(currentDataName, designPreviewOptions = []) {
const dataOptions = designPreviewOptions.filter((item) => {
return item.dataSource === currentDataName;
});
dataOptions.sort((a, b) => b.widthDimension - a.widthDimension);
let size = window.responsiveState.breakpoint;
if (size === '100%') {
size = window.innerWidth;
}
return dataOptions.find((item) => {
return size >= item.widthDimension;
});
}
function responsiveModeChangesHandler(e) {
if (e.data !== 'responsiveUpdate') {
return;
}
setPreviewOption(getDesignPreviewImage(state.dataName, state.designPreview));
}
}
function copyToClipboard(e, context) {
e.stopPropagation();
if (typeof context !== 'string') {
context = JSON.stringify(context, null, 2);
}
if (navigator.clipboard && window.isSecureContext) {
// This case will not work on non-https pages.
return navigator.clipboard.writeText(context);
} else {
let textArea = document.createElement("textarea");
textArea.value = context;
textArea.style.position = "fixed";
textArea.style.left = "-999999px";
textArea.style.top = "-999999px";
document.body.appendChild(textArea);
// textArea.focus();
textArea.select();
return new Promise((res, rej) => {
document.execCommand('copy') ? res() : rej();
textArea.remove();
e.target.focus()
});
}
}
export function setupDataOptions(rootAttributes) {
// INIT
const wrapper = document.createElement('div');
document.querySelector('.page_toolbar__left').prepend(wrapper)
const root = ReactDOM.createRoot(wrapper);
const html = (<DataOptions rootAttributes={rootAttributes}/>);
root.render(html);
}
@@ -0,0 +1,146 @@
import React, {createRef, useCallback, useEffect, useState} from 'react';
import {PreviewButtonStyle, PreviewStyle} from "./design-preview.style.js";
let startDragPosition = {x: 0, y: 0};
export function DesignPreview({previewOption = {widthDimension: 0}}) {
const reference = createRef();
const [active, setActive] = useState(false);
const [position, setPosition] = useState({x: getInitialXPosition(previewOption), y: 0});
const [opacity, setOpacity] = useState(85);
const keyDownHandler = useCallback(
function (e) {
if (!active) {
return;
}
const step = e.shiftKey ? 10 : 1;
if (e.key === 'ArrowUp') {
updatePosition({y: step * -1}, true)
} else if (e.key === 'ArrowDown') {
updatePosition({y: step}, true)
} else if (e.key === 'ArrowLeft') {
updatePosition({x: step * -1}, true)
} else if (e.key === 'ArrowRight') {
updatePosition({x: step}, true)
}
}, [position, active]);
useEffect(() => {
document.addEventListener('keydown', keyDownHandler);
return () => {
document.removeEventListener('keydown', keyDownHandler)
}
}, [keyDownHandler]);
useEffect(() => {
updatePosition({x: getInitialXPosition(previewOption), y: 0});
window.addEventListener('message', responsiveModeChangesHandler);
return () => window.removeEventListener('message', responsiveModeChangesHandler);
}, [previewOption.widthDimension]);
if (!previewOption) {
return;
}
const disabled = !previewOption.url;
const classNames = [];
if (!disabled && active) {
classNames.push('active');
}
if (disabled) {
classNames.push('disabled');
}
const handleOpacityChange = (e) => {
setOpacity(e.target.value);
}
return <>
<PreviewButtonStyle onClick={() => toggle()} className={classNames.join(' ')} disabled={disabled}/>
{active && !disabled &&
<>
<input type="range" value={opacity} min="0" max="100" onChange={handleOpacityChange}/>
<PreviewStyle onMouseUp={e => stop(e)} style={{opacity: opacity / 100}}>
<img src={previewOption.url} alt="" ref={reference}
onMouseDown={e => start(e)}
draggable={false}
style={{top: position.y + 'px', left: position.x + 'px'}}
/>
</PreviewStyle>
</>
}
</>
//
// Functions
//
function start(e) {
startDragPosition.x = e.clientX;
startDragPosition.y = e.clientY;
document.body.addEventListener('mousemove', handler);
}
function stop(e) {
const target = reference.current;
updatePosition({x: target.offsetLeft, y: target.offsetTop});
document.body.removeEventListener('mousemove', handler);
}
function handler(e) {
const target = reference.current;
if (target) {
const updatedPosition = {
x: target.offsetLeft - (startDragPosition.x - e.clientX),
y: target.offsetTop - (startDragPosition.y - e.clientY),
};
target.style.left = updatedPosition.x + 'px';
target.style.top = updatedPosition.y + 'px';
startDragPosition.x = e.clientX;
startDragPosition.y = e.clientY;
}
}
function toggle() {
setActive(!active);
}
function updatePosition({x, y}, relative = false) {
x = typeof x === 'undefined' ? position.x : relative ? position.x + x : x;
y = typeof y === 'undefined' ? position.y : relative ? position.y + y : y;
setPosition({x, y})
}
function getInitialXPosition(previewOption) {
if (!previewOption) {
return 0;
}
const scrollbarOffset = 7;
return window.innerWidth / 2 - previewOption.widthDimension / 2 - scrollbarOffset;
}
function responsiveModeChangesHandler(e) {
if (e.data !== 'responsiveUpdate') {
return;
}
if (window.responsiveState.breakpoint === '100%') {
updatePosition({x: getInitialXPosition(previewOption), y: 0});
}
}
}
@@ -0,0 +1,115 @@
import styled from "styled-components";
export const SidebarStyle = styled.div`
--sidebarWidth: 360px;
position: fixed;
top: 0;
bottom: 0;
left: calc(var(--sidebarWidth) * -1);
width: var(--sidebarWidth);
background-color: #E2E8F0;
border-right: 1px solid #CBD5E0;
padding: 0 0.75rem 0.75rem;
box-sizing: border-box;
overflow: hidden;
transition: left .2s ease-in-out, visibility .2s ease-in-out;
visibility: hidden;
color: #333;
display: flex;
flex-direction: column;
align-items: flex-start;
&.active {
left: 0;
visibility: visible;
}
pre {
overflow-x: auto;
padding: 0.5rem;
background-color: #EDF2F7;
border-radius: 4px;
color: #333;
border: 1px solid #cbd5e0;
width: 100%;
box-sizing: border-box;
}
`;
export const SidebarHeaderStyle = styled.header`
width: 100%;
min-height: 34px;
padding: 0.5rem 0;
display: flex;
align-items: center;
justify-content: flex-end;
`;
export const SidebarButtonToggleStyle = styled.button`
--size: 1.5rem;
cursor: pointer;
border: 0;
background-image: url("/scripts/dist/toolbar/images/icon-json.svg");
background-repeat: no-repeat;
background-size: calc(var(--size) - 0.15rem);
background-position: center 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;
`;
export const SidebarCloseButtonStyle = styled.button`
--size: 1.5rem;
cursor: pointer;
border: 0;
background-image: url("/scripts/dist/toolbar/images/icon-close.svg");
background-repeat: no-repeat;
background-size: calc(var(--size) - 0.15rem);
background-position: center 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;
`;
export const SidebarDataOptionsStyle = styled.div`
margin-top: 0.5rem;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
box-sizing: border-box;
select {
flex: 1 1;
display: block;
appearance: none;
border: 1px solid #cbd5e0;
padding: 0.5rem;
color: #333;
border-radius: 4px;
background-color: #edf2f7;
background-image: url("/scripts/dist/toolbar/images/icon-dropdown-arrow.svg");
background-position: right 0.75rem center;
background-repeat: no-repeat;
background-size: 0.5rem;
}
label {
display: block;
}
`;
@@ -0,0 +1,60 @@
import styled from "styled-components";
export const PreviewButtonStyle = styled.button`
--size: 1.5rem;
cursor: pointer;
border: 0;
background-image: url("/scripts/dist/toolbar/images/icon-preview.svg");
background-repeat: no-repeat;
background-size: calc(var(--size) - 0.15rem);
background-position: center 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;
&.active {
background-image: url("/scripts/dist/toolbar/images/icon-preview-disabled.svg");
}
&.disabled {
opacity: 0.25;
cursor: default;
}
`;
export const PreviewStyle = styled.div`
position: fixed;
top: calc(var(--top_panel_height) - 2px);
bottom: 0;
left: 0;
right: 14px; // Scrollbars
background-color: rgba(0, 0, 0, 0.1);
display: flex;
justify-content: center;
overflow: hidden;
align-items: baseline;
img {
width: auto;
height: auto;
display: block;
cursor: move;
position: absolute;
}
`;
export const PreviewImageStyle = styled.div`
height: 100%;
width: 100%;
background-repeat: no-repeat;
background-position: top;
cursor: move;
`;
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
<path
d="M12.45 38.7 9.3 35.55 20.85 24 9.3 12.5l3.15-3.2L24 20.8 35.55 9.3l3.15 3.2L27.2 24l11.5 11.55-3.15 3.15L24 27.2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 218 B

@@ -0,0 +1,7 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px"
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
<path id="XMLID_225_" d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,0.001l-139.39,139.393L25.607,79.393
c-5.857-5.857-15.355-5.858-21.213,0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,4.393,10.606,4.393
s7.794-1.581,10.606-4.394l149.996-150C331.465,94.749,331.465,85.251,325.607,79.393z"/>
</svg>

After

Width:  |  Height:  |  Size: 553 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
<path
d="M28.6 40.75V36.2h4.75q1.2 0 2.05-.825.85-.825.85-2.075v-3.85q0-1.8 1.075-3.225T40.15 24.2v-.4q-1.75-.55-2.825-2-1.075-1.45-1.075-3.3v-3.85q0-1.2-.85-2.05-.85-.85-2.05-.85H28.6V7.2h5.95q2.6 0 4.425 1.825Q40.8 10.85 40.8 13.5v3.85q0 1.2.825 2.05.825.85 2.075.85h1.1v7.5h-1.1q-1.25 0-2.075.85-.825.85-.825 2.05v3.85q0 2.65-1.825 4.45-1.825 1.8-4.425 1.8Zm-15.1 0q-2.7 0-4.475-1.8-1.775-1.8-1.775-4.45v-3.85q0-1.2-.85-2.05-.85-.85-2.05-.85h-1.1v-7.5h1.1q1.2 0 2.05-.85.85-.85.85-2.05V13.5q0-2.65 1.8-4.475Q10.85 7.2 13.5 7.2h5.95v4.55H14.7q-1.25 0-2.075.85-.825.85-.825 2.05v3.85q0 1.85-1.1 3.3-1.1 1.45-2.85 2v.4q1.75.6 2.85 2.025 1.1 1.425 1.1 3.225v3.85q0 1.25.825 2.075.825.825 2.075.825h4.75v4.55Z"/>
</svg>

After

Width:  |  Height:  |  Size: 803 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
<path
d="M9.5 43.05q-1.85 0-3.2-1.35t-1.35-3.2v-29q0-1.9 1.35-3.25T9.5 4.9h13.35v4.6H9.5v29h29V25.15h4.6V38.5q0 1.85-1.35 3.2t-3.25 1.35ZM20.3 30.9l-3.15-3.2 18.2-18.2h-9.5V4.9H43.1v17.25h-4.6V12.7Z"/>
</svg>

After

Width:  |  Height:  |  Size: 294 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="m31.7 26.4-3.85-3.8q1.05-2.2-.625-3.45t-3.125-.3l-3.5-3.55q.75-.4 1.65-.6.9-.2 1.75-.2 3.55 0 6.025 2.475Q32.5 19.45 32.5 23q0 .8-.2 1.8t-.6 1.6Zm7.4 7.5-2.75-2.8q2.2-1.75 3.825-3.85T42.8 23q-2.6-5.5-7.525-8.75Q30.35 11 24.5 11q-2.1 0-4.1.35-2 .35-2.95.8l-3.3-3.3q1.75-.8 4.625-1.4 2.875-.6 5.475-.6 7.35 0 13.6 4.25t9.3 11.9q-1.25 3.3-3.375 6.075Q41.65 31.85 39.1 33.9Zm1.25 11.35-7.7-7.6q-1.75.7-4 1.1-2.25.4-4.65.4-7.55 0-13.825-4.275Q3.9 30.6.85 23q.9-2.55 2.75-5.15 1.85-2.6 4.3-5L1.75 6.8l2.5-2.6L42.7 42.65ZM10.8 15.7Q9 17.15 7.5 19.125 6 21.1 5.2 23q2.6 5.55 7.65 8.775Q17.9 35 24.4 35q1.35 0 2.775-.15 1.425-.15 2.225-.55l-3.2-3.2q-.4.2-1.025.3-.625.1-1.175.1-3.5 0-6-2.45T15.5 23q0-.55.075-1.15.075-.6.225-1.05Zm16.15 6.4Zm-6.85 3.45Z"/></svg>

After

Width:  |  Height:  |  Size: 825 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M24 31.5q3.55 0 6.025-2.475Q32.5 26.55 32.5 23q0-3.55-2.475-6.025Q27.55 14.5 24 14.5q-3.55 0-6.025 2.475Q15.5 19.45 15.5 23q0 3.55 2.475 6.025Q20.45 31.5 24 31.5Zm0-3.7q-2 0-3.4-1.4T19.2 23q0-2 1.4-3.4t3.4-1.4q2 0 3.4 1.4t1.4 3.4q0 2-1.4 3.4T24 27.8Zm0 11.35q-7.7 0-13.9-4.5T.85 23q3.05-7.15 9.25-11.65T24 6.85q7.7 0 13.9 4.5T47.15 23q-3.05 7.15-9.25 11.65T24 39.15ZM24 23Zm0 12q6 0 11.05-3.275Q40.1 28.45 42.75 23q-2.65-5.45-7.675-8.725Q30.05 11 24 11q-6 0-11.05 3.275Q7.9 17.55 5.2 23q2.7 5.45 7.725 8.725Q17.95 35 24 35Z"/></svg>

After

Width:  |  Height:  |  Size: 604 B

+1 -1
View File
@@ -39,7 +39,7 @@ function Publish(props = {}) {
export function setupPublish(rootAttributes) {
// INIT
const wrapper = document.createElement('div');
document.querySelector('.page_toolbar__right').prepend(wrapper)
document.querySelector('.page_toolbar__right').append(wrapper)
const root = ReactDOM.createRoot(wrapper);
const html = (<Publish rootAttributes={rootAttributes}/>);
+18 -12
View File
@@ -22,14 +22,12 @@ 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));
const [mode, setMode] = useState(initialState.mode);
const [breakpoint, setBreakpoint] = useState(initialState.breakpoint);
useEffect(() => {
// Update the document title using the browser API
updateController(state);
});
updateController();
}, [mode, breakpoint]);
const previewFrame = props.rootAttributes.previewFrame;
return render();
@@ -50,22 +48,30 @@ function Responsive(props = {}) {
function selectMode(mode, breakpoint) {
if (mode === 'reset') {
updateState(initialState);
setMode(initialState.mode);
setBreakpoint(initialState.breakpoint);
return;
}
updateState({mode, breakpoint})
setMode(mode);
setBreakpoint(breakpoint);
}
function isActive(mode) {
return mode === state.mode;
function isActive(requestedMode) {
return mode === requestedMode;
}
function updateController() {
const unit = typeof state.breakpoint === 'string' ? '' : 'px';
previewFrame.style.setProperty('--breakpoint', state.breakpoint + unit);
let frameBreakpoint = breakpoint;
if (typeof frameBreakpoint !== 'string') {
frameBreakpoint = frameBreakpoint + 'px';
}
previewFrame.style.setProperty('--breakpoint', frameBreakpoint);
previewFrame.classList.add('has-breakpoint');
window.postMessage('responsiveUpdate');
window.responsiveState = {mode, breakpoint};
}
}
+7 -1
View File
@@ -3,7 +3,7 @@
--btn-background-color: white;
display: inline-block;
padding: calc(0.375rem - 2px) 0.75rem;
font-size: 1rem;
font-size: 0.875rem;
line-height: 1.5;
text-align: center;
text-decoration: none;
@@ -22,4 +22,10 @@
--btn-color: white;
--btn-background-color: #333;
}
&--secondary {
border-color: #cbd5e0;
--btn-color: #333;
--btn-background-color: #edf2f7;
}
}
@@ -1,16 +1,24 @@
.preview {
overflow-y: scroll;
height: calc(100% - var(--top_panel_height));
position: relative;
}
#preview_frame {
display: block;
margin-right: auto;
margin-left: auto;
min-height: 100%;
--top_spacing: 0px;
--breakpoint_top_spacing: 30px;
margin-top: var(--top_spacing);
height: calc(100% - var(--top_panel_height) - var(--top_spacing));
height: 100%;
background-color: white;
border: 1px solid #E2E8F0;
border: 0;
outline: 1px solid #E2E8F0;
transition: max-width .3s ease-in-out, width .3s ease-in-out, margin-top .3s ease-in-out;
@@ -19,7 +27,6 @@
width: 100%;
max-width: var(--breakpoint);
box-sizing: border-box;
}
}
+54 -10
View File
@@ -14,7 +14,7 @@ body {
--btn-background-color: white;
display: inline-block;
padding: calc(0.375rem - 2px) 0.75rem;
font-size: 1rem;
font-size: 0.875rem;
line-height: 1.5;
text-align: center;
text-decoration: none;
@@ -32,6 +32,11 @@ body {
--btn-color: white;
--btn-background-color: #333;
}
.btn--secondary {
border-color: #cbd5e0;
--btn-color: #333;
--btn-background-color: #edf2f7;
}
.overlay {
position: fixed;
@@ -50,28 +55,32 @@ body {
.page_toolbar {
--gap: 2.5rem;
font-size: 14px;
min-height: 50px;
box-sizing: border-box;
font-family: Arial, sans-serif;
background-color: #EDF2F7;
padding: var(--top_panel_vertical_height) 1rem;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
z-index: 1;
}
.page_toolbar .page_toolbar__middle {
.page_toolbar__middle {
display: flex;
justify-content: center;
gap: var(--gap);
align-items: center;
}
.page_toolbar .page_toolbar__middle > div {
.page_toolbar__middle > div {
position: relative;
}
@media (max-width: 1024px) {
.page_toolbar .page_toolbar__middle > div {
.page_toolbar__middle > div {
display: none;
}
}
.page_toolbar .page_toolbar__middle > div:not(:first-child):after {
.page_toolbar__middle > div:not(:first-child):after {
content: "|";
position: absolute;
left: calc(var(--gap) / 2 * -1);
@@ -80,7 +89,7 @@ body {
line-height: 1;
transform: translateY(-50%);
}
.page_toolbar .page_toolbar__middle > div select {
.page_toolbar__middle > div select {
font-size: 0.85em;
margin: 0;
display: inline-block;
@@ -90,28 +99,63 @@ body {
padding: 4px 8px;
}
@media (max-width: 1024px) {
.page_toolbar .page_toolbar__middle__data_options {
.page_toolbar__middle__data_options {
display: block;
}
}
.page_toolbar__left, .page_toolbar__right {
display: flex;
align-items: center;
gap: 1rem;
min-width: 480px;
}
.page_toolbar__right {
justify-content: flex-end;
}
.preview {
overflow-y: scroll;
height: calc(100% - var(--top_panel_height));
position: relative;
}
#preview_frame {
display: block;
margin-right: auto;
margin-left: auto;
min-height: 100%;
--top_spacing: 0px;
--breakpoint_top_spacing: 30px;
margin-top: var(--top_spacing);
height: calc(100% - var(--top_panel_height) - var(--top_spacing));
height: 100%;
background-color: white;
border: 1px solid #E2E8F0;
border: 0;
outline: 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;
}
.open_in_new_tab {
--size: 1.5rem;
width: var(--size);
height: var(--size);
background-image: url("/scripts/toolbar/images/icon-open-new-tab.svg");
background-repeat: no-repeat;
background-size: calc(var(--size) - 0.15rem);
background-position: center center;
background-color: initial;
cursor: pointer;
border: 0;
font-size: 1px;
color: rgba(0, 0, 0, 0);
line-height: 1;
display: block;
border-radius: 0.25rem;
outline: none;
}
/*# sourceMappingURL=page--main.css.map */
+1 -1
View File
@@ -1 +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"}
{"version":3,"sourceRoot":"","sources":["page--main.scss","_buttons.scss","_overlay.scss","_page--preview.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;;AAGF;EACE;EACA;EACA;;;AC5BJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AFIF;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAEA;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;;;AAKN;EAEE;EACA;EACA;EACA;;AAGF;EACE;;;AGlFJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EAEA;EACA;EAEA;;AAEA;EACE;EAEA;EACA;;;AH6DJ;EACE;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA","file":"page--main.css"}
+39 -2
View File
@@ -16,6 +16,8 @@ body {
.page_toolbar {
--gap: 2.5rem;
font-size: 14px;
min-height: 50px;
box-sizing: border-box;
font-family: Arial, sans-serif;
background-color: #EDF2F7;
@@ -25,7 +27,10 @@ body {
justify-content: space-between;
align-items: center;
.page_toolbar__middle {
position: relative;
z-index: 1;
&__middle {
display: flex;
justify-content: center;
gap: var(--gap);
@@ -66,7 +71,39 @@ body {
}
}
&__left,
&__right {
display: flex;
align-items: center;
gap: 1rem;
min-width: 480px;
}
&__right {
justify-content: flex-end;
}
}
@import "page--breakpoints";
@import "page--preview";
.open_in_new_tab {
--size: 1.5rem;
width: var(--size);
height: var(--size);
background-image: url("/scripts/toolbar/images/icon-open-new-tab.svg");
background-repeat: no-repeat;
background-size: calc(var(--size) - 0.15rem);
background-position: center center;
background-color: initial;
cursor: pointer;
border: 0;
font-size: 1px;
color: rgba(0, 0, 0, 0);
line-height: 1;
display: block;
border-radius: 0.25rem;
outline: none;
}
+1 -9
View File
@@ -1,19 +1,11 @@
body {
margin: 0;
overflow-y: hidden;
}
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 -1
View File
@@ -1 +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"}
{"version":3,"sourceRoot":"","sources":["page--view.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA","file":"page--view.css"}
+9 -8
View File
@@ -1,17 +1,18 @@
body {
margin: 0;
overflow-y: hidden;
}
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;
}
// Container rules must be provided by the projectStyles (theme).
//.container {
// max-width: 1200px;
// margin-left: auto;
// margin-right: auto;
// padding-left: 15px;
// padding-right: 15px;
//}
+2238 -8879
View File
File diff suppressed because it is too large Load Diff
+8 -15
View File
@@ -1,6 +1,6 @@
{
"name": "@axe-web/create-block",
"version": "1.0.11",
"name": "@axe-web/block-dev-tool",
"version": "1.0.18",
"author": {
"name": "AXE-WEB",
"email": "office@axe-web.com",
@@ -8,11 +8,9 @@
},
"scripts": {
"start": "component-dev",
"dev": "NODE_ENV=development node server.js",
"generate-block": "yo ./generators/block/index.js",
"create-block": "node ./create-block.js pull",
"dev": "NODE_ENV=development NODE_CONFIG_DIR=blocks/team/config BLOCK_NAME=team node server.js",
"build": "rollup --config rollup.config.js",
"build-platform": "NODE_ENV=development yo ./build.cjs",
"build-platform": "NODE_ENV=development NODE_CONFIG_DIR=blocks/team/config BLOCK_NAME=team node ./build.js",
"build-platform-cli": "component-build",
"dev-js": "rollup --config rollup.config.js --watch"
},
@@ -22,7 +20,6 @@
"@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",
@@ -37,15 +34,13 @@
"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",
"plugin-error": "^2.0.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"
"yeoman-generator": "^5.6.1"
},
"devDependencies": {
"@babel/preset-react": "^7.18.6",
@@ -62,14 +57,12 @@
"styled-components": "^5.3.5"
},
"bin": {
"create-block": "./create-block.js",
"component-dev": "./server.js",
"component-build": "./build.js"
},
"files": [
"generators/block/templates/.gitignore",
"generators/block/**/*",
"helpers.js",
"layouts/**/*",
"helpers.js"
"platforms/**/*"
]
}
+15 -1
View File
@@ -1,5 +1,4 @@
import {readFile, writeFile, mkdir, copyFile} from "fs/promises";
import {capitalize} from "../../helpers.js";
export async function buildHubspot(blockName) {
const distPath = `./exports/hubspot/${blockName}.module`;
@@ -225,3 +224,18 @@ function convertToHubspotField(field = {}) {
});
}
}
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 -3
View File
@@ -54,14 +54,15 @@ class Component_Builder {
public string $component_name = '';
private string $module_path = '';
function __construct( $component_name, $module_path ) {
function __construct( $component_name, $module_path, $project_path ) {
$this->module_path = $module_path;
$this->project_path = $project_path;
$this->component_name = $component_name;
$this->register_default_handlebar_helpers();
}
function build(): void {
$root_path = __DIR__ . '/' . $this->module_path . '/../..';
$root_path = __DIR__ . '/' . $this->module_path . '/../../' . $this->project_path;
$file_name = $this->get_handlebars_template( "$root_path/src/$this->component_name.template.hbs" );
$output_folder = $root_path . '/exports/wordpress/templates';
@@ -122,4 +123,4 @@ class Component_Builder {
}
}
( new Component_Builder( $argv[1], $argv[2] ) )->build();
( new Component_Builder( $argv[1], $argv[2], $argv[3] ) )->build();
@@ -0,0 +1,31 @@
<?php
use Core\Global_Functions;
class <%= blockClassModel %>_Component {
public function __construct() {
add_action( 'wp_enqueue_scripts', [ $this, 'register_assets' ] );
}
function register_assets(): void {
wp_enqueue_style( '<%= blockFilename %>',
get_template_directory_uri() . '/components/partials/<%= blockFilename %>/templates/styles/<%= blockFilename %>.min.css',
[ 'style-wp' ],
Global_Functions::get_current_version_number()
);
wp_enqueue_script( '<%= blockFilename %>',
get_template_directory_uri() . '/components/partials/<%= blockFilename %>/templates/scripts/<%= blockFilename %>.min.js',
[ 'jquery', 'swiper' ],
Global_Functions::get_current_version_number(),
true
);
}
public function render( $args = [] ): void {
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( array_merge( [], $args ), self::class );
echo apply_filters( 'the_content', wpautop( $output ) );
}
}
@@ -0,0 +1,68 @@
<?php
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>;
class <%= blockClassModel %>_Component extends \Core\Component {
public function get_content( $args = [] ): string {
$args = array_merge( Helpers\<%= blockClassModel %>_Defaults::default_args(), $args );
$args = Helpers\<%= blockClassModel %>_API::prepare_args( $args );
<% if (templateFormat === 'hbs') { %>return $this->get_component_template( __DIR__ . '/templates/<%= blockFilename %>.template.hbs', $args );<% } %>
<% if (templateFormat === 'php') { %>
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( array_merge( [], $args ), self::class );
echo apply_filters( 'the_content', wpautop( $output ) );
<% } %>
}
<% if (!include_acf_block && !include_native_gutenberg_block) { %>
public function register_assets() {
$this->add_style( __DIR__ . '/templates/styles/<%= blockFilename %>.min.css' );
<% if (include_script) { %>$this->add_script( __DIR__ . '/templates/scripts/<%= blockFilename %>.js' );<% } %>
}
<% } %><% if (include_elementor_widget) { %> function register_custom_logic(): void {
add_action( 'elementor/widgets/widgets_registered', [ $this, 'register_elementor_widget' ] );
}
<% } %><% if (include_elementor_widget) { %> function register_elementor_widget( $widgets_manager ) {
$widgets_manager->register_widget_type( new Helpers\<%= blockClassModel %>_Elementor_Widget() );
}
<% } %><% if (include_acf_block) { %> function register_acf_block() {
$this->register_block( __DIR__ . "/<%= blockFilename %>.block.json", [
'enqueue_assets' => function () {
wp_enqueue_style( '<%= blockFilename %>', \Core\Global_Functions::get_file_url( __DIR__ . '/templates/styles/<%= blockFilename %>.min.css', true ), [], get_blocks_version() );<% if (include_script) { %>
wp_enqueue_script( '<%= blockFilename %>', \Core\Global_Functions::get_file_url( __DIR__ . '/templates/scripts/<%= blockFilename %>.js', true ), ['assets-script'], get_blocks_version() );<% } %>
},
'default' => Helpers\<%= blockClassModel %>_Defaults::default_args(),
'supports' => [
//'jsx' => true,
'color' => [
'background' => true,
'text' => true,
],
'spacing' => [
'margin' => [ 'top', 'bottom' ],
'padding' => [ 'top', 'bottom' ]
],
]
] );
}
<% } %><% if (include_native_gutenberg_block) { %> function register_native_gutenberg_block() {
register_block_type( __DIR__ . '/templates/gutenberg-block/block.json' );<% if (include_script) { %>
add_action( 'wp_enqueue_scripts', function () {
$asset_file_front = include( plugin_dir_path( __FILE__ ) . '/templates/gutenberg-block/build/front.asset.php' );
wp_enqueue_script(
'gutenberg-<%= blockFilename %>-scripts-front',
plugins_url( 'templates/gutenberg-block/build/front.js', __FILE__ ),
$asset_file_front['dependencies'],
$asset_file_front['version'],
true
);
} );<% } %>
}<% } %>
}
@@ -0,0 +1,14 @@
<?php
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
class <%= blockClassModel %>_API {
static function prepare_args( $args = [] ) {
// $args = array_merge( [], $args );
return $args;
}
}
@@ -0,0 +1,15 @@
<?php
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
/**
* Component's default args.
*/
class <%= blockClassModel %>_Defaults {
public static function default_args(): array {
return [
'title' => '<%= title %>',
];
}
}
+73
View File
@@ -0,0 +1,73 @@
import path from "path";
import {mkdir, copyFile} from "fs/promises";
import {capitalize, createFiles, getBlockName, getConfigs, readJSONFile} from "../../helpers.js";
import {fileURLToPath} from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const {projectPath} = getConfigs();
export async function buildWordPress(isBlock = false) {
const distPath = path.join(projectPath, 'exports', 'wordpress');
await mkdir(distPath, {recursive: true})
const jsonFilePath = path.join(projectPath, 'block.json');
await copyFile(jsonFilePath, `${distPath}/block.json`)
let data = await readJSONFile(jsonFilePath);
Object.assign(data, getBlockName(data.name));
// let data = await readJSONFile(path.join(projectPath, `block.json`));
const title = capitalize(data.name);
const owner = capitalize(data.project);
data = Object.assign(data, {
title,
blockClassModel: title.replace(/ /ig, '_'),
blockFilename: title.toLowerCase().replace(/ /ig, '-'),
blockClassName: title.toLowerCase().replace(/ /ig, '_'),
owner,
ownerClass: owner.replace(/ /ig, '_'),
ownerFilename: owner.toLowerCase().replace(/ /ig, '-'),
templateFormat: 'php',
include_acf_block: false,
include_native_gutenberg_block: false,
include_script: true,
include_elementor_widget: false,
});
if (isBlock) {
await createFiles(data, [{
from: `templates/Template_Component.php`,
to: `${data.blockClassModel}_Component.php`,
}], {
pathDist: distPath,
generatorsPath: path.join(__dirname)
});
await createFiles(data, [{
from: `templates/helpers/Template_API.php`,
to: `helpers/${data.blockClassModel}_API.php`,
}], {
pathDist: distPath,
generatorsPath: path.join(__dirname)
});
await createFiles(data, [{
from: `templates/helpers/Template_Defaults.php`,
to: `helpers/${data.blockClassModel}_Defaults.php`,
}], {
pathDist: distPath,
generatorsPath: path.join(__dirname)
});
} else {
await createFiles(data, [{
from: `templates/Template_Basic_Component.php`,
to: `${data.blockClassModel}_Component.php`,
}], {
pathDist: distPath,
generatorsPath: path.join(__dirname)
});
}
}
+191 -108
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env node
import path from 'path';
import fetch from "node-fetch";
import express from 'express';
import {create} from 'express-handlebars';
@@ -19,27 +20,25 @@ import {sanitizeUrl} from "@braintree/sanitize-url";
import sanitizeHtml from 'sanitize-html';
import {escape} from "lodash-es";
import archiver from 'archiver';
import {getBlockConfigs, getConfigs, readJSONFile} from "./helpers.js";
import PluginError from 'plugin-error';
/**
* Constants
*/
const isDev = process.env.NODE_ENV === 'development' || (config.has('isDev') && config.get('isDev')); // Check README file in case you get "missing files" error.
const {isDev, modulesPath, projectPath, developmentBlockName} = getConfigs();
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);
buildStyleFiles()
buildScriptFiles()
/**
* Init server
*/
let port = 3000;
let port = 3000; // This variable is used in `*.hbs` and it will be updated once BrowserSync is ready.
let previewFrameUrl = `http://localhost:${port}`; // This variable is used in `*.hbs` and it will be updated once BrowserSync is ready.
const dataFiles = prepareListOfDataFiles(await fs.readdir(path.join(projectPath, 'data')));
const app = express();
const sass = gulpSass(dartSass);
const hbs = create({
extname: '.hbs', defaultLayout: false, partialsDir: ['.'], helpers: {
@@ -58,21 +57,27 @@ const hbs = create({
app.engine('.hbs', hbs.engine);
app.set('view engine', '.hbs');
app.set('views', projectDir + 'layouts');
app.set('views', path.join(modulesPath, 'layouts'));
const dataFiles = prepareListOfDataFiles(await fs.readdir('./data'));
//
// Routes
//
app.get('/', async (req, res) => {
let jsonFileName = req.query.data ? req.query.data : 'default';
const data = await getBlockConfigs(jsonFileName);
const data = await getBlockConfigs(jsonFileName, {includeConfigs: true, projectPath, modulesPath, dataFiles});
if (data.error && data.errorMessage) {
return res.send(data.errorMessage);
}
const baseView = config.has('baseView') ? config.get('baseView') : 'container';
const baseViewUrl = `view/${baseView}`;
data.helpers = {
port: port,
include_partial: (path) => projectDir + path,
baseView: config.has('baseView') ? config.get('baseView') : 'container',
port,
include_partial: (filesPath) => path.join(modulesPath, filesPath),
baseView,
previewFrameUrl: `${previewFrameUrl}/${baseViewUrl}`,
}
res.render('index', data);
@@ -80,14 +85,18 @@ app.get('/', async (req, res) => {
app.get('/view/:baseView', async (req, res) => {
let jsonFileName = req.query.data ? req.query.data : 'default';
const data = await getBlockConfigs(jsonFileName);
const data = await getBlockConfigs(jsonFileName, {includeConfigs: true, projectPath, modulesPath, dataFiles});
if (data.error && data.errorMessage) {
return res.send(data.errorMessage);
}
const blockName = config.has('blockName') ? config.get('blockName') : developmentBlockName;
data.helpers = {
include_partial: (path) => projectDir + path,
include_block_template: (path) => 'src/' + (config.has('blockName') ? config.get('blockName') : 'development') + '.template',
include_partial: (filesPath) => path.join(modulesPath, filesPath),
include_block_template: () => path.join(projectPath, 'src', `${blockName}.template`),
section_class: `${blockName}--${jsonFileName}`,
base_url: '/'
}
const baseView = req.params.baseView ?? 'container';
@@ -96,7 +105,7 @@ app.get('/view/:baseView', async (req, res) => {
});
app.get('/publish', async (req, res) => {
const data = await readJSONFile('./block.json');
const data = await readJSONFile(path.join(projectPath, `block.json`));
let responseData;
try {
@@ -119,7 +128,7 @@ app.get('/publish', async (req, res) => {
if (responseData.uploadUrl) {
await zipProject();
const body = await fs.readFile('./dist.zip');
const body = await fs.readFile(path.join(projectPath, 'dist.zip'));
const response = await fetch(`${responseData.uploadUrl}`, {
method: 'PUT',
body,
@@ -129,7 +138,7 @@ app.get('/publish', async (req, res) => {
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');
await fs.unlink(path.join(projectPath, 'dist.zip'));
return;
}
}
@@ -137,48 +146,117 @@ app.get('/publish', async (req, res) => {
res.json({success: true});
await fs.unlink('./dist.zip');
await fs.unlink(path.join(projectPath, 'dist.zip'));
});
app.use(express.static('src'));
app.use(express.static(projectDir + 'layouts'));
app.get('/data', async (req, res) => {
let jsonDataFileName = req.query.name ? req.query.name : 'default';
const listener = app.listen(0, async () => {
const PORT = listener.address().port;
await open(`http://localhost:${PORT}`);
const dataFiles = prepareListOfDataFiles(await fs.readdir(path.join(projectPath, 'data')));
const data = await getBlockConfigs(jsonDataFileName, {projectPath, modulesPath, dataFiles});
const designPreviewFiles = getListOfDesignPreviewFiles(jsonDataFileName, await fs.readdir(path.join(projectPath, 'design', 'preview')));
console.log(`The web server has started on port ${PORT}`);
const bs = browserSync.create();
gulp.watch(['src/**/*.js', 'src/**/*.mjs', '!src/**/*.min.js'], {delay: 400}, gulp.series([buildScriptFiles, function (cb) {
browserSyncReload(bs, 'js', 'Script Files Change');
return cb();
}]));
gulp.watch('src/**/*.scss', {delay: 400}, gulp.series([buildStyleFiles, function (cb) {
browserSyncReload(bs, 'css', 'Style Files Change');
return cb();
}]));
// bs.watch("src/**/*.js", function (event, file) {});
// bs.watch("src/**/*.css", function (event, file) {});
bs.watch("src/**/*.hbs", function (event, file) {
browserSyncReload(bs, '', 'Template File Change: ' + file)
return res.json({
dataOptions: dataFiles,
designPreview: designPreviewFiles,
data,
});
bs.init({
proxy: `http://localhost:${PORT}`, open: false
}, (err, bs) => {
port = bs.server._connectionKey.split(':').pop();
});
});
/**
* Functions
*/
// Errors handler
app.use(handleSyntaxErrors);
// Static Files
app.use(express.static(path.join(projectPath, 'src')));
app.use(express.static(path.join(projectPath, 'design')));
app.use(express.static(path.join(modulesPath, 'layouts')));
// Setup Gulp
await buildAssetFiles();
// BrowserSync
const bsOptions = await startBrowserSync();
port = bsOptions.port;
previewFrameUrl = bsOptions.previewFrameUrl;
await open(bsOptions.devToolUrl);
//
// Functions
//
function getListOfDesignPreviewFiles(jsonDataFileName, previewFiles) {
return previewFiles
.filter(fileName => {
return fileName.startsWith(jsonDataFileName + '.');
})
.map(fileName => {
const fileData = fileName.split('.');
const fileFormat = fileData.pop();
const previewSize = fileData.pop();
return {
dataSource: jsonDataFileName,
widthDimension: Number.parseInt(previewSize, 10),
url: `/preview/${fileName}`,
};
});
}
function startBrowserSync() {
return new Promise((resolve, reject) => {
const listener = app.listen(0, async () => {
const PORT = listener.address().port;
console.log(`The web server has started on port ${PORT}`);
const bs = browserSync.create();
const files = getJSBundleFiles();
gulp.watch(files, {delay: 400}, gulp.series(['build-script-files', function (cb) {
browserSyncReload(bs, 'js', 'Script Files Change');
return cb();
}]));
gulp.watch(path.join(projectPath, 'src/**/*.scss'), {delay: 400}, gulp.series(['build-styling-files', function (cb) {
browserSyncReload(bs, 'css', 'Style Files Change');
return cb();
}]));
bs.watch("src/**/*.hbs", function (event, file) {
browserSyncReload(bs, '', 'Template File Change: ' + file)
});
bs.init({
proxy: `http://localhost:${PORT}`,
open: false
}, (err, bs) => {
if (err) {
return reject(err);
}
const options = bs.getOptions().toJS();
const urls = {
devTool: options.urls.local.replace(options.port, options.proxy.url.port),
previewFrame: options.urls.local,
};
// If local network is available.
if (options.urls.external) {
urls.devTool = options.urls.external.replace(options.port, options.proxy.url.port);
urls.previewFrame = options.urls.external;
}
resolve({
devToolUrl: urls.devTool,
previewFrameUrl: urls.previewFrame,
port: options.port
});
});
});
});
}
function browserSyncReload(bs, extension = '', message = '') {
if (isDev) {
@@ -193,26 +271,58 @@ function browserSyncReload(bs, extension = '', message = '') {
bs.reload(extension);
}
function buildScriptFiles() {
return gulp.src(['src/**/*.js', 'src/**/*.mjs', '!src/**/*.min.js'])
function getJSBundleFiles() {
return [path.join(projectPath, 'src/**/*.js'), path.join(projectPath, 'src/**/*.mjs'), '!' + path.join(projectPath, 'src/**/*.min.js')];
}
function buildScriptFiles(done) {
const files = getJSBundleFiles();
return gulp.src(files)
.pipe(sourcemaps.init({}))
.pipe(babel())
.pipe(gulp.src('vendor/*.js'))
.pipe(babel()).on('error', function (error) {
showError(new PluginError('JavaScript', error).toString());
done();
})
.pipe(gulp.src(path.join(projectPath, 'vendor/*.js')))
// .pipe(gulp.dest('src/'))
.pipe(uglify())
.pipe(rename({extname: '.min.js'}))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('src/'));
.pipe(gulp.dest(path.join(projectPath, 'src/')));
}
function buildStyleFiles() {
return gulp.src('src/**/*.scss')
function buildStyleFiles(done) {
return gulp.src(path.join(projectPath, 'src/**/*.scss'))
.pipe(sourcemaps.init({}))
.pipe(sass.sync().on('error', sass.logError))
.pipe(sass.sync({outputStyle: 'compressed'}).on('error', function (error) {
showError(new PluginError('SCSS', error.messageFormatted).toString());
// sass.logError(error);
done();
}))
// .pipe(gulp.dest('src/'))
.pipe(rename({extname: '.min.css'}))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('src/'))
.pipe(sourcemaps.write('.', {}))
.pipe(gulp.dest(path.join(projectPath, 'src')))
}
function buildAssetFiles() {
// Register tasks.
gulp.task('build-script-files', buildScriptFiles);
gulp.task('build-styling-files', buildStyleFiles);
// Run first build.
return new Promise((resolve) => {
gulp.series('build-script-files', 'build-styling-files', function (cb) {
resolve();
})();
});
}
function showError(errorMessage) {
console.log(errorMessage);
// TODO: Send this message to browser.
// So the developer can understand there is an error.
}
function prepareListOfDataFiles(dataFiles) {
@@ -226,46 +336,6 @@ function prepareListOfDataFiles(dataFiles) {
.sort();
}
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.
@@ -304,10 +374,23 @@ async function zipProject() {
// 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);
// append files from a subdirectory, putting its contents at the root of archive
archive.directory(path.join(projectPath, '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();
}
function handleSyntaxErrors(err, req, res, next) {
if (err) {
return res.render('error', {
helpers: {
include_partial: (filesPath) => path.join(modulesPath, filesPath),
},
err
});
}
next();
}