76 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: #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
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
79 changed files with 19052 additions and 10669 deletions
+1
View File
@@ -10,3 +10,4 @@ deploy-*.sh
# Custom
blocks
exports
+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
+74
View File
@@ -0,0 +1,74 @@
#!/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";
import {getConfigs} from "./helpers.js";
import {buildWordPress} from "./platforms/wordpress/wordpress-adapter.js";
import path from "path";
const {modulesPath, projectPath} = getConfigs();
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', 'WordPress Block', 'Hubspot', 'Hubspot Email', 'JavaScript', 'PHP'],
default: 'WordPress'
}
])
}
writing() {
new Promise((resolve => {
if (['WordPress', 'PHP'].includes(this.data.platform)) {
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(() => {
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);
-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(' ');
}
-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
-155
View File
@@ -1,155 +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.
### 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.
@@ -1,5 +0,0 @@
module.exports = {
cssUrl: "https://",
blockName: "<%= blockFilename %>",
baseView: "<%= baseView %>",
}
@@ -1,14 +0,0 @@
{
"title": "Option Two",
"content": "<p><b>Delivering top results to industry leaders:</b></p>",
"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,6 +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>",
"cta_text": "Our Success Stories",
"url": "https://google.com"
}
-10
View File
@@ -1,10 +0,0 @@
{
"name": "<%= blockFilename %>",
"version": "1.0.0",
"scripts": {
"start": "component-dev"
},
"devDependencies": {
"create-block-dev-tool": "git+https://roman-axe-web@bitbucket.org/axeweb/create-block-dev-tool.git#master"
}
}
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 = 16pixels
* 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 {
// Example of BEM usage.
}
}
&__content {
}
&__footer {
}
}
@@ -1,70 +0,0 @@
<section class="<%= blockClassName %>">
{{!
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;
}
</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>
+109
View File
@@ -0,0 +1,109 @@
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 '';
}
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) }}
+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>
+26
View File
@@ -0,0 +1,26 @@
<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: '{{ previewFrameUrl }}',
};
</script>
<div class="preview">
<iframe id="preview_frame" src="{{ previewFrameUrl }}" class="breakpoint"></iframe>
</div>
<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>
+8 -3
View File
@@ -1,4 +1,9 @@
<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>
<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}}
+8 -9
View File
@@ -1,15 +1,14 @@
<header class="page_toolbar">
<div class="page_toolbar__data_options">
<label for="data-options">Data Options: </label>
<select name="data" id="data-options">
{{#each config.dataFiles }}
<option value="{{ name }}" {{#if active }}selected="selected"{{/if}}>{{ name }}</option>
{{/each}}
</select>
<div class="page_toolbar__left"></div>
<div class="page_toolbar__middle">
<div>
Sizes: <b>1rem = {{ config.remToPx }}px</b>
</div>
</div>
<div>
Sizes: <b>1rem = {{ config.remToPx }}px</b>
<div class="page_toolbar__right">
<a href="{{ previewFrameUrl }}" target="_blank" class="open_in_new_tab"></a>
</div>
</header>
+10053
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

+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

@@ -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

+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

@@ -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

+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

+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;
}
+24
View File
@@ -0,0 +1,24 @@
'use strict';
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,
}
setupResponsiveness(rootAttributes);
setupDataOptions(rootAttributes);
setupPublish(rootAttributes)
window.addEventListener('message', function (e) {
const RESIZE_CODE = 'resize:';
if (typeof e.data !== 'string' || !e.data.startsWith(RESIZE_CODE)) {
return;
}
const data = JSON.parse(e.data.substring(RESIZE_CODE.length))
previewFrame.style.height = data.height + 'px';
});
-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,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,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,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,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" 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

@@ -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').append(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;
`;
+87
View File
@@ -0,0 +1,87 @@
// 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 [mode, setMode] = useState(initialState.mode);
const [breakpoint, setBreakpoint] = useState(initialState.breakpoint);
useEffect(() => {
updateController();
}, [mode, breakpoint]);
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') {
setMode(initialState.mode);
setBreakpoint(initialState.breakpoint);
return;
}
setMode(mode);
setBreakpoint(breakpoint);
}
function isActive(requestedMode) {
return mode === requestedMode;
}
function updateController() {
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};
}
}
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;
`;
+31
View File
@@ -0,0 +1,31 @@
.btn {
--btn-color: #333;
--btn-background-color: white;
display: inline-block;
padding: calc(0.375rem - 2px) 0.75rem;
font-size: 0.875rem;
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;
}
&--secondary {
border-color: #cbd5e0;
--btn-color: #333;
--btn-background-color: #edf2f7;
}
}
+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;
}
+32
View File
@@ -0,0 +1,32 @@
.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: 100%;
background-color: white;
border: 0;
outline: 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);
}
}
+135 -21
View File
@@ -1,28 +1,87 @@
: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: 0.875rem;
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;
}
.btn--secondary {
border-color: #cbd5e0;
--btn-color: #333;
--btn-background-color: #edf2f7;
}
.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;
min-height: 50px;
box-sizing: border-box;
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;
position: relative;
z-index: 1;
}
.page_toolbar__middle {
display: flex;
justify-content: center;
gap: var(--gap);
align-items: center;
margin-bottom: 2rem;
}
header.page_toolbar > div {
.page_toolbar__middle > div {
position: relative;
}
header.page_toolbar > div:not(:first-child):after {
content: '|';
@media (max-width: 1024px) {
.page_toolbar__middle > div {
display: none;
}
}
.page_toolbar__middle > div:not(:first-child):after {
content: "|";
position: absolute;
left: calc(var(--gap) / 2 * -1);
top: 40%;
@@ -30,18 +89,73 @@ 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__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__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: 100%;
background-color: white;
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);
}
.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
View File
@@ -0,0 +1 @@
{"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"}
+109
View File
@@ -0,0 +1,109 @@
: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;
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;
&__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;
}
}
}
&__left,
&__right {
display: flex;
align-items: center;
gap: 1rem;
min-width: 480px;
}
&__right {
justify-content: flex-end;
}
}
@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;
}
+11
View File
@@ -0,0 +1,11 @@
body {
margin: 0;
overflow-y: hidden;
}
main {
margin-left: auto;
margin-right: auto;
}
/*# sourceMappingURL=page--view.css.map */
+1
View File
@@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["page--view.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA","file":"page--view.css"}
+18
View File
@@ -0,0 +1,18 @@
body {
margin: 0;
overflow-y: hidden;
}
main {
margin-left: auto;
margin-right: auto;
}
// 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;
//}
+6337 -10049
View File
File diff suppressed because it is too large Load Diff
+46 -9
View File
@@ -1,17 +1,27 @@
{
"name": "create-block-dev-tool",
"version": "1.0.2",
"name": "@axe-web/block-dev-tool",
"version": "1.0.18",
"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"
"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 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"
},
"license": "ISC",
"main": "server.js",
"type": "module",
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"archiver": "^5.3.1",
"browser-sync": "^2.27.9",
"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 +31,38 @@
"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",
"node-fetch": "^3.2.10",
"open": "^8.4.0",
"plugin-error": "^2.0.0",
"sanitize-html": "^2.7.1",
"sass": "^1.50.1",
"yeoman-generator": "^5.6.1",
"yo": "^4.3.0"
"yeoman-environment": "^3.10.0",
"yeoman-generator": "^5.6.1"
},
"devDependencies": {
"@babel/preset-react": "^7.18.6",
"@modular-css/rollup": "^28.2.2",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup": "^2.77.2",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-jsx": "^1.0.3",
"styled-components": "^5.3.5"
},
"bin": {
"component-dev": "./server.js"
}
"component-dev": "./server.js",
"component-build": "./build.js"
},
"files": [
"helpers.js",
"layouts/**/*",
"platforms/**/*"
]
}
+241
View File
@@ -0,0 +1,241 @@
import {readFile, writeFile, mkdir, copyFile} from "fs/promises";
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,
});
}
}
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(' ');
}
+126
View File
@@ -0,0 +1,126 @@
<?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, $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 . '/../../' . $this->project_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], $argv[3] ) )->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"
}
@@ -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)
});
}
}
+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' }
]
})
],
}
+306 -76
View File
@@ -1,5 +1,7 @@
#!/usr/bin/env node
import path from 'path';
import fetch from "node-fetch";
import express from 'express';
import {create} from 'express-handlebars';
import fsExtra from 'fs-extra';
@@ -13,106 +15,248 @@ 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';
import {getBlockConfigs, getConfigs, readJSONFile} from "./helpers.js";
import PluginError from 'plugin-error';
/**
* 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 sass = gulpSass(dartSass);
buildStyleFiles()
buildScriptFiles()
const {isDev, modulesPath, projectPath, developmentBlockName} = getConfigs();
const blocksRegistry = isDev ? 'http://localhost:3020' : 'https://axe-web-blocks-registry.captain.devdevdev.life';
/**
* Init server
*/
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: ['.'],
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);
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';
if (!jsonFileName || !await fsExtra.exists(`./data/${jsonFileName}.json`)) {
jsonFileName = 'default';
}
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 = {
include_partial: (path) => projectDir + path,
include_block_template: (path) => 'src/' + config.get('blockName') + '.template',
let jsonFileName = req.query.data ? req.query.data : 'default';
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}`;
res.render(baseView, data);
data.helpers = {
port,
include_partial: (filesPath) => path.join(modulesPath, filesPath),
baseView,
previewFrameUrl: `${previewFrameUrl}/${baseViewUrl}`,
}
res.render('index', data);
});
app.use(express.static('src'));
app.use(express.static(projectDir + 'layouts'));
app.get('/view/:baseView', async (req, res) => {
let jsonFileName = req.query.data ? req.query.data : 'default';
const data = await getBlockConfigs(jsonFileName, {includeConfigs: true, projectPath, modulesPath, dataFiles});
if (data.error && data.errorMessage) {
return res.send(data.errorMessage);
}
const listener = app.listen(0, () => {
const PORT = listener.address().port;
const blockName = config.has('blockName') ? config.get('blockName') : developmentBlockName;
console.log(`The web server has started on port ${PORT}`);
data.helpers = {
include_partial: (filesPath) => path.join(modulesPath, filesPath),
include_block_template: () => path.join(projectPath, 'src', `${blockName}.template`),
section_class: `${blockName}--${jsonFileName}`,
base_url: '/'
}
const bs = browserSync.create();
const baseView = req.params.baseView ?? 'container';
gulp.watch(['src/**/*.js', 'src/**/*.mjs', '!src/**/*.min.js'], {delay: 400}, gulp.series([buildScriptFiles, function (cb) {
browserSyncReload(bs, 'js', 'Script Files Change');
return cb();
}]));
res.render(baseView, data)
});
gulp.watch('src/**/*.scss', {delay: 400}, gulp.series([buildStyleFiles, function (cb) {
browserSyncReload(bs, 'css', 'Style Files Change');
return cb();
}]));
app.get('/publish', async (req, res) => {
const data = await readJSONFile(path.join(projectPath, `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(path.join(projectPath, '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(path.join(projectPath, 'dist.zip'));
return;
}
}
// bs.watch("src/**/*.js", function (event, file) {});
// bs.watch("src/**/*.css", function (event, file) {});
res.json({success: true});
bs.watch("src/**/*.hbs", function (event, file) {
browserSyncReload(bs, '', 'Template File Change: ' + file)
});
await fs.unlink(path.join(projectPath, 'dist.zip'));
});
bs.init({
proxy: `http://localhost:${PORT}`,
app.get('/data', async (req, res) => {
let jsonDataFileName = req.query.name ? req.query.name : 'default';
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')));
return res.json({
dataOptions: dataFiles,
designPreview: designPreviewFiles,
data,
});
});
/**
* 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) {
@@ -127,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) {
@@ -160,7 +336,61 @@ 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 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 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();
}