Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25862c4b1c | |||
| a61510f136 | |||
| 0d3ae8f03e | |||
| b58ef27f1e | |||
| 161e34e8ee | |||
| 8eb7bea2e8 | |||
| d9543bb22c | |||
| 02abf4652e | |||
| 1ed038ff02 | |||
| 0bb705ccdc | |||
| 9d25bdcb88 | |||
| 4d1d2337bf | |||
| 00866a8115 | |||
| 1d733fb952 | |||
| 295c6f82af | |||
| f0af682a43 | |||
| ad35540111 | |||
| 5cb6343184 | |||
| 502326fd8d | |||
| 38a2d60f2c | |||
| 15c5286905 | |||
| f3e5b92318 | |||
| c42c9fecba | |||
| 6ebd37190a | |||
| 37ecce6290 | |||
| e0ae4d3d69 | |||
| 70d4630971 | |||
| a2f8c7ebc1 | |||
| 1ae643b306 | |||
| 52109f5010 | |||
| e0fb681d3b | |||
| 840f73d739 | |||
| 702cd6e226 | |||
| eabe411b7a | |||
| 7bf571e0fb | |||
| 73a10d9d55 | |||
| 527416c63d | |||
| ac70e7eb9a | |||
| 05660eb4e4 | |||
| 499c5b8163 | |||
| 16f65254da | |||
| 812ea6d2c5 | |||
| 28ad7f1571 | |||
| 3c39f49a84 | |||
| 344ce55e67 | |||
| 73be6c050b | |||
| f23c745a15 | |||
| 6ddc9b9910 | |||
| b5d23d612f | |||
| 7be17a28fa | |||
| c5502e2be2 | |||
| a9597684de | |||
| d1bbffce2e | |||
| 12ef5e0570 | |||
| c48420249b | |||
| 82d4f1b755 | |||
| f8a46b8148 | |||
| 8c75133759 | |||
| 1d214ca60f | |||
| ff33974b78 | |||
| 212e0a414b | |||
| e407dcd0ca | |||
| 7d168b27ee | |||
| c570cd9c75 | |||
| 86f64e8b4b | |||
| 9ac3a19b2f | |||
| 89e24c9285 | |||
| f9d30546ca | |||
| 7201d360ee | |||
| 7eeafdbd31 | |||
| 1d3aec4dff | |||
| 1132790fd8 | |||
| 5672912f04 | |||
| fe95eb0ef3 | |||
| 86602b0ab8 | |||
| 49cc9f685c | |||
| 0ace3de83e | |||
| 38629e7fbb | |||
| 44dc99c641 | |||
| dfcd9681bc | |||
| 29a75a6e26 | |||
| 0ec37c4c5b | |||
| 56839fee2e | |||
| 20c5a68d81 | |||
| 35afd615f8 | |||
| 70d89f567a | |||
| ab0fe727b3 | |||
| 40c67a0bd8 | |||
| e735f2d141 | |||
| 804c504c06 | |||
| 50501b05c6 | |||
| 5fcdc36b09 | |||
| 2a5daa2445 | |||
| e184c3483a | |||
| ee197d24c4 | |||
| 30a3e964bd | |||
| 93c39981f9 | |||
| 019e51ab1c | |||
| 760825be2d | |||
| 6b3ed38e04 | |||
| 190800cc80 | |||
| 33dbe48a2c | |||
| c7e492ae8a | |||
| ae68a88fd1 | |||
| 7b7a8c101f | |||
| 3aa5370c2b | |||
| 92e4ca76f4 | |||
| 2917cb7332 | |||
| 0d4baf6b11 | |||
| 50d7117609 | |||
| 28ecfa281c | |||
| 9173efe9c9 | |||
| a7e51803cf | |||
| de8d78ea31 | |||
| 29734a6b94 | |||
| d791edda3e | |||
| 0ea8f8b443 | |||
| 718e6b32a9 |
@@ -10,3 +10,4 @@ deploy-*.sh
|
||||
|
||||
# Custom
|
||||
blocks
|
||||
exports
|
||||
|
||||
@@ -20,3 +20,11 @@ environment.
|
||||
|
||||
Generated blocks are including this repository.
|
||||
This project is running a nodejs script with `browsersync` and `gulp` which improves the development process.
|
||||
|
||||
## Development / Testing the tool
|
||||
|
||||
Run `npm run generate-block` command and give the `development` name to the block, it will generate the `/blocks` folder.
|
||||
|
||||
Copy the `/development/data` and `/development/src` folders to root folder of project.
|
||||
Now you're ready to run development process: `npm run dev`.
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
// For development purposes - run `npm run build-platform`.
|
||||
|
||||
import config from 'config';
|
||||
import prompts from "prompts";
|
||||
import {buildHubspotEmail} from "./platforms/hubspot/hubspot-email-adapter.js";
|
||||
import {getConfigs} from "./helpers.js";
|
||||
import {buildWordPress} from "./platforms/wordpress/wordpress-adapter.js";
|
||||
import {buildHubspotPage} from "./platforms/hubspot/hubspot-page-adapter.js";
|
||||
|
||||
const {isDev, developmentBlockName} = getConfigs();
|
||||
const blockName = !isDev && config.has('blockName') ? config.get('blockName') : developmentBlockName;
|
||||
|
||||
export const PLATFORM_OPTIONS = [{
|
||||
name: 'wordpress',
|
||||
title: 'WordPress'
|
||||
}, {
|
||||
name: 'wordpress-blocks',
|
||||
title: 'WordPress Block'
|
||||
}, {
|
||||
name: 'wordpress-elementor',
|
||||
title: 'WordPress Elementor'
|
||||
}, {
|
||||
name: 'hubspot',
|
||||
title: 'Hubspot'
|
||||
}, {
|
||||
name: 'hubspot-email',
|
||||
title: 'Hubspot Email'
|
||||
}, {
|
||||
name: 'javascript',
|
||||
title: 'JavaScript'
|
||||
}, {
|
||||
name: 'php',
|
||||
title: 'PHP'
|
||||
}];
|
||||
|
||||
const data = await getExportData();
|
||||
const selectedPlatform = PLATFORM_OPTIONS[data['platform']];
|
||||
await buildExportFiles(selectedPlatform);
|
||||
|
||||
console.log('--------------------\nDone!');
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
export async function buildExportFiles(platform) {
|
||||
if (platform.name.startsWith('wordpress')) {
|
||||
if (platform.name === 'wordpress-blocks') {
|
||||
await buildWordPress(blockName, true);
|
||||
} else if (platform.name === 'wordpress-elementor') {
|
||||
await buildWordPress(blockName, false, true);
|
||||
} else {
|
||||
await buildWordPress(blockName);
|
||||
}
|
||||
} else if (platform.name === 'hubspot-email') {
|
||||
await buildHubspotEmail(blockName)
|
||||
} else if (platform.name === 'hubspot') {
|
||||
await buildHubspotPage(blockName)
|
||||
}
|
||||
}
|
||||
|
||||
function getExportData() {
|
||||
return prompts([
|
||||
{
|
||||
type: "select",
|
||||
name: "platform",
|
||||
message: "Choose Platform",
|
||||
choices: PLATFORM_OPTIONS.map(item => item.title),
|
||||
default: 'WordPress'
|
||||
}
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import {getConfigs} from "./helpers.js";
|
||||
|
||||
const {isDev, modulesPath, projectPath, developmentBlockName} = getConfigs();
|
||||
|
||||
console.log('----------------------------------\n')
|
||||
console.log('Local Details', process.versions, '\n');
|
||||
console.log('Configs', {
|
||||
isDev,
|
||||
modulesPath,
|
||||
projectPath,
|
||||
developmentBlockName
|
||||
});
|
||||
console.log('\n----------------------------------\n')
|
||||
@@ -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(' ');
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Basic
|
||||
.idea
|
||||
.DS_Store
|
||||
node_modules
|
||||
vendor
|
||||
|
||||
# Custom
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
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>
|
||||
<img src="images/demo.jpeg" alt="">
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<br><br>
|
||||
<hr>
|
||||
|
||||
<h2>Available Buttons Styling</h2>
|
||||
|
||||
<div>
|
||||
<a href="#" class="btn btn--primary">Primary</a>
|
||||
<br>
|
||||
<code>
|
||||
<a href="#" class="btn btn--primary">Primary</a>
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
<a href="#" class="btn btn--secondary">Secondary</a>
|
||||
<br>
|
||||
<code>
|
||||
<a href="#" class="btn btn--secondary">Secondary</a>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!
|
||||
Remove END
|
||||
}}
|
||||
|
||||
</section>
|
||||
@@ -0,0 +1,154 @@
|
||||
import path from 'path';
|
||||
import config from 'config';
|
||||
import {fileURLToPath} from 'url';
|
||||
import memFs from 'mem-fs';
|
||||
import editor from 'mem-fs-editor';
|
||||
import fsExtra from "fs-extra";
|
||||
import archiver from "archiver";
|
||||
|
||||
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: process.env.MODULE_PATH ?? (isDev ? '' : 'node_modules/block-dev-tool'),
|
||||
projectPath: process.env.PROJECT_PATH ?? (isDev ? path.join('blocks', developmentBlockName) : ''),
|
||||
};
|
||||
}
|
||||
|
||||
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(' ');
|
||||
}
|
||||
|
||||
export async function zipProject(srcDir, outputFileName = 'dist.zip') {
|
||||
// create a file to stream archive data to.
|
||||
const output = await fsExtra.createWriteStream(outputFileName);
|
||||
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(srcDir, 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();
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
{{> (include_partial "layouts/partials/head") }}
|
||||
|
||||
<body>
|
||||
|
||||
{{> (include_partial "layouts/partials/toolbar") }}
|
||||
<body class="{{#if iframeMode}}body--iframe{{/if}}">
|
||||
|
||||
<main>
|
||||
{{> (include_block_template) }}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
{{> (include_partial "layouts/partials/head") }}
|
||||
|
||||
<body>
|
||||
|
||||
{{> (include_partial "layouts/partials/toolbar") }}
|
||||
<body class="{{#if iframeMode}}body--iframe{{/if}}">
|
||||
|
||||
<main>
|
||||
<div class="container">
|
||||
|
||||
@@ -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>
|
||||
@@ -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 }}?iframe=true" class="breakpoint"></iframe>
|
||||
</div>
|
||||
|
||||
<script src="/scripts/dist/index.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,8 +2,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="{{ config.cssUrl }}">
|
||||
<link rel="stylesheet" href="styles/page--main.css">
|
||||
<link rel="stylesheet" href="styles/{{ config.blockName }}.min.css">
|
||||
{{#if config.cssUrl }}
|
||||
{{#each config.cssUrl }}
|
||||
<link rel="stylesheet" href="{{ this }}">
|
||||
{{/each}}
|
||||
{{/if}}<link rel="stylesheet" href="/styles/page--view.css">{{# if config.blockName}}
|
||||
<link rel="stylesheet" href="/styles/{{ config.blockName }}.min.css">{{/if}}
|
||||
<link rel="stylesheet" href="https://unpkg.com/swiper@8/swiper-bundle.min.css"/>
|
||||
</head>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<script src="/scripts/dist/frame-index.min.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}}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<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">
|
||||
{{#if config.styleGuideUrl}}
|
||||
<a href="{{ config.styleGuideUrl }}" target="_blank" class="palette" title="Open Style Guide"></a>
|
||||
{{/if}}
|
||||
<a href="{{ previewFrameUrl }}" target="_blank" class="open_in_new_tab" title="Open in New Window"></a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
!function(){let e;function n(n){const t=document.querySelector("body > main").scrollHeight;if(e===t)return;e=t,window.parent.postMessage("resize:"+JSON.stringify({height:e}),"*")}n(),new ResizeObserver(n).observe(document.body)}();
|
||||
@@ -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,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
|
||||
<path
|
||||
d="M24 45.25q-4.35 0-8.225-1.675T9 39q-2.9-2.9-4.575-6.775Q2.75 28.35 2.75 24q0-4.5 1.675-8.375t4.625-6.75Q12 6 15.95 4.35q3.95-1.65 8.4-1.65 4.2 0 8 1.425t6.675 3.95Q41.9 10.6 43.6 14.05q1.7 3.45 1.7 7.5 0 5.7-3.2 9.35-3.2 3.65-8.9 3.65h-2.9q-.8 0-1.4.65-.6.65-.6 1.45 0 1.25.5 1.75t.5 1.55q0 2.2-1.5 3.75-1.5 1.55-3.8 1.55ZM24 24Zm-11.2 1.3q1 0 1.75-.75t.75-1.75q0-1-.75-1.75t-1.75-.75q-1 0-1.75.75t-.75 1.75q0 1 .75 1.75t1.75.75Zm6.1-8.15q1 0 1.75-.75t.75-1.75q0-1.05-.75-1.775-.75-.725-1.75-.725-1.05 0-1.775.725-.725.725-.725 1.725 0 1.05.725 1.8t1.775.75Zm10.25 0q1 0 1.75-.75t.75-1.75q0-1.05-.75-1.775-.75-.725-1.75-.725-1.05 0-1.775.725-.725.725-.725 1.725 0 1.05.725 1.8t1.775.75Zm6.2 8.15q1 0 1.75-.75t.75-1.75q0-1-.75-1.75t-1.75-.75q-1.05 0-1.775.75-.725.75-.725 1.75t.725 1.75q.725.75 1.775.75ZM23.8 40.55q.4 0 .6-.175.2-.175.2-.625 0-.7-.75-1.175-.75-.475-.75-2.375 0-2.45 1.65-4.4 1.65-1.95 4.1-1.95h4.35q3.7 0 5.525-2.2 1.825-2.2 1.825-5.8 0-6.6-4.925-10.5Q30.7 7.45 24.4 7.45q-7.1 0-12.025 4.825Q7.45 17.1 7.45 24q0 6.9 4.8 11.725 4.8 4.825 11.55 4.825Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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 |
@@ -0,0 +1,17 @@
|
||||
export function setupFrameResizeListener() {
|
||||
const previewFrame = getPreviewFrame();
|
||||
|
||||
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'
|
||||
});
|
||||
}
|
||||
|
||||
export function getPreviewFrame() {
|
||||
return document.getElementById('preview_frame');
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
// Blocks Initialization.
|
||||
function initBlock(blockName = '', selector = '', cb) {
|
||||
document.querySelectorAll(selector).forEach((el) => cb(el));
|
||||
}
|
||||
|
||||
// Scrollbars / Frame resizes notifications.
|
||||
(function () {
|
||||
let height;
|
||||
const debug = false;
|
||||
|
||||
handleHeightChange(); // Initial frame's height setup.
|
||||
setupResizeListener(); // Listen to frame's height changes.
|
||||
|
||||
///
|
||||
|
||||
function setupResizeListener() {
|
||||
const resizeObserver = new ResizeObserver(handleHeightChange);
|
||||
resizeObserver.observe(document.body);
|
||||
}
|
||||
|
||||
function handleHeightChange(entries) {
|
||||
const updatedHeight = getCurrentHeight();
|
||||
|
||||
if (debug) {
|
||||
console.log('Height Updates', 'Old vs New: ' + height, updatedHeight);
|
||||
}
|
||||
|
||||
if (height === updatedHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RESIZE_CODE = 'resize:';
|
||||
height = updatedHeight;
|
||||
window.parent.postMessage(RESIZE_CODE + JSON.stringify({height}), '*');
|
||||
|
||||
if (debug) {
|
||||
console.log('Resize message sent: ', height)
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentHeight() {
|
||||
return document.querySelector('body > main').scrollHeight;
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
import {setupResponsiveness} from './toolbar/responsive.jsx';
|
||||
import {setupPublish} from "./toolbar/publish.jsx";
|
||||
import {setupDataOptions} from "./toolbar/data-options/DataOptions.jsx";
|
||||
import {getPreviewFrame, setupFrameResizeListener} from "./frame/editor.js";
|
||||
|
||||
const rootAttributes = {
|
||||
previewFrame: getPreviewFrame(),
|
||||
};
|
||||
|
||||
setupFrameResizeListener();
|
||||
setupResponsiveness(rootAttributes);
|
||||
setupDataOptions(rootAttributes);
|
||||
setupPublish(rootAttributes);
|
||||
@@ -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,178 @@
|
||||
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;
|
||||
|
||||
const previewFrameUrl = new URL(window.devTool.previewFrameUrl);
|
||||
previewFrameUrl.searchParams.set('data', dataName);
|
||||
previewFrameUrl.searchParams.set('iframe', 'true');
|
||||
|
||||
props.rootAttributes.previewFrame.src = previewFrameUrl.href;
|
||||
|
||||
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,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
|
||||
<path
|
||||
d="M24 45.25q-4.35 0-8.225-1.675T9 39q-2.9-2.9-4.575-6.775Q2.75 28.35 2.75 24q0-4.5 1.675-8.375t4.625-6.75Q12 6 15.95 4.35q3.95-1.65 8.4-1.65 4.2 0 8 1.425t6.675 3.95Q41.9 10.6 43.6 14.05q1.7 3.45 1.7 7.5 0 5.7-3.2 9.35-3.2 3.65-8.9 3.65h-2.9q-.8 0-1.4.65-.6.65-.6 1.45 0 1.25.5 1.75t.5 1.55q0 2.2-1.5 3.75-1.5 1.55-3.8 1.55ZM24 24Zm-11.2 1.3q1 0 1.75-.75t.75-1.75q0-1-.75-1.75t-1.75-.75q-1 0-1.75.75t-.75 1.75q0 1 .75 1.75t1.75.75Zm6.1-8.15q1 0 1.75-.75t.75-1.75q0-1.05-.75-1.775-.75-.725-1.75-.725-1.05 0-1.775.725-.725.725-.725 1.725 0 1.05.725 1.8t1.775.75Zm10.25 0q1 0 1.75-.75t.75-1.75q0-1.05-.75-1.775-.75-.725-1.75-.725-1.05 0-1.775.725-.725.725-.725 1.725 0 1.05.725 1.8t1.775.75Zm6.2 8.15q1 0 1.75-.75t.75-1.75q0-1-.75-1.75t-1.75-.75q-1.05 0-1.775.75-.725.75-.725 1.75t.725 1.75q.725.75 1.775.75ZM23.8 40.55q.4 0 .6-.175.2-.175.2-.625 0-.7-.75-1.175-.75-.475-.75-2.375 0-2.45 1.65-4.4 1.65-1.95 4.1-1.95h4.35q3.7 0 5.525-2.2 1.825-2.2 1.825-5.8 0-6.6-4.925-10.5Q30.7 7.45 24.4 7.45q-7.1 0-12.025 4.825Q7.45 17.1 7.45 24q0 6.9 4.8 11.725 4.8 4.825 11.55 4.825Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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 |
@@ -0,0 +1,47 @@
|
||||
import React, {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;
|
||||
`;
|
||||
@@ -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;
|
||||
`;
|
||||
@@ -0,0 +1,16 @@
|
||||
<html lang="en">
|
||||
|
||||
{{> (include_partial "layouts/partials/head") }}
|
||||
|
||||
<body class="{{#if iframeMode}}body--iframe{{/if}}">
|
||||
|
||||
<main>
|
||||
<section class="fullscreen_layout"></section>
|
||||
{{> (include_block_template) }}
|
||||
<section class="fullscreen_layout"></section>
|
||||
</main>
|
||||
|
||||
{{> (include_partial "layouts/partials/scripts") }}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
@@ -0,0 +1,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.swiper {
|
||||
&-slide {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
height: initial;
|
||||
}
|
||||
}
|
||||
@@ -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,77 @@ 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, .palette {
|
||||
--size: 1.5rem;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
background-image: url("/scripts/dist/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;
|
||||
}
|
||||
|
||||
.palette {
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-palette.svg");
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=page--main.css.map */
|
||||
|
||||
@@ -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;;;AAGF;EAEE","file":"page--main.css"}
|
||||
@@ -0,0 +1,114 @@
|
||||
: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/dist/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;
|
||||
}
|
||||
|
||||
.palette {
|
||||
@extend .open_in_new_tab;
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-palette.svg");
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
main .swiper-slide {
|
||||
width: initial;
|
||||
}
|
||||
main .swiper-wrapper {
|
||||
height: initial;
|
||||
}
|
||||
|
||||
.body--iframe {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.body--iframe main {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.fullscreen_layout {
|
||||
background-color: #9cc3ff;
|
||||
min-height: 100%;
|
||||
background-image: url("https://i.ibb.co/pjwL8D1/shapelined-JBKdviwe-XI-unsplash.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=page--view.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["page--view.scss","_page--view-swiper.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;;ACPA;EACE;;AAGF;EACE;;;ADSJ;EACE;;AAEA;EAGE;;;AAIJ;EACE;EACA;EACA;EACA","file":"page--view.css"}
|
||||
@@ -0,0 +1,32 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden;
|
||||
|
||||
// Fixes scrolling issues of swiperJS. Should be included in all projects.
|
||||
@import "page--view-swiper";
|
||||
}
|
||||
|
||||
// iFrame mode
|
||||
.body--iframe {
|
||||
overflow-y: hidden;
|
||||
|
||||
main {
|
||||
// If you change to "overflow: initial", the margin-top/bottom of first/last element will be not included.
|
||||
// Test on fresh block setup where heading has margin-top.
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.fullscreen_layout {
|
||||
background-color: #9cc3ff;
|
||||
min-height: 100%;
|
||||
background-image: url('https://i.ibb.co/pjwL8D1/shapelined-JBKdviwe-XI-unsplash.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
{
|
||||
"name": "create-block-dev-tool",
|
||||
"version": "1.0.2",
|
||||
"name": "@axe-web/block-dev-tool",
|
||||
"version": "1.0.22",
|
||||
"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"
|
||||
"info": "node debug.js",
|
||||
"dev": "NODE_ENV=development NODE_CONFIG_DIR=blocks/text-block/config BLOCK_NAME=text-block node server.js",
|
||||
"build": "rollup --config rollup.config.js",
|
||||
"build-platform": "NODE_ENV=development NODE_CONFIG_DIR=blocks/text-block/config BLOCK_NAME=text-block node ./build.js",
|
||||
"build-platform-cli": "component-build",
|
||||
"dev-js": "NODE_ENV=development 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",
|
||||
"exec-php": "^0.0.6",
|
||||
"express": "^4.17.3",
|
||||
"express-handlebars": "^6.0.4",
|
||||
"fs-extra": "^10.0.1",
|
||||
@@ -21,11 +33,44 @@
|
||||
"gulp-sass": "^5.1.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"sass": "^1.50.1",
|
||||
"yeoman-generator": "^5.6.1",
|
||||
"yo": "^4.3.0"
|
||||
"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",
|
||||
"prompts": "^2.4.2",
|
||||
"sanitize-html": "^2.7.1",
|
||||
"sass": "^1.50.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",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"styled-components": "^5.3.5"
|
||||
},
|
||||
"bin": {
|
||||
"component-dev": "./server.js"
|
||||
}
|
||||
"component-dev": "./server.js",
|
||||
"component-build": "./build.js"
|
||||
},
|
||||
"files": [
|
||||
"helpers.js",
|
||||
"debug.js",
|
||||
"layouts/**/*.hbs",
|
||||
"layouts/styles/*.css*",
|
||||
"layouts/scripts/dist/*.js*",
|
||||
"layouts/scripts/dist/**/*.jpg",
|
||||
"layouts/scripts/dist/**/*.png",
|
||||
"layouts/scripts/dist/**/*.svg",
|
||||
"platforms/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
import path from "path";
|
||||
import {readFile, writeFile, mkdir, copyFile} from "fs/promises";
|
||||
import {capitalize, getConfigs} from "../../helpers.js";
|
||||
|
||||
export async function buildHubspotEmail(blockName) {
|
||||
const distPath = await createDistFolder(blockName);
|
||||
|
||||
// Template
|
||||
let handlebars = await readFile(path.join(srcPath, `${blockName}.template.hbs`), "utf8");
|
||||
await writeFile(path.join(distPath, 'module.html'), handlebarsToHubl(handlebars));
|
||||
|
||||
// JSON
|
||||
await buildHubspotJSONFiles(distPath, {
|
||||
global: false,
|
||||
host_template_types: ["EMAIL"],
|
||||
label: capitalize(blockName),
|
||||
is_available_for_new_content: true
|
||||
});
|
||||
}
|
||||
|
||||
export async function createDistFolder(blockName) {
|
||||
const distPath = path.join('exports', 'hubspot', `${blockName}.module`);
|
||||
await mkdir(distPath, {recursive: true})
|
||||
|
||||
return distPath;
|
||||
}
|
||||
|
||||
export 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);
|
||||
});
|
||||
}
|
||||
|
||||
export function convertToHubspotField(field = {}) {
|
||||
const data = {
|
||||
id: field.name,
|
||||
name: field.name,
|
||||
label: field.label,
|
||||
display_width: null,
|
||||
validation_regex: "",
|
||||
required: false,
|
||||
locked: false,
|
||||
};
|
||||
|
||||
if (field.default) {
|
||||
data.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 options = [];
|
||||
if (field.options) {
|
||||
Object.keys(field.options).forEach(value => options.push([value, field.options[value]]));
|
||||
}
|
||||
|
||||
return Object.assign({}, data, {
|
||||
type: "choice",
|
||||
choices: options
|
||||
});
|
||||
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 async function buildHubspotJSONFiles(distPath, metaData) {
|
||||
const {modulesPath, projectPath} = getConfigs();
|
||||
await writeFile(path.join(distPath, 'meta.json'), JSON.stringify(metaData, null, 4));
|
||||
|
||||
const blockJSON = await readFile(path.join(projectPath, '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",
|
||||
});
|
||||
|
||||
stylingGroup.children = Object.values(stylingFieldsByName);
|
||||
stylingGroup.tab = "STYLE";
|
||||
|
||||
fields.push(stylingGroup);
|
||||
}
|
||||
|
||||
// Export JSON file.
|
||||
await writeFile(path.join(distPath, 'fields.json'), JSON.stringify(fields, null, 4));
|
||||
}
|
||||
|
||||
|
||||
export function handlebarsToHubl(handlebars) {
|
||||
handlebars = handlebars.replace(/{{#if /g, '{% if module.');
|
||||
handlebars = handlebars.replace(/{{\/if}}/g, '{% endif %}');
|
||||
handlebars = handlebars.replace(/{{#each /g, '{% for module.');
|
||||
handlebars = handlebars.replace(/{{\/each}}/g, '{% endfor %}');
|
||||
handlebars = handlebars.replace(/{{base_url}}/g, '');
|
||||
handlebars = handlebars.replace(/{esc_attr /g, '{');
|
||||
handlebars = handlebars.replace(/{esc_url /g, '{');
|
||||
handlebars = handlebars.replace(/{esc_html /g, '{');
|
||||
handlebars = handlebars.replace(/{{{ /g, '{{');
|
||||
handlebars = handlebars.replace(/ }}}/g, '}}');
|
||||
handlebars = handlebars.replace(/{{{/g, '{{');
|
||||
handlebars = handlebars.replace(/}}}/g, '}}');
|
||||
handlebars = handlebars.replace(/{{/g, '{{module.');
|
||||
handlebars = handlebars.replace(/{{module. /g, '{{ module.');
|
||||
|
||||
return handlebars;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import path from "path";
|
||||
import {copyFile, readFile, writeFile} from "fs/promises";
|
||||
import {capitalize, getConfigs} from "../../helpers.js";
|
||||
import {buildHubspotJSONFiles, createDistFolder, handlebarsToHubl,} from "./hubspot-email-adapter.js";
|
||||
|
||||
export async function buildHubspotPage(blockName) {
|
||||
const {modulesPath, projectPath} = getConfigs();
|
||||
const distPath = await createDistFolder(blockName);
|
||||
|
||||
const srcPath = path.join(projectPath, 'src');
|
||||
|
||||
// Template
|
||||
let handlebars = await readFile(path.join(srcPath, `${blockName}.template.hbs`), "utf8");
|
||||
await writeFile(path.join(distPath, 'module.html'), handlebarsToHubl(handlebars));
|
||||
|
||||
// Assets
|
||||
await copyFile(path.join(srcPath, 'styles', `${blockName}.min.css`), path.join(distPath, 'module.css'));
|
||||
await copyFile(path.join(srcPath, 'scripts', `${blockName}.min.js`), path.join(distPath, 'module.js'));
|
||||
// await copy(path.join(projectPath, 'src', 'images'), path.join(distPath, 'images'));
|
||||
|
||||
// JSON
|
||||
await buildHubspotJSONFiles(distPath, {
|
||||
global: false,
|
||||
host_template_types: ["PAGE"],
|
||||
label: capitalize(blockName),
|
||||
is_available_for_new_content: true
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
// Composer - Autoloader
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Brick\VarExporter\ExportException;
|
||||
use LightnCandy\Flags;
|
||||
use LightnCandy\LightnCandy;
|
||||
use Brick\VarExporter\VarExporter;
|
||||
|
||||
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 = '';
|
||||
public string $module_path = '';
|
||||
public string $project_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;
|
||||
|
||||
$this->buildTemplatePhpFile( $root_path );
|
||||
}
|
||||
|
||||
private function buildTemplatePhpFile( $root_path ) {
|
||||
$file_name = $this->get_handlebars_template( "$root_path/src/$this->component_name.template.hbs" );
|
||||
|
||||
$output_folder = $root_path . '/exports/wordpress/templates';
|
||||
rename( $file_name, "$output_folder/$this->component_name.template.php" );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function build( $args = [] ) {
|
||||
( new Component_Builder( $args['blockName'], $args['backPath'], $args['projectPath'] ) )->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ExportException
|
||||
*/
|
||||
function jsonToPhp( $args = [] ): ?string {
|
||||
$json = $args['json'] ?? [];
|
||||
|
||||
return VarExporter::export( $json );
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "8.0.0"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"zordius/lightncandy": "^1.2.6",
|
||||
"brick/varexporter": "^0.3.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"_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": "6731e0a19d488ee457b6682e62ef9018",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/varexporter",
|
||||
"version": "0.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/brick/varexporter.git",
|
||||
"reference": "3e263cd718d242594c52963760fee2059fd5833c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/brick/varexporter/zipball/3e263cd718d242594c52963760fee2059fd5833c",
|
||||
"reference": "3e263cd718d242594c52963760fee2059fd5833c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"nikic/php-parser": "^4.0",
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.2",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0",
|
||||
"vimeo/psalm": "4.23.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Brick\\VarExporter\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A powerful alternative to var_export(), which can export closures and objects without __set_state()",
|
||||
"keywords": [
|
||||
"var_export"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/brick/varexporter/issues",
|
||||
"source": "https://github.com/brick/varexporter/tree/0.3.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/BenMorel",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-06-29T23:37:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v4.15.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
|
||||
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-tokenizer": "*",
|
||||
"php": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ircmaxell/php-yacc": "^0.0.7",
|
||||
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/php-parse"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpParser\\": "lib/PhpParser"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nikita Popov"
|
||||
}
|
||||
],
|
||||
"description": "A PHP parser written in PHP",
|
||||
"keywords": [
|
||||
"parser",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
|
||||
},
|
||||
"time": "2022-11-12T15:38:23+00:00"
|
||||
},
|
||||
{
|
||||
"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,49 @@
|
||||
<?php
|
||||
|
||||
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>;
|
||||
|
||||
class <%= blockClassModel %>_Component {
|
||||
|
||||
public function __construct() {
|
||||
// add_action( 'wp_enqueue_scripts', [ $this, 'register_assets' ] );
|
||||
add_action( 'after_setup_theme', [ $this, 'register_assets' ] );
|
||||
}
|
||||
|
||||
function register_assets(): void {
|
||||
// $version = get_plugin_data( __DIR__ . "/../../scytale-custom-blocks.php" )['Version']; // In Plugins
|
||||
$version = \Core\Global_Functions::get_current_version_number(); // In Theme
|
||||
|
||||
// $base_path = plugin_dir_url( __FILE__ ); // In Plugins
|
||||
$base_path = get_template_directory_uri() . '/components/partials/<%= blockFilename %>/';
|
||||
|
||||
wp_register_style( '<%= blockFilename %>',
|
||||
$base_path . 'templates/styles/<%= blockFilename %>.min.css',
|
||||
[ 'style-wp' ],
|
||||
$version
|
||||
);
|
||||
wp_enqueue_style( '<%= blockFilename %>' );
|
||||
|
||||
wp_register_script( 'script-<%= blockFilename %>',
|
||||
$base_path . 'templates/scripts/<%= blockFilename %>.min.js',
|
||||
[ 'jquery', 'swiper' ],
|
||||
$version,
|
||||
true
|
||||
);
|
||||
wp_enqueue_script( 'script-<%= blockFilename %>' );
|
||||
}
|
||||
|
||||
public function render( $args = [] ): void {
|
||||
$args = array_merge( [], Helpers\<%= blockClassModel %>_Defaults::default_args( $args ), $args);
|
||||
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( $args, self::class );
|
||||
|
||||
echo apply_filters( 'the_content', wpautop( $output ) );
|
||||
}
|
||||
|
||||
<% if (isElementor) { %>function register_elementor_widget( $widgets_manager ): void {
|
||||
require_once "helpers/<%= blockClassModel %>_Elementor_Widget.php";
|
||||
$widgets_manager->register_widget_type( new Helpers\<%= blockClassModel %>_Elementor_Widget() );
|
||||
}<% } %>
|
||||
|
||||
}
|
||||
|
||||
// ( new <%= blockClassModel %>_Component() ); // Initialization
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>;
|
||||
|
||||
class <%= blockClassModel %>_Component extends \Core\Component {
|
||||
|
||||
public function get_content( $args = [] ): string {
|
||||
$default_args = apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::default_args', [] ); // Not really practical.
|
||||
|
||||
$args = apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::prepare_args', array_merge( $default_args, $args ) );
|
||||
|
||||
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( $args, self::class );
|
||||
|
||||
return apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::content', $output );
|
||||
}
|
||||
|
||||
<% if (!include_acf_block && !include_native_gutenberg_block) { %>function register_assets(): void {
|
||||
$version = get_plugin_data( __DIR__ . "/../../scytale-custom-blocks.php" )['Version']; // In Plugins
|
||||
// $version = \Core\Global_Functions::get_current_version_number(); // In Theme
|
||||
|
||||
// $base_path = get_template_directory_uri() . '/components/partials/<%= blockFilename %>/';
|
||||
wp_enqueue_style( 'block-<%= blockFilename %>', plugins_url( 'templates/styles/<%= blockFilename %>.min.css', __FILE__ ), ['assets-style'], get_blocks_version() );<% if (include_script) { %>
|
||||
wp_enqueue_script( 'block-<%= blockFilename %>', plugins_url( 'templates/scripts/<%= blockFilename %>.min.js', __FILE__ ), ['assets-script'], get_blocks_version(), true );<% } %>
|
||||
|
||||
wp_enqueue_script( 'script-block-<%= blockFilename %>' );
|
||||
}<% } %>
|
||||
|
||||
<% 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", [
|
||||
'style_assets' => [
|
||||
[
|
||||
'name' => '<%= blockFilename %>',
|
||||
'url' => plugins_url( 'templates/styles/<%= blockFilename %>.min.css', __FILE__ ),
|
||||
]
|
||||
],
|
||||
'script_assets' => [
|
||||
[
|
||||
'name' => '<%= blockFilename %>',
|
||||
'url' => plugins_url( 'templates/scripts/<%= blockFilename %>.min.js', __FILE__ ),
|
||||
]
|
||||
]
|
||||
] );
|
||||
}
|
||||
|
||||
<% } %><% 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
|
||||
);
|
||||
} );<% } %>
|
||||
}<% } %>
|
||||
|
||||
}
|
||||
|
||||
<%= blockClassModel %>_Component::get_instance();
|
||||
@@ -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,17 @@
|
||||
<?php
|
||||
|
||||
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
|
||||
|
||||
/**
|
||||
* Component's default args.
|
||||
*/
|
||||
|
||||
class <%= blockClassModel %>_Defaults {
|
||||
public static function default_args(): array {
|
||||
$args = <%- defaultData %>;
|
||||
|
||||
// $args['base_url'] = \Core\Global_Functions::get_file_url( __DIR__ . '/../templates/' );
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
|
||||
|
||||
class <%= blockClassModel %>_Elementor_Widget extends \Elementor\Widget_Base {
|
||||
const PLUGIN_NAME = '<%= blockFilename %>';
|
||||
|
||||
public function get_name() {
|
||||
return self::PLUGIN_NAME;
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return '<%= blockClassModel %>';
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-plus-square-o';
|
||||
}
|
||||
|
||||
protected function _register_controls() {
|
||||
$this->start_controls_section( 'section_content', [ 'label' => 'Content' ] );
|
||||
|
||||
$repeater = new \Elementor\Repeater();
|
||||
|
||||
// $repeater->add_control(
|
||||
// 'video_url', [
|
||||
// 'label' => 'YouTube URL',
|
||||
// 'type' => \Elementor\Controls_Manager::URL,
|
||||
// 'label_block' => true,
|
||||
// 'condition' => [
|
||||
// 'type' => 'video',
|
||||
// ],
|
||||
// ]
|
||||
// );
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
|
||||
function get_style_depends() {
|
||||
return [ '<%= blockFilename %>' ];
|
||||
}
|
||||
|
||||
function get_script_depends() {
|
||||
return [ 'script-<%= blockFilename %>' ];
|
||||
}
|
||||
|
||||
protected function render() {
|
||||
$settings = $this->get_settings_for_display();
|
||||
|
||||
$component = new \AXEWEB_Blocks\Blocks\Scytale\<%= blockClassModel %>\<%= blockClassModel %>_Component();
|
||||
|
||||
$args = self::prepare( $settings );
|
||||
$component->render( $args );
|
||||
}
|
||||
|
||||
public static function prepare( $args ): array {
|
||||
// Prepare $args for render function.
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import path from "path";
|
||||
import {mkdir, copyFile} from "fs/promises";
|
||||
import {capitalize, createFiles, getBlockName, getConfigs, readJSONFile} from "../../helpers.js";
|
||||
import {fileURLToPath} from 'url';
|
||||
import {copy} from "fs-extra";
|
||||
import {exec} from 'child_process';
|
||||
import execPhp from "exec-php";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export async function buildWordPress(blockName, isBlock = false, isElementor = false) {
|
||||
const {modulesPath, projectPath} = getConfigs();
|
||||
|
||||
const distPath = path.join(projectPath, 'exports', 'wordpress');
|
||||
// await mkdir(distPath, {recursive: true})
|
||||
await mkdir(path.join(distPath, 'templates'), {recursive: true})
|
||||
|
||||
const blockFilePath = path.join(projectPath, 'block.json');
|
||||
|
||||
let data = await readJSONFile(blockFilePath);
|
||||
Object.assign(data, getBlockName(data.name));
|
||||
|
||||
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: isBlock,
|
||||
include_native_gutenberg_block: false,
|
||||
include_script: true,
|
||||
include_elementor_widget: isElementor,
|
||||
isElementor,
|
||||
});
|
||||
|
||||
await copyFile(blockFilePath, path.join(distPath, data.blockFilename + '.block.json'));
|
||||
|
||||
const backPath = modulesPath ? modulesPath.split('/').map(() => '..').join('/') : '';
|
||||
|
||||
const phpGeneratorPath = path.join(modulesPath, 'platforms', 'php');
|
||||
await execCommand(`cd ${phpGeneratorPath} && composer install`);
|
||||
await execPHPFile(path.join(phpGeneratorPath, 'build.php'), 'build', {blockName, backPath, projectPath});
|
||||
|
||||
await copyStaticFile(
|
||||
path.join(projectPath, 'src', 'styles', `${data.blockFilename}.min.css`),
|
||||
path.join(distPath, 'templates', 'styles', `${data.blockFilename}.min.css`),
|
||||
);
|
||||
|
||||
await copyStaticFile(
|
||||
path.join(projectPath, 'src', 'scripts', `${data.blockFilename}.min.js`),
|
||||
path.join(distPath, 'templates', 'scripts', `${data.blockFilename}.min.js`),
|
||||
);
|
||||
|
||||
await copy(
|
||||
path.join(projectPath, 'src', 'images'),
|
||||
path.join(distPath, 'templates', 'images'),
|
||||
);
|
||||
|
||||
const phpDataObject = await execPHPFile(path.join(phpGeneratorPath, 'build.php'), 'jsonToPhp', {
|
||||
json: await readJSONFile(path.join(projectPath, 'data', 'default.json'), "utf8"),
|
||||
});
|
||||
|
||||
// await createFiles(Object.assign({}, data, {defaultData: phpDataObject}), [{
|
||||
// from: `templates/helpers/Template_Defaults.php`,
|
||||
// to: `helpers/${data.blockClassModel}_Defaults.php`,
|
||||
// }], {
|
||||
// pathDist: distPath,
|
||||
// generatorsPath: path.join(__dirname),
|
||||
// });
|
||||
|
||||
if (isElementor) {
|
||||
await createFiles(data, [{
|
||||
from: `templates/helpers/Template_Elementor_Widget.php`,
|
||||
to: `helpers/${data.blockClassModel}_Elementor_Widget.php`,
|
||||
}], {
|
||||
pathDist: distPath,
|
||||
generatorsPath: path.join(__dirname)
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
// });
|
||||
} else {
|
||||
await createFiles(data, [{
|
||||
from: `templates/Template_Basic_Component.php`,
|
||||
to: `${data.blockClassModel}_Component.php`,
|
||||
}], {
|
||||
pathDist: distPath,
|
||||
generatorsPath: path.join(__dirname)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function execCommand(cmd = '') {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
exec(cmd, function (error) {
|
||||
if (error) {
|
||||
console.log('Error:', error)
|
||||
reject(error);
|
||||
}
|
||||
|
||||
// console.log(stdout);
|
||||
resolve();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function execPHPFile(file = '', functionName = '', args = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
execPhp(file, 'php', (err, php, out) => {
|
||||
if (err) {
|
||||
console.error(out);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
php[functionName.toLowerCase()](args, (err, res, out) => {
|
||||
if (err) {
|
||||
console.error(out);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve(res);
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async function copyStaticFile(from = '', to = '') {
|
||||
await mkdir(path.dirname(to), {recursive: true})
|
||||
await copyFile(from, to);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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';
|
||||
import {terser} from "rollup-plugin-terser";
|
||||
|
||||
const devMode = (process.env.NODE_ENV === 'development');
|
||||
console.log('Build Mode', devMode ? 'Development' : 'Production');
|
||||
|
||||
export default [{
|
||||
input: 'layouts/scripts/index.js',
|
||||
output: {
|
||||
file: 'layouts/scripts/dist/index.min.js',
|
||||
sourcemap: devMode,
|
||||
},
|
||||
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(),
|
||||
!devMode && terser(),
|
||||
copy({
|
||||
targets: [
|
||||
{src: 'layouts/scripts/toolbar/images/**/*', dest: 'layouts/scripts/dist/toolbar/images'}
|
||||
]
|
||||
})
|
||||
],
|
||||
}, {
|
||||
input: 'layouts/scripts/frame/frame.js',
|
||||
output: {
|
||||
file: 'layouts/scripts/dist/frame-index.min.js',
|
||||
sourcemap: devMode
|
||||
},
|
||||
plugins: [
|
||||
commonjs(),
|
||||
!devMode && terser()
|
||||
]
|
||||
}];
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/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';
|
||||
import browserSync from 'browser-sync';
|
||||
import config from 'config';
|
||||
import gulp from 'gulp';
|
||||
@@ -13,106 +14,254 @@ 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 {getBlockConfigs, getConfigs, readJSONFile, zipProject} 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 PRODUCTION_REGISTRY_URL = 'https://blocks-registery.axe-web.com';
|
||||
|
||||
const sass = gulpSass(dartSass);
|
||||
const {isDev, modulesPath, projectPath, developmentBlockName} = getConfigs();
|
||||
const blocksRegistry = isDev ? 'http://localhost:3020' : PRODUCTION_REGISTRY_URL;
|
||||
|
||||
buildStyleFiles()
|
||||
buildScriptFiles()
|
||||
|
||||
/**
|
||||
* 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) => handlebarLayoutsPath(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) => handlebarLayoutsPath(modulesPath, filesPath),
|
||||
include_block_template: () => handlebarLayoutsPath(projectPath, 'src', `${blockName}.template`),
|
||||
section_class: `${blockName}--${jsonFileName}`,
|
||||
base_url: '/',
|
||||
}
|
||||
|
||||
const bs = browserSync.create();
|
||||
if (!!req.query.iframe) {
|
||||
data.iframeMode = true;
|
||||
}
|
||||
|
||||
gulp.watch(['src/**/*.js', 'src/**/*.mjs', '!src/**/*.min.js'], {delay: 400}, gulp.series([buildScriptFiles, function (cb) {
|
||||
browserSyncReload(bs, 'js', 'Script Files Change');
|
||||
return cb();
|
||||
}]));
|
||||
const baseView = req.params.baseView ?? 'container';
|
||||
|
||||
gulp.watch('src/**/*.scss', {delay: 400}, gulp.series([buildStyleFiles, function (cb) {
|
||||
browserSyncReload(bs, 'css', 'Style Files Change');
|
||||
return cb();
|
||||
}]));
|
||||
res.render(baseView, data)
|
||||
});
|
||||
|
||||
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(path.join(projectPath, 'src'));
|
||||
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.posix.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 +276,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.posix.join(projectPath, "src/**/*.js"), path.posix.join(projectPath, "src/**/*.mjs"), "!" + path.posix.join(projectPath, "src/**/*.min.js")];
|
||||
}
|
||||
|
||||
function buildScriptFiles(done) {
|
||||
const files = getJSBundleFiles();
|
||||
return gulp.src(files, {base: path.posix.join(projectPath, 'src')})
|
||||
.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.posix.join(projectPath, 'src')));
|
||||
}
|
||||
|
||||
function buildStyleFiles() {
|
||||
return gulp.src('src/**/*.scss')
|
||||
function buildStyleFiles(done) {
|
||||
return gulp.src(path.join(projectPath, 'src/**/*.scss'), {base: path.posix.join(projectPath, 'src')})
|
||||
.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.posix.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 +341,20 @@ 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)
|
||||
function handleSyntaxErrors(err, req, res, next) {
|
||||
if (err) {
|
||||
return res.render('error', {
|
||||
helpers: {
|
||||
include_partial: (filesPath) => path.join(modulesPath, filesPath),
|
||||
},
|
||||
err
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
function handlebarLayoutsPath() {
|
||||
return path.join(...arguments)
|
||||
.replace(/\\/g, '/'); // Windows path issue. Fix all "\" for Handlebars.
|
||||
}
|
||||
|
||||