90 Commits

Author SHA1 Message Date
roman 4192d16b7a Added doctype to all HTML templates 2024-01-21 23:43:07 +02:00
roman 12abd35cfa Middleware is not necessary 2024-01-21 23:39:50 +02:00
roman 073ed52932 Added CORS header. 2024-01-21 23:31:07 +02:00
roman 8f2605be9d Updated Dockerfile 2024-01-14 01:14:19 +02:00
roman 9e7860542b Added Dockerfile 2024-01-14 00:58:38 +02:00
roman f48b2a3274 - Rebuilt the entire project - now we can pass blockName in URL.
- Added "ViewMode"
2024-01-14 00:36:14 +02:00
roman b76f83d4fa Merge pull request 'dev' (#9) from dev into master
Reviewed-on: AXE-WEB/block-dev-tool#9
2024-01-06 16:16:40 +00:00
roman 977ec0003c Merge branch 'master' into dev
# Conflicts:
#	package-lock.json
#	package.json
2024-01-06 18:15:57 +02:00
roman 0e91c42e89 Update $base_url parameter since the block location changed. 2023-12-15 12:33:26 +02:00
roman 0a4fd3608f Updated path of assets files. 2023-11-13 01:01:35 +02:00
roman 11152dda87 1.0.28 2023-11-06 15:51:40 +02:00
roman 246c9ddd35 Fix capitalization function 2023-11-06 15:51:26 +02:00
roman 164ef7433a 1.0.30 2023-08-27 17:29:05 +03:00
roman a6decb9cfb Remove Block Name from Sync action. 2023-08-27 17:28:54 +03:00
roman 9c85a89a8f Fix issue of blocks folder 2023-08-23 16:55:53 +03:00
roman 58ed3de9dc Make sure we sync versions after each Publish action.
Update versions in JSON files.
2023-08-19 22:38:19 +03:00
roman 3de4a4abb6 1.0.29 2023-08-19 18:09:39 +03:00
roman 256450226b Ask if user wants to download latest version of block on lunch of the devTool. 2023-08-19 18:08:39 +03:00
roman 51db3da072 Fix scrolling issues & swiperjs conflicts. 2023-08-18 23:28:13 +03:00
roman 05c5697bc2 Fit the devTool to nodejs v16. 2023-08-14 16:11:21 +03:00
roman 01e4160040 1.0.28 2023-08-14 15:50:21 +03:00
roman 81f5166043 Print error message to have better understanding of dataObjects errors. 2023-08-14 15:50:12 +03:00
roman a1929fe33c Support missing images folder. 2023-08-13 23:50:52 +03:00
roman 840c002834 Added default layout view. 2023-08-13 23:26:38 +03:00
roman f4474fb7e4 Support work without DevConfigs. 2023-08-13 22:52:45 +03:00
roman 8ca6cb0365 Added "Content-Type" to files that are sent to S3. 2023-08-13 14:27:25 +03:00
roman e2553358be Added support of Project Global Assets. 2023-08-12 21:01:45 +03:00
roman 34dbff5701 Hide "Version" label 2023-06-26 20:05:11 +03:00
roman c408f2f5b3 Added option to generate test variations - updated error messages. 2023-06-25 22:18:44 +03:00
roman 15dbe26220 Added option to generate test variations. 2023-06-25 21:44:21 +03:00
roman 9818067013 Fix sharable link issues. Browsersync works with ngrok now. 2023-06-25 12:19:32 +03:00
roman 996d222436 Added live-editing in browser option of dataOption (json). 2023-06-25 07:00:07 +03:00
roman e964892e65 Added option to share remote preview URL. (ngrok) 2023-06-23 18:57:31 +03:00
roman 6d1ab34b2b Merge branch 'js-rendering' into dev
# Conflicts:
#	layouts/scripts/dist/frame-index.min.js
#	layouts/scripts/dist/frame-index.min.js.map
#	layouts/scripts/dist/index.min.js
#	layouts/scripts/dist/index.min.js.map
2023-06-21 22:01:13 +03:00
roman 18d89a9a19 1.0.27 2023-06-21 21:47:35 +03:00
roman 5c7ea9df3c Added map files. 2023-06-21 21:47:27 +03:00
roman 010154797a Render Component with JavaScript. 2023-06-21 21:46:31 +03:00
roman 98d0720bc0 Render Component with JavaScript. 2023-06-21 21:45:34 +03:00
roman 378038b244 Added base_url helper to blocks. 2023-04-23 12:52:41 +03:00
roman 090978e8f8 Updated swiper version 2023-04-13 20:17:57 +03:00
roman aae474901b Change position of swiper.css in loading queue. 2023-03-18 16:56:37 +02:00
Roman Axelrod a4193d63e1 Add WordPress hook name to components. 2023-03-14 13:56:28 +02:00
roman 9f5c157e54 Update templates, white spacings and code format. 2023-02-17 14:24:43 +02:00
roman b399d2fc89 Update format of elementor template. 2023-02-17 14:08:26 +02:00
roman c899f8c229 Added validation message in build php process. 2023-02-17 13:57:36 +02:00
roman 80464a61d7 Update output code format. 2023-02-17 13:50:40 +02:00
roman 3a78aee088 - Update export dir of wordpress projects. 2023-02-17 13:33:22 +02:00
roman 0f60a0ac30 Merge pull request 'elementor-build' (#8) from elementor-build into master
Reviewed-on: AXE-WEB/block-dev-tool#8
2023-02-17 08:19:32 +00:00
roman 7dcaa80d71 Merge pull request 'project-path-feat' (#7) from project-path-feat into master
Reviewed-on: AXE-WEB/block-dev-tool#7
2023-02-17 08:18:41 +00:00
roman 0a8cdf1504 Merge branch 'master' into project-path-feat 2023-02-17 10:18:11 +02:00
roman 1718157b4e Add minimum required version of node to package.json. 2023-01-14 19:43:55 +02:00
roman df7866b8b5 Updated Build Template of Elementor block. 2023-01-02 14:23:11 +02:00
roman 50d743140b Updated Build Template of Elementor block. 2023-01-02 12:48:49 +02:00
roman f86afc8394 Added new Resolution - 320px. 2022-12-29 10:01:37 +02:00
roman 269fbbc401 Update logic of hubspot builder. 2022-12-14 16:07:42 +02:00
roman f236674c1e Update logic of hubspot builder. 2022-12-14 12:51:43 +02:00
roman e9827fdd88 Fix dist path of hubspot blocks. 2022-12-14 12:01:52 +02:00
roman b441ac613d Merge branch 'master' into project-path-feat 2022-12-14 03:19:25 +02:00
roman 394d5a42d9 Fix source maps of JS file. 2022-12-14 03:19:15 +02:00
roman 6885db162e Merge branch 'master' into project-path-feat 2022-12-14 00:24:08 +02:00
roman 1a19d63192 Set Height limit to frame. 2022-12-14 00:23:53 +02:00
roman 3c163b8e76 Fix zip archive typo. 2022-12-11 15:08:01 +02:00
roman c627c873f9 Merge branch 'master' into project-path-feat 2022-12-11 09:44:17 +02:00
roman ec61e01950 Add hook_prefix to Component object. 2022-12-11 09:42:40 +02:00
roman 682299445c Add hook_prefix to Component object. 2022-12-11 09:37:16 +02:00
roman ddbc713603 Add scripts/styling to the page only on frontend side. (not in admin panel). 2022-12-10 12:59:26 +02:00
roman 4abdd29709 Add FLAG::THIS in PHP builder. 2022-12-09 19:56:49 +02:00
roman a0def8467b Make sure WordPress className initialized. 2022-12-09 18:29:42 +02:00
roman bd5cafa549 - Support Zip archive with provided PROJECT_PATH.
- Added notes for future tests.
- Make sure we can overwrite MODULE_PATH and PROJECT_PATH.
- Simplify the process of env variables setup. Provide default values.
- Include env.js in package.json
Organized all env in env.js file. It allows us to overwrite these values.
2022-12-09 17:54:41 +02:00
roman dd860ec473 1.0.26 2022-12-09 12:46:54 +02:00
roman c1340f2035 - Added notes for future tests.
- Make sure we can overwrite MODULE_PATH and PROJECT_PATH.
- Simplify the process of env variables setup. Provide default values.
- Include env.js in package.json
Organized all env in env.js file. It allows us to overwrite these values.
2022-12-09 12:46:03 +02:00
roman b43a4d6b4f - Make sure we can overwrite MODULE_PATH and PROJECT_PATH.
- Simplify the process of env variables setup. Provide default values.
- Include env.js in package.json
Organized all env in env.js file. It allows us to overwrite these values.
2022-12-09 12:31:39 +02:00
roman 1a98a60fac - Make sure we can overwrite MODULE_PATH and PROJECT_PATH.
- Simplify the process of env variables setup. Provide default values.
- Include env.js in package.json
Organized all env in env.js file. It allows us to overwrite these values.
2022-12-09 12:26:54 +02:00
roman de0b6740d1 - Simplify the process of env variables setup. Provide default values.
- Include env.js in package.json
Organized all env in env.js file. It allows us to overwrite these values.
2022-12-09 07:41:39 +02:00
roman f9c0852ede - Include env.js in package.json
Organized all env in env.js file. It allows us to overwrite these values.
2022-12-09 07:13:01 +02:00
roman cdbb4d3064 Organized all env in env.js file. It allows us to overwrite these values. 2022-12-09 07:11:33 +02:00
roman 8d7ce53e46 1.0.25 2022-12-08 14:15:57 +02:00
roman e42643f182 rebuild dist bundle. 2022-12-08 14:15:44 +02:00
roman 05b5ccb2b5 Code format - build of wordpress. 2022-12-08 13:34:12 +02:00
roman 54523ed903 1.0.24 2022-12-08 13:25:43 +02:00
roman f611185a47 Wrap build options/platforms in reusable function. 2022-12-08 13:25:32 +02:00
roman 7024c2bfcb 1.0.23 2022-12-08 13:09:21 +02:00
roman b882c9a1c3 Added "WordPress + Component Manager" adapter. 2022-12-08 13:09:05 +02:00
roman 1dc8d192f7 Merge pull request 'acf-build' (#6) from acf-build into master
Reviewed-on: AXE-WEB/block-dev-tool#6
2022-12-08 03:41:08 +00:00
roman d8c3d4c54b - Added compile note (date) in PHP bundle.
- Update spacings/code-format of PHP bundle.
2022-12-08 05:40:39 +02:00
roman 6626ecff2a Update spacings/code-format of PHP bundle. 2022-12-08 05:16:21 +02:00
roman 25862c4b1c 1.0.22 2022-12-08 05:00:43 +02:00
roman a61510f136 Updated build process of ACF Block.
Added initBlock function.
2022-12-08 04:56:45 +02:00
roman 0d3ae8f03e Added "scroll" layout for scrolling tests.
Added swiperjs rules that fix the height/vertical-scrolling issues of sliders.
2022-12-07 06:38:54 +02:00
roman b58ef27f1e Fix scrolling issues of iFrame. 2022-12-06 11:32:14 +02:00
59 changed files with 2140 additions and 8571 deletions
+4
View File
@@ -18,6 +18,10 @@ indent_size = 2
indent_style = tab
indent_size = 4
[*.php]
indent_style = tab
indent_size = 4
[*.md]
trim_trailing_whitespace = false
+19
View File
@@ -0,0 +1,19 @@
FROM node:19.4.0-slim as node
WORKDIR /app/
COPY ./package.json .
COPY ./package-lock.json .
COPY ./inc ./inc
COPY ./layouts ./layouts
COPY ./platforms ./platforms
COPY ./routes ./routes
COPY ./env.js .
COPY ./helpers.js .
COPY ./rollup.config.js .
COPY ./server.js .
RUN npm install
EXPOSE 3010
CMD npm run view-mode
+7 -23
View File
@@ -3,20 +3,20 @@
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";
import {buildExportFiles, getConfigs} from "./helpers.js";
const {isDev, developmentBlockName} = getConfigs();
const blockName = !isDev && config.has('blockName') ? config.get('blockName') : developmentBlockName;
export const PLATFORM_OPTIONS = [{
name: 'wordpress-acf-block',
title: 'WordPress AFC Block'
}, {
name: 'wordpress',
title: 'WordPress'
}, {
name: 'wordpress-blocks',
title: 'WordPress Block'
name: 'wordpress-component-manager',
title: 'WordPress (Component Manager)'
}, {
name: 'wordpress-elementor',
title: 'WordPress Elementor'
@@ -36,7 +36,7 @@ export const PLATFORM_OPTIONS = [{
const data = await getExportData();
const selectedPlatform = PLATFORM_OPTIONS[data['platform']];
await buildExportFiles(selectedPlatform);
await buildExportFiles(blockName, selectedPlatform);
console.log('--------------------\nDone!');
@@ -44,22 +44,6 @@ 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([
{
+46
View File
@@ -0,0 +1,46 @@
import path from 'path';
/**
* Since this file overwrites environment variables for `config` lib,
* it's important to "import" this file before all scripts in entry point file,
* especially before `config` module import.
*/
/**
* Export constant variables
*/
export const PRODUCTION_REGISTRY_URL = 'https://blocks-registery.axe-web.com';
export const IS_DEV = process.env.NODE_ENV === 'development';
export const BLOCK_NAME = process.env.BLOCK_NAME;
/**
* Overwrite env variables.
*/
process.env.NODE_CONFIG_DIR = path.join(getProjectPath(), 'config');
export function getModulePath() {
let modulePath = 'node_modules/block-dev-tool';
if (typeof process.env.MODULE_PATH !== 'undefined') {
modulePath = process.env.MODULE_PATH;
} else if (process.env.BLOCK_NAME) {
modulePath = 'node_modules/@axe-web/block-dev-tool';
}
return modulePath;
}
export function getProjectPath() {
let projectPath = '';
if (typeof process.env.PROJECT_PATH !== 'undefined') {
projectPath = path.join(process.env.PROJECT_PATH ?? '', process.env.BLOCK_NAME ?? '')
} else if (process.env.BLOCK_NAME) {
projectPath = path.join('blocks', process.env.BLOCK_NAME ?? '')
}
return projectPath;
}
+266 -28
View File
@@ -1,23 +1,30 @@
import path from 'path';
import {BLOCK_NAME, getModulePath, getProjectPath, IS_DEV} from "./env.js";
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";
import {buildWordPress} from "./platforms/wordpress/wordpress-adapter.js";
import {buildHubspotEmail} from "./platforms/hubspot/hubspot-email-adapter.js";
import {buildHubspotPage} from "./platforms/hubspot/hubspot-page-adapter.js";
import fs from "fs/promises";
import {constants} from "fs";
import fetch from "node-fetch";
import mime from "mime-types";
import {exec} from "child_process";
import {REGISTRY_URL} from "@axe-web/create-block/env.js";
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) : ''),
isDev: IS_DEV,
developmentBlockName: BLOCK_NAME,
modulesPath: getModulePath(),
projectPath: getProjectPath(),
};
}
@@ -25,6 +32,7 @@ export async function readJSONFile(jsonFile) {
let data = {};
try {
await fs.access(jsonFile, constants.F_OK | constants.R_OK);
data = await fsExtra.readJson(jsonFile);
} catch (e) {
return {
@@ -42,37 +50,47 @@ function getErrorHtml(message = '', errorMessage = '') {
</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;
}
export async function getBlockData(jsonFileName = 'default', {projectPath} = {jsonFileName: 'default'}) {
const filePath = path.join(projectPath, 'data', `${jsonFileName}.json`);
const data = await readJSONFile(filePath);
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,
})
});
if (data.error) {
console.log(filePath, data.errorMessage.replace(/<[^>]*>?/gm, ''));
return {};
}
return data;
}
export function getBlockName(name = '') {
if (name.startsWith('@')) {
name = name.substring(1);
export async function getBlockConfigs(args) {
const config = await readJSONFile(path.join('blocks', '@' + args.project, args.name, 'config', 'default.json'));
const updatedConfig = Object.assign({}, config, // The entire config object.
{
projectDir: '', dataFiles: args.dataFiles.map((name) => {
return {
name,
};
}), remToPx: 16,
});
// Avoid cache conflict on Global Project css/js files.
if (updatedConfig.project) {
updatedConfig.project.css = updatedConfig.project.css ? updatedConfig.project.css + '?cache=' + Date.now() : undefined;
updatedConfig.project.js = updatedConfig.project.js ? updatedConfig.project.js + '?cache=' + Date.now() : undefined;
}
return updatedConfig;
}
export function getBlockName(name = '') {
// Remove all @ from the beginning of the string.
name = name.replace(/^@/, '');
const arr = name.split('/');
return {
blockName: `@${arr[0]}/${arr[1]}`,
project: arr[0],
name: arr[1],
};
@@ -100,7 +118,7 @@ export function capitalize(str) {
return str
.toLowerCase()
.split(/[ -_]/g)
.split(/[-_ ]/g)
.filter((word) => !!word)
.map((word) => {
return word.charAt(0).toUpperCase() + word.slice(1);
@@ -152,3 +170,223 @@ export async function zipProject(srcDir, outputFileName = 'dist.zip') {
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
await archive.finalize();
}
export async function buildExportFiles(blockName, platform) {
if (platform.name.startsWith('wordpress')) {
await buildWordPress(blockName, {platform: platform.name});
} else if (platform.name === 'hubspot-email') {
await buildHubspotEmail(blockName)
} else if (platform.name === 'hubspot') {
await buildHubspotPage(blockName)
}
}
export function removeCommentsFromCss(content) {
return content.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '');
}
export function removeCommentsFromJs(content) {
return content.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '');
}
export async function uploadFile(filePath, uploadUrl, validator) {
const options = {};
const contentType = mime.lookup(filePath);
if (['text/css', 'application/javascript'].includes(contentType)) {
options.encoding = 'utf8';
}
let body = await fs.readFile(filePath, options);
if (validator) {
body = validator(body);
}
try {
const response = await fetch(uploadUrl, {
method: 'PUT',
body: body,
headers: {'Content-Type': contentType}
});
return response.status !== 200;
} catch (err) {
console.log(err)
const fileName = filePath.split('/').pop();
throw new Error(`Can't upload "${fileName}" file. Server permission error.`);
}
}
export async function getImagesList(rootFolder, subFolder = '') {
const imagesPath = path.join(rootFolder, subFolder);
try {
await fs.access(imagesPath);
} catch (err) {
// Folder doesn't exist.
return [];
}
const images = await fs.readdir(imagesPath);
const imagesList = [];
for (const image of images) {
const stats = await fs.stat(path.join(imagesPath, image));
if (stats.isDirectory()) {
const subImages = await getImagesList(rootFolder, image);
imagesList.push(...subImages);
} else {
imagesList.push(path.join(subFolder, image));
}
}
return imagesList;
}
export async function isFileEmpty(filePath, ignoreComments = false) {
const fileContent = await fs.readFile(filePath, 'utf8');
const lines = fileContent.split('\n');
for (const line of lines) {
if (ignoreComments && line.trim().startsWith('//')) {
continue;
}
if (line.trim().length > 0) {
return false;
}
}
return true;
}
export function replaceNames(content, images, uploadedImages) {
images.forEach((image, index) => {
content = content.replace(image, uploadedImages[index].fileName);
});
return content;
}
export async function getBlockFromCloud(blockName, blocksRegistry) {
const queryString = new URLSearchParams();
queryString.append('blockName', blockName);
queryString.append('includeDevConfig', 'true');
const response = await fetch(`${blocksRegistry}?${queryString.toString()}`);
const responseData = await response.json();
if (!responseData || !responseData.name) {
const message = `⚠️ Block not found, please contact administrator. [${blockName}]`
throw new Error(message);
}
if (responseData.statusCode && responseData.statusCode !== 200) {
const message = ["⚠️ [ERROR]", responseData.message || "Server side error."].join(' ');
throw new Error(message);
}
return responseData;
}
export async function verifyVersion(blockName, projectPath, blocksRegistry) {
const block = await getBlockFromCloud(blockName, blocksRegistry);
if (!block) {
throw new Error(`Block not found. [${blockName}]`);
}
const blockJson = await readJSONFile(path.join(projectPath, `block.json`));
if (blockJson.error) {
return false; // Block doesn't exist locally.
}
/*
* This block is managed on cloud.
* Let's detect the latest version.
*/
return block.version === blockJson.version;
}
export async function syncFilesWithCloud(blockName, bs, sourceFiles = false) {
const block = await getBlockFromCloud(blockName, REGISTRY_URL);
let fileExists = false;
try {
await readJSONFile(path.join('blocks', '@' + block.project, block.name, 'block.json'));
fileExists = true;
} catch (e) {
// Dir doesn't exist.
}
// TODO: Pause watcher of the block?
blockName = `@${block.project}/${block.name}`;
await new Promise((resolve) => {
const createBlockModulePath = `./node_modules/@axe-web/create-block`;
const action = sourceFiles ? fileExists ? 'sync --source' : 'pull' : 'sync';
exec(`BLOCK_NAME=${blockName} node ${createBlockModulePath}/create-block.js ${action}`, (err, stdout, stderr) => {
if (err || stderr) {
const message = err || stderr;
console.error('Error:', message);
throw new Error(message);
}
console.log(stdout);
resolve();
});
});
// TODO: Release watcher of the block?
}
export async function getBlockVariations(projectPath) {
const dataFiles = [];
try {
await fs.access(path.join(projectPath, 'data'));
const files = prepareListOfDataFiles(await fs.readdir(path.join(projectPath, 'data')));
dataFiles.push(...files);
} catch (e) {
console.log('Warning: data folder not found.', projectPath);
}
return dataFiles;
}
export function prepareListOfDataFiles(dataFiles) {
return dataFiles
.filter((fileName) => fileName.split('.').pop() === 'json')
.map((fileName) => {
const splitName = fileName.split('.');
splitName.pop();
return splitName.join('');
})
.sort();
}
export function handlebarLayoutsPath() {
return path.join(...arguments)
.replace(/\\/g, '/'); // Windows path issue. Fix all "\" for Handlebars.
}
export function getListOfDesignPreviewFiles(jsonDataFileName, previewFiles, block) {
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: `/design/${block.project}/${block.name}/preview/${fileName}`,
};
});
}
+109
View File
@@ -0,0 +1,109 @@
import gulp from "gulp";
import path from "path";
import sourcemaps from "gulp-sourcemaps";
import babel from "gulp-babel";
import PluginError from "plugin-error";
import rename from "gulp-rename";
import uglify from "gulp-uglify";
import dartSass from 'sass';
import gulpSass from "gulp-sass";
import {getBlockName} from "../helpers.js";
const sass = gulpSass(dartSass);
export function setupWatcher(viewSync) {
const watchSCSS = gulp.watch(['blocks/**/*.scss'], {delay: 400});
watchSCSS.on('change', async function (filepath, stats) {
const pathArray = filepath.split('/', 3);
pathArray.shift();
const block = getBlockName(pathArray.join('/'));
buildStyleFiles(path.join('blocks', '@' + block.project, block.name), () => {
viewSync.syncTemplate(block.blockName, 'styleUpdate');
})
});
const watchJS = gulp.watch(['blocks/**/*.js', '!blocks/**/*.min.js'], {delay: 400});
watchJS.on('change', async function (filepath) {
const pathArray = filepath.split('/', 3);
pathArray.shift();
const block = getBlockName(pathArray.join('/'));
buildScriptFiles(path.join('blocks', '@' + block.project, block.name), () => {
viewSync.syncTemplate(block.blockName, 'scriptUpdate');
})
});
const watchHBS = gulp.watch(['blocks/**/*.hbs'], {delay: 400});
watchHBS.on('change', async function (filepath) {
const pathArray = filepath.split('/', 3);
pathArray.shift();
const block = getBlockName(pathArray.join('/'));
await viewSync.syncTemplate(block.blockName);
});
}
export function buildAssetFiles(projectPath) {
// Register tasks.
gulp.task('build-script-files', (done) => buildScriptFiles(projectPath, done));
gulp.task('build-styling-files', (done) => buildStyleFiles(projectPath, done));
// Run first build.
return new Promise((resolve) => {
gulp.series('build-script-files', 'build-styling-files', function () {
resolve();
})();
});
}
function showError(errorMessage) {
console.log(errorMessage);
// TODO: Send this message to browser.
// So the developer can understand there is an error.
}
function buildScriptFiles(projectPath = '', done) {
const files = getJSBundleFiles(projectPath);
const stream = gulp.src(files, {base: path.posix.join(projectPath, 'src')})
.pipe(sourcemaps.init({}))
.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(rename({extname: '.min.js'}))
.pipe(uglify())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(path.posix.join(projectPath, 'src')));
stream.on('end', done);
return stream;
}
function getJSBundleFiles(projectPath = '') {
return [path.posix.join(projectPath, "src/**/*.js"), path.posix.join(projectPath, "src/**/*.mjs"), "!" + path.posix.join(projectPath, "src/**/*.min.js")];
}
function buildStyleFiles(projectPath = '', done) {
const stream = gulp.src(path.join(projectPath, 'src/**/*.scss'), {base: path.posix.join(projectPath, 'src')})
.pipe(sourcemaps.init({}))
.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(path.posix.join(projectPath, 'src')));
stream.on('end', done);
return stream;
}
+4
View File
@@ -0,0 +1,4 @@
export function setHeaders(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
}
+53
View File
@@ -0,0 +1,53 @@
import express from "express";
import bodyParser from "body-parser";
import {createServer} from "http";
import {create} from "express-handlebars";
import {escape} from "lodash-es";
import {sanitizeUrl} from "@braintree/sanitize-url";
import path from "path";
export function init() {
const app = express();
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}));
// parse application/json
app.use(bodyParser.json());
const hbs = getHandlebarsViewEngine();
app.engine('.hbs', hbs.engine);
app.set('view engine', '.hbs');
const modulesPath = '.';
app.set('views', path.join(modulesPath, 'layouts'));
// Static Files of blockDevTool.
app.use(express.static(path.join(modulesPath, 'layouts')));
return {
expressApp: app,
httpServer: createServer(app),
};
}
function getHandlebarsViewEngine() {
return create({
extname: '.hbs', defaultLayout: false, partialsDir: ['.'], helpers: handlebarsHelpers()
});
}
function handlebarsHelpers() {
return {
esc_attr(attr) {
return escape(attr);
},
esc_url(url) {
return sanitizeUrl(url);
},
esc_html(html) {
return html;
},
};
}
+50
View File
@@ -0,0 +1,50 @@
import {Server} from "socket.io";
import fs from "fs/promises";
import {getBlockName, handlebarLayoutsPath} from "../helpers.js";
import path from "path";
export class ViewSync {
constructor(httpServer) {
this.sessions = {};
this.init(httpServer);
}
addSession(session, blockName) {
if (!this.sessions[blockName]) {
this.sessions[blockName] = [];
}
this.sessions[blockName].push(session);
}
init(httpServer) {
const io = new Server(httpServer);
io.on('connection', async (socket) => {
const blockName = socket.handshake.query.block;
if (!blockName) {
return;
}
this.addSession(socket, blockName);
await this.syncTemplate(blockName);
});
// return httpServer;
}
async syncTemplate(blockName, updateType = 'templateUpdate') {
const block = getBlockName(blockName);
const projectPath = path.join('blocks', '@' + block.project, block.name);
const hbsTemplate = await fs.readFile(handlebarLayoutsPath(projectPath, 'src', `${block.name}.template.hbs`), 'utf8');
if (!this.sessions[blockName]) {
return;
}
this.sessions[blockName].forEach(session => {
session.emit(updateType, {block: blockName, template: hbsTemplate});
});
}
}
+3 -4
View File
@@ -1,12 +1,11 @@
<!doctype html>
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
<body>
<body class="{{#if iframeMode}}body--iframe{{/if}}">
<main>
{{> (include_block_template) }}
</main>
<div id="hbs-container"></div>
{{> (include_partial "layouts/partials/scripts") }}
+5 -6
View File
@@ -1,14 +1,13 @@
<!doctype html>
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
<body>
<body class="{{#if iframeMode}}body--iframe{{/if}}">
<main>
<div class="container">
{{> (include_block_template) }}
</div>
</main>
<div class="container">
<div id="hbs-container"></div>
</div>
{{> (include_partial "layouts/partials/scripts") }}
+1
View File
@@ -1,3 +1,4 @@
<!doctype html>
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
+7 -2
View File
@@ -1,3 +1,4 @@
<!doctype html>
<html lang="en">
<head>
@@ -7,18 +8,22 @@
<title>Block Development Tool</title>
</head>
<body>
<body class="{{#if viewMode }}view-mode{{/if}}">
{{> (include_partial "layouts/partials/toolbar") }}
<script>
window.devTool = {
blockName: '{{ blockName }}',
previewFrameUrl: '{{ previewFrameUrl }}',
{{#if publicUrl}}
publicUrl: true,
{{/if}}
};
</script>
<div class="preview">
<iframe id="preview_frame" src="{{ previewFrameUrl }}" class="breakpoint"></iframe>
<iframe id="preview_frame" src="{{ previewFrameUrl }}?iframe=true&block={{blockName}}" class="breakpoint"></iframe>
</div>
<script src="/scripts/dist/index.min.js"></script>
+4 -2
View File
@@ -2,11 +2,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/swiper@8.4.5/swiper-bundle.min.css"/>
{{#if config.project.css }} <link rel="stylesheet" href="{{ config.project.css }}">
{{/if}}
{{#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"/>
<link rel="stylesheet" id="block-stylesheet" href="{{staticFilesPath}}/styles/{{ config.blockName }}.min.css?v=1">{{/if}}
</head>
+7 -2
View File
@@ -1,9 +1,14 @@
<script src="/socket.io/socket.io.js"></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
<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>{{#if config.jsUrl }}
<script src="https://unpkg.com/swiper@8.4.5/swiper-bundle.min.js"></script>
{{#if config.project.js }}<script src="{{ config.project.js }}"></script>
{{/if}}
{{#if config.jsUrl }}
{{#each config.jsUrl }}<script src="{{ this }}"></script>
{{/each}}
{{/if}}
{{#if config.blockName }}
<script src="/scripts/{{ config.blockName }}.min.js"></script>
<script id="block-script" src="{{staticFilesPath}}/scripts/{{ config.blockName }}.min.js?v=1"></script>
{{/if}}
+8 -5
View File
@@ -3,15 +3,18 @@
<div class="page_toolbar__left"></div>
<div class="page_toolbar__middle">
<div>
Sizes: <b>1rem = {{ config.remToPx }}px</b>
<div style="display: none">
Version: {{ config.version }}
</div>
</div>
<div class="page_toolbar__right">
{{#if config.styleGuideUrl}}
<a href="{{ config.styleGuideUrl }}" target="_blank" class="palette" title="Open Style Guide"></a>
{{#if shareUrl}}
<a href="{{ shareUrl }}" target="_blank" class="share" title="Share URL"></a>
{{/if}}
<a href="{{ previewFrameUrl }}" target="_blank" class="open_in_new_tab" title="Open in New Window"></a>
{{#if styleGuideUrl}}
<a href="{{ styleGuideUrl }}" target="_blank" class="palette" title="Open Style Guide"></a>
{{/if}}
<a href="{{ previewFrameUrl }}?block={{blockName}}" target="_blank" class="open_in_new_tab" title="Open in New Window"></a>
</div>
</header>
+1 -1
View File
@@ -1 +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)}();
let e;window.initBlock=function(e="",n="",o){t=function(){document.querySelectorAll(n).forEach((e=>o(e)))},t()};let t,n={};!function(){const o=r().block;function r(){const e=new URLSearchParams(window.location.search),t={};for(const[n,o]of e)t[n]=o;return t}function a(e){const t=document.getElementById(e),n=t.parentNode;let o,r;return"SCRIPT"===t.tagName?(o=document.createElement("script"),o.type="text/javascript",r="src"):"LINK"===t.tagName&&(o=document.createElement("link"),o.rel="stylesheet",r="href"),o.id=e,o[r]=t[r].replace(/(\?v=)[^&]+/,`$1${Date.now()}`),t.remove(),n.append(o),o}function c(r={}){r.template&&(e=r.template),r.data&&(n=r.data),e&&function(e,n,r){const a=Handlebars.compile(e);let c;Handlebars.registerHelper("esc_attr",(function(e){return e})),Handlebars.registerHelper("esc_url",(function(e){return e})),Handlebars.registerHelper("esc_html",(function(e){return e})),Handlebars.registerHelper("base_url",(function(){const e=["block"];return o&&e.push(o.substr(1)),["",...e,""].join("/")}));try{c=a(n)}catch(e){c=`<div style="max-width: 1280px; margin: 1rem auto;">\n <h1 style="all: unset; font-size: 1.5rem; font-weight: bold; display: block;">Syntax Error:</h1>\n <pre style="all: unset; padding: 10px 15px; background-color: #ffe6e6; border: 1px solid red; border-radius: 0.25rem; overflow: auto; display: block; white-space: pre;">${e.toString()}</pre>\n </div>`}r.innerHTML=c,t&&t()}(e,n||{},document.getElementById("hbs-container"))}!function(){const e=new URLSearchParams({block:o,name:r().data||"default"});fetch(`/data?${e}`).then((e=>e.json())).then((e=>{n=e.data,c({data:n})}))}(),window.addEventListener("message",(function(e){const t=e.data,o="dataUpdate:";if("string"==typeof t&&t.startsWith(o))try{n=JSON.parse(t.substring(o.length)),c({data:n})}catch(e){console.log("Error parsing incoming data.",e)}})),function(){const e=window.io.connect("",{query:`block=${o}`});e.on("error",(function(e){console.log(e)})),e.on("templateUpdate",(function(e){c({template:e.template})})),e.on("scriptUpdate",(function(e){a("block-script").onload=()=>{c({template:e.template})}})),e.on("styleUpdate",(function(e){a("block-stylesheet")}))}()}();
File diff suppressed because one or more lines are too long
+23 -6
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
<svg width="478" height="506" viewBox="0 0 478 506" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M379.8 315.4C352.01 315.4 327.062 327.817 310.146 347.315L186.499 278.249C188.838 270.119 190.1 261.521 190.1 252.7C190.1 243.782 188.834 235.223 186.427 227.037L309.923 158.096C326.774 177.698 351.821 190.2 379.7 190.2C430.409 190.2 471.8 148.918 471.8 98.1C471.8 47.2863 430.514 6 379.7 6C328.886 6 287.6 47.2863 287.6 98.1C287.6 106.995 288.859 115.615 291.265 123.769L167.886 192.697C151.025 173.006 125.968 160.6 98.1 160.6C47.3908 160.6 6 201.882 6 252.7C6 303.523 47.3954 344.8 98.2 344.8C126.093 344.8 151.144 332.287 168.074 312.598L291.571 381.633C289.143 389.853 287.8 398.557 287.8 407.5C287.8 458.209 329.082 499.6 379.9 499.6C430.714 499.6 472 458.314 472 407.5C472 356.677 430.605 315.4 379.8 315.4ZM379.8 45.1C409.086 45.1 432.9 68.9137 432.9 98.2C432.9 127.486 409.086 151.3 379.8 151.3C350.514 151.3 326.7 127.486 326.7 98.2C326.7 68.9203 350.607 45.1 379.8 45.1ZM98.2 305.8C68.9137 305.8 45.1 281.986 45.1 252.7C45.1 223.414 68.9137 199.6 98.2 199.6C127.486 199.6 151.3 223.414 151.3 252.7C151.3 281.98 127.393 305.8 98.2 305.8ZM379.8 460.5C350.514 460.5 326.7 436.686 326.7 407.4C326.7 378.114 350.514 354.3 379.8 354.3C409.086 354.3 432.9 378.114 432.9 407.4C432.9 436.686 409.086 460.5 379.8 460.5Z" fill="#21252D" stroke="#21252D" stroke-width="12"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

-17
View File
@@ -1,17 +0,0 @@
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');
}
+168 -25
View File
@@ -1,41 +1,184 @@
'use strict';
// Scrollbars / Frame resizes notifications.
(function () {
let height;
const debug = false;
window.initBlock = initBlock;
handleHeightChange(); // Initial frame's height setup.
setupResizeListener(); // Listen to frame's height changes.
let template;
let data = {};
let reload;
///
function setupResizeListener() {
const resizeObserver = new ResizeObserver(handleHeightChange);
resizeObserver.observe(document.body);
// Blocks Initialization.
function initBlock(blockName = '', selector = '', cb) {
reload = function () {
document.querySelectorAll(selector).forEach((el) => cb(el));
}
function handleHeightChange(entries) {
const updatedHeight = getCurrentHeight();
reload();
}
if (debug) {
console.log('Height Updates', 'Old vs New: ' + height, updatedHeight);
// Data Updates Listeners.
(function () {
const block = getQueryParams().block;
loadDataOptions();
listenToDataOptionsUpdates();
function listenToDataOptionsUpdates() {
window.addEventListener('message', function (event) {
const message = event.data;
const prefix = 'dataUpdate:';
if (typeof message !== "string" || !message.startsWith(prefix)) {
return;
}
try {
data = JSON.parse(message.substring(prefix.length));
updateBlock({data});
} catch (e) {
console.log('Error parsing incoming data.', e);
}
});
}
function getQueryParams() {
const urlParams = new URLSearchParams(window.location.search);
const params = {};
for (const [key, value] of urlParams) {
params[key] = value;
}
if (height === updatedHeight) {
return params;
}
function loadDataOptions() {
const queryParameters = new URLSearchParams({
block,
name: getQueryParams().data || 'default'
});
fetch(`/data?${queryParameters}`)
.then((response) => response.json())
.then((response) => {
data = response.data; // Update state.
updateBlock({data});
})
}
// Listen to Template updates.
initSocket();
function initSocket() {
const socket = window.io.connect('', {query: `block=${block}`});
socket.on('error', function (err) {
console.log(err);
});
// socket.on('connect', function () {
// console.log('user connected', socket.id)
// });
socket.on('templateUpdate', function (args) {
updateBlock({template: args.template});
});
socket.on('scriptUpdate', function (args) {
const tag = updateTag('block-script');
tag.onload = () => {
updateBlock({template: args.template});
}
});
socket.on('styleUpdate', function (args) {
updateTag('block-stylesheet');
});
}
function updateTag(id) {
const tag = document.getElementById(id);
const wrapper = tag.parentNode;
let clone;
let attr;
if (tag.tagName === 'SCRIPT') {
clone = document.createElement('script');
clone.type = 'text/javascript';
attr = 'src';
} else if (tag.tagName === 'LINK') {
clone = document.createElement('link');
clone.rel = 'stylesheet';
attr = 'href';
}
clone.id = id;
// Add version to the stylesheet URL, make sure we override the cache and already existing version there.
clone[attr] = tag[attr].replace(/(\?v=)[^&]+/, `$1${Date.now()}`);
tag.remove();
wrapper.append(clone);
return clone;
}
function updateBlock(args = {}) {
if (args.template) {
template = args.template; // Update state.
}
if (args.data) {
data = args.data; // Update state.
}
if (!template) {
return;
}
const RESIZE_CODE = 'resize:';
height = updatedHeight;
window.parent.postMessage(RESIZE_CODE + JSON.stringify({height}), '*');
renderBlock(template, data || {}, document.getElementById("hbs-container"));
}
if (debug) {
console.log('Resize message sent: ', height)
function renderBlock(templateHbs, jsonData, target) {
const template = Handlebars.compile(templateHbs);
/**
* Handlebars Helpers
*/
Handlebars.registerHelper('esc_attr', function (attr) {
return attr;
});
Handlebars.registerHelper('esc_url', function (attr) {
return attr;
});
Handlebars.registerHelper('esc_html', function (attr) {
return attr;
});
Handlebars.registerHelper('base_url', function () {
const list = ['block'];
if (block) {
list.push(block.substr(1));
}
return ['', ...list, ''].join('/');
});
let html;
try {
html = template(jsonData);
} catch (e) {
html = `<div style="max-width: 1280px; margin: 1rem auto;">
<h1 style="all: unset; font-size: 1.5rem; font-weight: bold; display: block;">Syntax Error:</h1>
<pre style="all: unset; padding: 10px 15px; background-color: #ffe6e6; border: 1px solid red; border-radius: 0.25rem; overflow: auto; display: block; white-space: pre;">${e.toString()}</pre>
</div>`;
}
target.innerHTML = html;
if (reload) {
reload();
}
}
function getCurrentHeight() {
return document.querySelector('body > main').scrollHeight;
}
})();
+5 -2
View File
@@ -3,13 +3,16 @@
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();
// setupFrameResizeListener();
setupResponsiveness(rootAttributes);
setupDataOptions(rootAttributes);
setupPublish(rootAttributes);
function getPreviewFrame() {
return document.getElementById('preview_frame');
}
+101
View File
@@ -0,0 +1,101 @@
'use strict';
import React, {useState} from "react";
import * as ReactDOM from "react-dom/client";
function init() {
const wrapper = document.querySelector('#screen');
const root = ReactDOM.createRoot(wrapper);
const html = (<SyncScreen/>);
root.render(html);
}
function SyncScreen() {
const [loading, setLoading] = useState(null);
const [error, setError] = useState(null);
return <>
<section className="container py-5">
<h1 style={{marginBottom: '2rem'}}>Oops... Block not in sync.</h1>
{error && <p className="alert alert-danger">{error}</p>}
{loading ?
<>
{loading === 'syncing' &&
<p>Version upgrade in progress...</p>
}
<p>Please wait...</p>
</>
:
<>
<p>Your version of the block is not in sync with the cloud (not latest version).<br/>
Would you like to update it?</p>
<div className="options">
<button className="btn btn-primary" style={{marginRight: '0.5rem'}} onClick={runSyncLogic}>Yes, Update to
Latest
</button>
<button className="btn btn-secondary" onClick={ignoreVersionSync}>Ignore</button>
</div>
</>
}
</section>
</>
async function ignoreVersionSync() {
// Add "ignore" query parameter to current URl and redirect.
const url = new URL(window.location.href);
url.searchParams.set('ignoreVersionSync', 'true');
window.location = url.href;
}
async function runSyncLogic() {
setLoading('syncing');
let data = {};
try {
const response = await fetch('/sync', {
method: 'POST',
body: JSON.stringify({block: getQueryParams().block}),
headers: {
'Content-Type': 'application/json'
}
});
data = await response.json();
} catch (err) {
setError('Error: ' + err.message);
setLoading(false);
return;
}
if (data.status !== 200) {
setError(data.message);
setLoading(false);
return;
}
setTimeout(() => window.location.reload(), 1000);
}
// TODO: This function is used in multiple places (repeated code).
// Move to a common place.
function getQueryParams() {
const urlParams = new URLSearchParams(window.location.search);
const params = {};
for (const [key, value] of urlParams) {
params[key] = value;
}
return params;
}
}
init();
@@ -9,10 +9,21 @@ import {
import {isClickOutside, isEscHit} from "../responsive-button/ResponsiveButton.jsx";
import {DesignPreview} from "./DesignPreview.jsx";
export const PRODUCTION_REGISTRY_URL = 'https://blocks-registery.axe-web.com';
function DataOptions(props = {}) {
props.rootAttributes = props.rootAttributes ?? {};
const initialState = {dataName: 'default', data: {}, dataOptions: [], designPreview: []};
const initialState = {
dataName: 'default',
data: {},
dataText: '{}',
dataOptions: [],
designPreview: [],
errorMessage: null,
loading: false,
};
const [state, setState] = useState(initialState);
const [previewOption, setPreviewOption] = useState(getDesignPreviewImage(state.dataName, state.designPreview));
const updateState = (update) => setState(Object.assign({}, state, update));
@@ -20,8 +31,7 @@ function DataOptions(props = {}) {
const [sidebarOpen, setSidebarOpen] = useState(false);
useEffect(async () => {
const data = await fetchDataOptions(state.dataName);
updateState(data);
await syncDataOptions(state.dataName);
}, []);
const handleCloseSidebarEscEvent = useCallback((e) => {
@@ -66,7 +76,7 @@ function DataOptions(props = {}) {
<SidebarDataOptionsStyle>
<label htmlFor="data-options">Data Options</label>
<select name="data" id="data-options" onChange={(e) => changeDataOption(e)} value={state.dataName}>
<select name="data" id="data-options" onChange={(e) => changeDataOption(e)} value={state.dataName} disabled={state.loading}>
{state.dataOptions.map((item) => {
const isSelected = state.dataName === item;
return <option value={item} selected={isSelected}>{item}</option>
@@ -76,10 +86,21 @@ function DataOptions(props = {}) {
}
{state.data &&
<pre>{JSON.stringify(state.data, null, 2)}</pre>
<textarea value={state.dataText} onChange={dataOptionUpdate} disabled={state.loading}/>
}
<button className='btn btn--secondary' onClick={(e) => copyToClipboard(e, state.data)}>Copy to Clipboard</button>
{state.errorMessage &&
<p className={'alert alert--error'}>{state.errorMessage}</p>
}
{state.loading &&
<p className={'alert'}>Loading, please wait...</p>
}
<div className={'actions'}>
<button className='btn btn--secondary' disabled={state.loading} onClick={(e) => copyToClipboard(e, state.data)}>Copy to Clipboard</button>
<button className='btn btn--secondary' disabled={state.loading} onClick={(e) => generateVariation(e, state.data)}>Generate Test</button>
</div>
</SidebarStyle>
</div>;
@@ -87,6 +108,52 @@ function DataOptions(props = {}) {
// Functions
//
function generateVariation() {
const url = PRODUCTION_REGISTRY_URL + '/content-generator/';
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: JSON.stringify({json: state.data}),
};
updateState({loading: true, errorMessage: null});
return fetch(url, requestOptions)
.then(response => response.json())
.then(result => {
console.log(result)
if (result.statusCode !== 200) {
throw new Error(result.message);
}
const data = result.variation;
updateState({dataText: JSON.stringify(data, null, 2), data, errorMessage: null, loading: false});
updateIframe(data);
})
.catch(error => {
updateState({loading: false, errorMessage: 'Something went wrong, please try again.'})
});
}
function dataOptionUpdate(e) {
let data;
try {
data = JSON.parse(e.target.value);
} catch (err) {
updateState({dataText: e.target.value, errorMessage: 'Invalid JSON, please review and try again.'});
return;
}
updateState({dataText: e.target.value, data, errorMessage: null});
updateIframe(data);
}
function openSidebar() {
setSidebarOpen(true);
setTimeout(() => document.querySelector('.sidebar-active').focus());
@@ -98,14 +165,11 @@ function DataOptions(props = {}) {
async function changeDataOption(e) {
const dataName = e.target.value;
props.rootAttributes.previewFrame.src = window.devTool.previewFrameUrl + '?data=' + dataName;
const dataOption = await fetchDataOptions(dataName);
updateState(Object.assign({}, dataOption, {dataName}));
await syncDataOptions(dataName);
}
async function fetchDataOptions(name = 'default') {
const queryParameters = new URLSearchParams({name});
const queryParameters = new URLSearchParams({name, block: window.devTool.blockName});
const response = await fetch(`/data?${queryParameters}`);
return await response.json();
}
@@ -133,6 +197,23 @@ function DataOptions(props = {}) {
setPreviewOption(getDesignPreviewImage(state.dataName, state.designPreview));
}
async function syncDataOptions(dataName) {
const dataOptions = await fetchDataOptions(dataName);
updateIframe(dataOptions.data);
const newState = Object.assign({errorMessage: null},
dataOptions,
{dataName},
{dataText: JSON.stringify(dataOptions.data, null, 2)},
);
updateState(newState);
}
function updateIframe(data) {
const previewIframe = props.rootAttributes.previewFrame;
previewIframe.contentWindow.postMessage('dataUpdate:' + JSON.stringify(data || {}), '*');
}
}
function copyToClipboard(e, context) {
@@ -24,7 +24,10 @@ export const SidebarStyle = styled.div`
visibility: visible;
}
pre {
pre, textarea {
height: 100%;
//min-height: 480px;
resize: vertical;
overflow-x: auto;
padding: 0.5rem;
background-color: #EDF2F7;
@@ -33,6 +36,20 @@ export const SidebarStyle = styled.div`
border: 1px solid #cbd5e0;
width: 100%;
box-sizing: border-box;
margin: 0.5rem 0;
display: block;
white-space: pre;
}
.actions {
display: flex;
width: 100%;
justify-content: space-between;
}
.alert--error {
color: red;
}
`;
@@ -0,0 +1,3 @@
<svg width="478" height="506" viewBox="0 0 478 506" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M379.8 315.4C352.01 315.4 327.062 327.817 310.146 347.315L186.499 278.249C188.838 270.119 190.1 261.521 190.1 252.7C190.1 243.782 188.834 235.223 186.427 227.037L309.923 158.096C326.774 177.698 351.821 190.2 379.7 190.2C430.409 190.2 471.8 148.918 471.8 98.1C471.8 47.2863 430.514 6 379.7 6C328.886 6 287.6 47.2863 287.6 98.1C287.6 106.995 288.859 115.615 291.265 123.769L167.886 192.697C151.025 173.006 125.968 160.6 98.1 160.6C47.3908 160.6 6 201.882 6 252.7C6 303.523 47.3954 344.8 98.2 344.8C126.093 344.8 151.144 332.287 168.074 312.598L291.571 381.633C289.143 389.853 287.8 398.557 287.8 407.5C287.8 458.209 329.082 499.6 379.9 499.6C430.714 499.6 472 458.314 472 407.5C472 356.677 430.605 315.4 379.8 315.4ZM379.8 45.1C409.086 45.1 432.9 68.9137 432.9 98.2C432.9 127.486 409.086 151.3 379.8 151.3C350.514 151.3 326.7 127.486 326.7 98.2C326.7 68.9203 350.607 45.1 379.8 45.1ZM98.2 305.8C68.9137 305.8 45.1 281.986 45.1 252.7C45.1 223.414 68.9137 199.6 98.2 199.6C127.486 199.6 151.3 223.414 151.3 252.7C151.3 281.98 127.393 305.8 98.2 305.8ZM379.8 460.5C350.514 460.5 326.7 436.686 326.7 407.4C326.7 378.114 350.514 354.3 379.8 354.3C409.086 354.3 432.9 378.114 432.9 407.4C432.9 436.686 409.086 460.5 379.8 460.5Z" fill="#21252D" stroke="#21252D" stroke-width="12"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+5
View File
@@ -5,6 +5,10 @@ function Publish(props = {}) {
const [state, setState] = useState({loading: false});
const updateState = (update) => setState(Object.assign({}, state, update));
if (window.devTool.publicUrl) {
return;
}
return <div>
{state.loading &&
<div className="overlay overlay--loading">Loading, Please wait...</div>
@@ -39,6 +43,7 @@ function Publish(props = {}) {
export function setupPublish(rootAttributes) {
// INIT
const wrapper = document.createElement('div');
wrapper.setAttribute('id', 'publish-btn');
document.querySelector('.page_toolbar__right').append(wrapper)
const root = ReactDOM.createRoot(wrapper);
@@ -4,7 +4,7 @@ import {ButtonStyling, ResponsiveButtonStyle, ResponsiveOptionsDropdown} from ".
const responsiveOptions = {
desktop: [1920, 1800, 1680, 1440, 1360, 1280, 1024],
tablet: [992, 768, 600],
mobile: [480, 414, 375, 360],
mobile: [480, 414, 375, 360, 320],
}
const defaultResponsiveOptions = {
+2 -1
View File
@@ -65,7 +65,8 @@ function Responsive(props = {}) {
function updateController() {
let frameBreakpoint = breakpoint;
if (typeof frameBreakpoint !== 'string') {
frameBreakpoint = frameBreakpoint + 'px';
const scrollbarWidth = 15;
frameBreakpoint = (scrollbarWidth + frameBreakpoint) + 'px';
}
previewFrame.style.setProperty('--breakpoint', frameBreakpoint);
+17
View File
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
<body class="{{#if iframeMode}}body--iframe{{/if}}">
<div>
<section class="fullscreen_layout"></section>
<div id="hbs-container"></div>
<section class="fullscreen_layout"></section>
</div>
{{> (include_partial "layouts/partials/scripts") }}
</body>
</html>
+1 -5
View File
@@ -1,5 +1,4 @@
.preview {
overflow-y: scroll;
height: calc(100% - var(--top_panel_height));
position: relative;
}
@@ -9,17 +8,14 @@
margin-right: auto;
margin-left: auto;
min-height: 100%;
//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 {
+9 -4
View File
@@ -8,6 +8,9 @@ body {
font-size: 1rem;
background-color: #F7FAFC;
}
body.view-mode #publish-btn {
display: none;
}
.btn {
--btn-color: #333;
@@ -114,7 +117,6 @@ body {
}
.preview {
overflow-y: scroll;
height: calc(100% - var(--top_panel_height));
position: relative;
}
@@ -127,10 +129,8 @@ body {
--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 {
@@ -139,7 +139,7 @@ body {
max-width: var(--breakpoint);
}
.open_in_new_tab, .palette {
.open_in_new_tab, .share, .palette {
--size: 1.5rem;
width: var(--size);
height: var(--size);
@@ -162,4 +162,9 @@ body {
background-image: url("/scripts/dist/toolbar/images/icon-palette.svg");
}
.share {
background-size: calc(var(--size) - 0.35rem);
background-image: url("/scripts/dist/toolbar/images/icon-share.svg");
}
/*# sourceMappingURL=page--main.css.map */
+1 -1
View File
@@ -1 +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"}
{"version":3,"sourceRoot":"","sources":["page--main.scss","_buttons.scss","_overlay.scss","_page--preview.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA;EAEA;;AAGE;EACE;;;ACbN;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;;;AFUF;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;;;AGxFJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EAGA;EACA;EAEA;EACA;EACA;EACA;;AAEA;EACE;EAEA;EACA;;;AHuEJ;EACE;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EAEE;;;AAGF;EAEE;EACA","file":"page--main.css"}
+12
View File
@@ -8,6 +8,12 @@ body {
font-size: 1rem;
//overflow: none;
background-color: #F7FAFC;
&.view-mode {
#publish-btn {
display: none;
}
}
}
@import "buttons";
@@ -112,3 +118,9 @@ body {
@extend .open_in_new_tab;
background-image: url("/scripts/dist/toolbar/images/icon-palette.svg");
}
.share {
@extend .open_in_new_tab;
background-size: calc(var(--size) - 0.35rem);
background-image: url("/scripts/dist/toolbar/images/icon-share.svg");
}
+9 -4
View File
@@ -1,11 +1,16 @@
body {
margin: 0;
overflow-y: hidden;
}
main {
margin-left: auto;
margin-right: auto;
.body--iframe {
overflow-y: scroll;
}
.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 */
+1 -1
View File
@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["page--view.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA","file":"page--view.css"}
{"version":3,"sourceRoot":"","sources":["page--view.scss"],"names":[],"mappings":"AAAA;EACE;;;AAIF;EACE;;;AAGF;EACE;EACA;EACA;EACA","file":"page--view.css"}
+9 -12
View File
@@ -1,18 +1,15 @@
body {
margin: 0;
overflow-y: hidden;
}
main {
margin-left: auto;
margin-right: auto;
// iFrame mode
.body--iframe {
overflow-y: scroll;
}
// Container rules must be provided by the projectStyles (theme).
//.container {
// max-width: 1200px;
// margin-left: auto;
// margin-right: auto;
// padding-left: 15px;
// padding-right: 15px;
//}
.fullscreen_layout {
background-color: #9cc3ff;
min-height: 100%;
background-image: url('https://i.ibb.co/pjwL8D1/shapelined-JBKdviwe-XI-unsplash.jpg');
background-size: cover;
}
+19
View File
@@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Block Development Tool</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
</head>
<body>
<div id="screen">Loading...</div>
<script src="/socket.io/socket.io.js"></script>
<script src="/scripts/dist/sync.min.js"></script>
</body>
</html>
+570 -7855
View File
File diff suppressed because it is too large Load Diff
+19 -9
View File
@@ -1,25 +1,29 @@
{
"name": "@axe-web/block-dev-tool",
"version": "1.0.21",
"version": "1.0.31",
"author": {
"name": "AXE-WEB",
"email": "office@axe-web.com",
"url": "https://axe-web.com/"
},
"scripts": {
"start": "component-dev",
"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"
"dev": "node server.js",
"view-mode": "VIEW_MODE=true node server.js",
"build-platform": "BLOCK_NAME=swiper-test MODULE_PATH= node ./build.js",
"dev-dev-tool": "rollup --config rollup.config.js --watch",
"build-dev-tool": "rollup --config rollup.config.js"
},
"engines": {
"node": ">=14.17.3"
},
"license": "ISC",
"type": "module",
"dependencies": {
"@axe-web/create-block": "^1.1.30",
"@braintree/sanitize-url": "^6.0.0",
"archiver": "^5.3.1",
"body-parser": "^1.20.2",
"browser-sync": "^2.27.9",
"config": "^3.3.7",
"escape-html": "^1.0.3",
@@ -36,12 +40,15 @@
"lodash-es": "^4.17.21",
"mem-fs": "^2.2.1",
"mem-fs-editor": "^9.5.0",
"mime-types": "^2.1.35",
"ngrok": "^5.0.0-beta.2",
"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"
"sass": "^1.50.1",
"ws": "^8.13.0"
},
"devDependencies": {
"@babel/preset-react": "^7.18.6",
@@ -56,13 +63,16 @@
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-jsx": "^1.0.3",
"rollup-plugin-terser": "^7.0.2",
"socket.io": "^4.6.2",
"styled-components": "^5.3.5"
},
"bin": {
"component-dev": "./server.js",
"component-build": "./build.js"
"component-build": "./build.js",
"component-info": "./debug.js"
},
"files": [
"env.js",
"helpers.js",
"debug.js",
"layouts/**/*.hbs",
+10 -3
View File
@@ -18,14 +18,18 @@ export async function buildHubspotEmail(blockName) {
});
}
export async function createDistFolder(blockName) {
const distPath = path.join('exports', 'hubspot', `${blockName}.module`);
export async function createDistFolder(blockName, projectPath = '') {
const distPath = path.join(projectPath, 'exports', 'hubspot', `${blockName}.module`);
await mkdir(distPath, {recursive: true})
return distPath;
}
export function getBlockFields(block = {}, type = 'content') {
if (!block['field_groups']) {
return [];
}
const fields_group = block['field_groups'].find((group) => group.name === type);
const fields = [];
@@ -248,9 +252,11 @@ export async function buildHubspotJSONFiles(distPath, metaData) {
export function handlebarsToHubl(handlebars) {
handlebars = handlebars.replace(/{{ else }}/g, '{% else %}');
handlebars = handlebars.replace(/{{else}}/g, '{% else %}');
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, '{% for item in module.');
handlebars = handlebars.replace(/{{\/each}}/g, '{% endfor %}');
handlebars = handlebars.replace(/{{base_url}}/g, '');
handlebars = handlebars.replace(/{esc_attr /g, '{');
@@ -262,6 +268,7 @@ export function handlebarsToHubl(handlebars) {
handlebars = handlebars.replace(/}}}/g, '}}');
handlebars = handlebars.replace(/{{/g, '{{module.');
handlebars = handlebars.replace(/{{module. /g, '{{ module.');
handlebars = handlebars.replace(/.url/g, '.src');
return handlebars;
}
+1 -1
View File
@@ -5,7 +5,7 @@ import {buildHubspotJSONFiles, createDistFolder, handlebarsToHubl,} from "./hubs
export async function buildHubspotPage(blockName) {
const {modulesPath, projectPath} = getConfigs();
const distPath = await createDistFolder(blockName);
const distPath = await createDistFolder(blockName, projectPath);
const srcPath = path.join(projectPath, 'src');
+98 -80
View File
@@ -9,125 +9,143 @@ use LightnCandy\LightnCandy;
use Brick\VarExporter\VarExporter;
trait Custom_Handlebars {
public array $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 );
}
public function register_default_handlebar_helpers(): void {
$this->add_handlebar( 'esc_url', function ( $context ) {
if ( function_exists( 'esc_url' ) ) {
return esc_url( $context );
}
return $context;
} );
return $context;
} );
$this->add_handlebar( 'esc_attr', function ( $context ) {
if ( function_exists( 'esc_attr' ) ) {
return esc_attr( $context );
}
$this->add_handlebar( 'esc_attr', function ( $context ) {
if ( function_exists( 'esc_attr' ) ) {
return esc_attr( $context );
}
return $context;
} );
return $context;
} );
$this->add_handlebar( 'esc_html', function ( $context ) {
if ( function_exists( 'esc_html' ) ) {
return esc_html( $context );
}
$this->add_handlebar( 'esc_html', function ( $context ) {
if ( function_exists( 'esc_html' ) ) {
return esc_html( $context );
}
return $context;
} );
return $context;
} );
$this->add_handlebar( 'safe_html', function ( $context ) {
if ( function_exists( 'wp_kses_post' ) ) {
return wp_kses_post( $context );
}
$this->add_handlebar( 'safe_html', function ( $context ) {
if ( function_exists( 'wp_kses_post' ) ) {
return wp_kses_post( $context );
}
return $context;
} );
}
return $context;
} );
}
public function add_handlebar( $key, $func ) {
$this->custom_handlebars[ $key ] = $func;
}
public function add_handlebar( $key, $func ): void {
$this->custom_handlebars[ $key ] = $func;
}
}
class Component_Builder {
use Custom_Handlebars;
use Custom_Handlebars;
public string $component_name = '';
public string $module_path = '';
public string $project_path = '';
public string $component_name = '';
public string $module_path = '';
public string $project_path = '';
private string $dist_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 __construct( $args = [] ) {
if ( ! isset( $args['backPath'] ) || ! isset( $args['projectPath'] ) || ! isset( $args['blockName'] ) || ! isset( $args['platform'] ) ) {
throw new \Exception( 'Error: Missing arguments. Make sure all parameter passed.' );
}
function build(): void {
$root_path = __DIR__ . '/' . $this->module_path . '/../../' . $this->project_path;
$this->module_path = $args['backPath'];
$this->project_path = $args['projectPath'];
$this->component_name = $args['blockName'];
$this->buildTemplatePhpFile( $root_path );
}
$this->dist_path = __DIR__ . '/' . $this->module_path . '/../../' . $this->project_path . '/exports' . '/' . $args['platform'];
$this->register_default_handlebar_helpers();
private function buildTemplatePhpFile( $root_path ) {
$file_name = $this->get_handlebars_template( "$root_path/src/$this->component_name.template.hbs" );
$this->add_handlebar( 'base_url', function ( $context ) {
$path = join( '/', [ $this->block_project, $this->block_name, 'templates' ] );
return join( '/', [ get_site_url(),'wp-content', 'axe-web-blocks', $path, '' ] );
} );
}
$output_folder = $root_path . '/exports/wordpress/templates';
rename( $file_name, "$output_folder/$this->component_name.template.php" );
}
function build(): void {
$root_path = __DIR__ . '/' . $this->module_path . '/../../' . $this->project_path;
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 ?? [],
]
);
$this->buildTemplatePhpFile( $root_path );
}
/**
* 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.
*/
private function buildTemplatePhpFile( $root_path ): void {
$file_name = $this->get_handlebars_template( "$root_path/src/$this->component_name.template.hbs" );
return self::create_cache_file( $path, $phpStr );
}
$output_folder = $this->dist_path . '/templates';
rename( $file_name, "$output_folder/$this->component_name.template.php" );
}
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 );
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 | Flags::FLAG_THIS,
'helpers' => $this->custom_handlebars ?? [],
]
);
$comment = "
/**
* 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.
*
* Compiled at " . date( 'Y-m-d h:i:s' ) . "
*/
";
$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." );
}
$t = file_put_contents( $file_path, '<?php ' . $comment . $content . ' ?>' );
if ( $t === false ) {
throw new \Exception( "Error: Can't generate HBS template to PHP file. Cache folder is not accessible." );
}
return $file_path;
}
return $file_path;
}
}
function build( $args = [] ) {
( new Component_Builder( $args['blockName'], $args['backPath'], $args['projectPath'] ) )->build();
/**
* Functions below will be triggered by JavaScript (index.js).
*/
function build( $args = [] ): void {
( new Component_Builder( $args ) )->build();
}
/**
* @throws ExportException
*/
function jsonToPhp( $args = [] ): ?string {
$json = $args['json'] ?? [];
$json = $args['json'] ?? [];
return VarExporter::export( $json );
return VarExporter::export( $json );
}
@@ -2,48 +2,54 @@
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>;
class <%= blockClassModel %>_Component {
class <%= blockClassModel %>_Component <% if (isComponentManager || isElementor) { %>extends \Core\Component <% } %>{
const VERSION = '<%= version %>';
public $block_project = '<%= ownerFilename %>';
public $block_name = '<%= blockFilename %>';
public $hook_prefix = 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>';
public function __construct() {
<% if (!isComponentManager && !isElementor) { %>public function __construct() {
parent::__construct();
// add_action( 'wp_enqueue_scripts', [ $this, 'register_assets' ] );
add_action( 'after_setup_theme', [ $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
<% } %>function register_assets(): void {
$base_path = plugin_dir_url( __FILE__ ); // In Plugins
// $base_path = get_template_directory_uri() . '/components/partials/<%= blockFilename %>/'; // In Theme
// $base_path = plugin_dir_url( __FILE__ ); // In Plugins
$base_path = get_template_directory_uri() . '/components/partials/<%= blockFilename %>/';
$style_deps = apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::style_deps', [ 'assets-style' ] );
wp_register_style( 'block-<%= blockFilename %>', $base_path . 'templates/styles/<%= blockFilename %>.min.css', $style_deps, self::VERSION );
wp_register_style( '<%= blockFilename %>',
$base_path . 'templates/styles/<%= blockFilename %>.min.css',
[ 'style-wp' ],
$version
);
wp_enqueue_style( '<%= blockFilename %>' );
$script_deps = apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::script_deps', [ 'assets-script' ] );
wp_register_script( 'block-<%= blockFilename %>', $base_path . 'templates/scripts/<%= blockFilename %>.min.js', $script_deps, self::VERSION, true );<% if (!isElementor) { %>
wp_register_script( 'script-<%= blockFilename %>',
$base_path . 'templates/scripts/<%= blockFilename %>.min.js',
[ 'jquery', 'swiper' ],
$version,
true
);
wp_enqueue_script( 'script-<%= blockFilename %>' );
if ( ! is_admin() ) {
wp_enqueue_style( 'block-<%= blockFilename %>' );
wp_enqueue_script( 'block-<%= blockFilename %>' );
}<% } %>
}
public function render( $args = [] ): void {
$args = array_merge( [], Helpers\<%= blockClassModel %>_Defaults::default_args( $args ), $args);
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 );
echo apply_filters( 'the_content', wpautop( $output ) );
return apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::content', $output );
}<% if (isElementor) { %>
function register_custom_logic(): void {
add_action( 'elementor/widgets/widgets_registered', [ $this, 'register_elementor_widget' ] );
}
<% 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() );
}<% } %>
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
<% if (isComponentManager) { %><%= blockClassModel %>_Component::get_instance();<% } else {
%>new <%= blockClassModel %>_Component();<% } %>
@@ -2,60 +2,47 @@
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>;
include_once __DIR__ . "/helpers/<%= blockClassModel %>_Defaults.php";
include_once __DIR__ . "/helpers/<%= blockClassModel %>_API.php";
class <%= blockClassModel %>_Component extends \Core\Component {
const VERSION = '<%= version %>';
public $block_project = '<%= ownerFilename %>';
public $block_name = '<%= blockFilename %>';
public $hook_prefix = 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>';
public function get_content( $args = [] ): string {
$args = array_merge( Helpers\<%= blockClassModel %>_Defaults::default_args(), $args );
$args = Helpers\<%= blockClassModel %>_API::prepare_args( $args );
$default_args = apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::default_args', [] ); // Not really practical.
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( array_merge( [], $args ), self::class );
//return apply_filters( 'the_content', $output );
return $output;
$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 {
<% 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_style( 'block-<%= blockFilename %>', $this->get_assets_path_url( 'templates/styles/<%= blockFilename %>.min.css' ), ['assets-style'], self::VERSION );<% if (include_script) { %>
wp_enqueue_script( 'block-<%= blockFilename %>', $this->get_assets_path_url( 'templates/scripts/<%= blockFilename %>.min.js' ), ['assets-script'], self::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_acf_block) { %> function register_acf_block() {
$this->register_block( __DIR__ . "/<%= blockFilename %>.block.json", [
'style_assets' => [
[
'name' => '<%= blockFilename %>',
'url' => $this->get_assets_path_url( 'templates/styles/<%= blockFilename %>.min.css' ),
]
],
'script_assets' => [
[
'name' => '<%= blockFilename %>',
'url' => $this->get_assets_path_url( 'templates/scripts/<%= blockFilename %>.min.js' ),
]
]
] );
}
<% } %><% if (include_elementor_widget) { %> function register_elementor_widget( $widgets_manager ) {
$widgets_manager->register_widget_type( new Helpers\<%= blockClassModel %>_Elementor_Widget() );
}
<% } %><% if (include_acf_block) { %> function register_acf_block() {
$this->register_block( __DIR__ . "/<%= blockFilename %>.block.json", [
'enqueue_assets' => function () {
wp_enqueue_style( '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 );<% } %>
},
'default' => Helpers\<%= blockClassModel %>_Defaults::default_args(),
'supports' => [
//'jsx' => true,
'color' => [
'background' => true,
'text' => true,
],
'spacing' => [
'margin' => [ 'top', 'bottom' ],
'padding' => [ 'top', 'bottom' ]
],
]
] );
}
<% } %><% if (include_native_gutenberg_block) { %> function register_native_gutenberg_block() {
register_block_type( __DIR__ . '/templates/gutenberg-block/block.json' );<% if (include_script) { %>
@@ -63,14 +50,13 @@ class <%= blockClassModel %>_Component extends \Core\Component {
$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__ ),
$this->get_assets_path_url( 'templates/gutenberg-block/build/front.js' ),
$asset_file_front['dependencies'],
$asset_file_front['version'],
true
);
} );<% } %>
}<% } %>
}
<%= blockClassModel %>_Component::get_instance();
@@ -18,43 +18,29 @@ class <%= blockClassModel %>_Elementor_Widget extends \Elementor\Widget_Base {
}
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();
$block_data = \Core\Block::get_block_data( __DIR__ . '/../<%= blockFilename %>.block.json' );
\Core\Elementor_Block::register_groups( $block_data, $this );
}
function get_style_depends() {
return [ '<%= blockFilename %>' ];
return [ 'block-<%= blockFilename %>' ];
}
function get_script_depends() {
return [ 'script-<%= blockFilename %>' ];
return [ 'block-<%= blockFilename %>' ];
}
protected function render() {
$settings = $this->get_settings_for_display();
$component = new \AXEWEB_Blocks\Blocks\Scytale\<%= blockClassModel %>\<%= blockClassModel %>_Component();
$component = new \AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\<%= blockClassModel %>_Component();
$args = self::prepare( $settings );
$component->render( $args );
}
public static function prepare( $args ): array {
// Prepare $args for render function.
// Prepare $args for render function.
return $args;
}
}
+39 -22
View File
@@ -1,5 +1,5 @@
import path from "path";
import {mkdir, copyFile} from "fs/promises";
import fs, {mkdir, copyFile} from "fs/promises";
import {capitalize, createFiles, getBlockName, getConfigs, readJSONFile} from "../../helpers.js";
import {fileURLToPath} from 'url';
import {copy} from "fs-extra";
@@ -9,10 +9,14 @@ 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) {
export async function buildWordPress(blockName, args = {}) {
const isBlock = args.platform === 'wordpress-acf-block';
const isElementor = args.platform === 'wordpress-elementor';
const isComponentManager = args.platform === 'wordpress-component-manager'
const {modulesPath, projectPath} = getConfigs();
const distPath = path.join(projectPath, 'exports', 'wordpress');
const distPath = path.join(projectPath, 'exports', args.platform);
// await mkdir(distPath, {recursive: true})
await mkdir(path.join(distPath, 'templates'), {recursive: true})
@@ -38,6 +42,7 @@ export async function buildWordPress(blockName, isBlock = false, isElementor = f
include_script: true,
include_elementor_widget: isElementor,
isElementor,
isComponentManager,
});
await copyFile(blockFilePath, path.join(distPath, data.blockFilename + '.block.json'));
@@ -46,7 +51,12 @@ export async function buildWordPress(blockName, isBlock = false, isElementor = f
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 execPHPFile(path.join(phpGeneratorPath, 'build.php'), 'build', {
blockName,
backPath,
projectPath,
platform: args.platform
});
await copyStaticFile(
path.join(projectPath, 'src', 'styles', `${data.blockFilename}.min.css`),
@@ -58,22 +68,29 @@ export async function buildWordPress(blockName, isBlock = false, isElementor = f
path.join(distPath, 'templates', 'scripts', `${data.blockFilename}.min.js`),
);
await copy(
path.join(projectPath, 'src', 'images'),
path.join(distPath, 'templates', 'images'),
);
try {
const imagesPath = path.join(projectPath, 'src', 'images');
await fs.access(imagesPath);
await copy(
path.join(imagesPath),
path.join(distPath, 'templates', 'images'),
);
} catch (err) {
// Folder doesn't exist.
}
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),
});
// 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, [{
@@ -94,13 +111,13 @@ export async function buildWordPress(blockName, isBlock = false, isElementor = f
generatorsPath: path.join(__dirname)
});
await createFiles(data, [{
from: `templates/helpers/Template_API.php`,
to: `helpers/${data.blockClassModel}_API.php`,
}], {
pathDist: distPath,
generatorsPath: path.join(__dirname)
});
// await createFiles(data, [{
// from: `templates/helpers/Template_API.php`,
// to: `helpers/${data.blockClassModel}_API.php`,
// }], {
// pathDist: distPath,
// generatorsPath: path.join(__dirname)
// });
} else {
await createFiles(data, [{
from: `templates/Template_Basic_Component.php`,
+22
View File
@@ -47,4 +47,26 @@ export default [{
commonjs(),
!devMode && terser()
]
}, {
input: 'layouts/scripts/sync.jsx',
output: {
file: 'layouts/scripts/dist/sync.min.js',
sourcemap: devMode
},
plugins: [
nodeResolve({
extensions: [".js"],
}),
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
preventAssignment: true,
}),
babel({
compact: false,
babelHelpers: 'bundled',
presets: ["@babel/preset-react"],
}),
commonjs(),
!devMode && terser()
]
}];
+43
View File
@@ -0,0 +1,43 @@
import {getBlockConfigs, getBlockName, getBlockVariations, handlebarLayoutsPath} from "../helpers.js";
import path from "path";
import {buildAssetFiles} from "../inc/changes-watcher.js";
export default async function (req, res) {
const blockName = req.query.block;
const block = getBlockName(blockName);
const blockDir = path.join('blocks', '@' + block.project, block.name);
const dataFiles = await getBlockVariations(blockDir);
const data = await getDataOfFrame(!!req.query.iframe, Object.assign({}, block, {dataFiles}));
if (data.error && data.errorMessage) {
return res.send(data.errorMessage);
}
const baseView = req.params.baseView ?? 'alignfull';
await buildAssetFiles(blockDir);
res.render(baseView, data)
}
async function getDataOfFrame(isIframe = false, block) {
const data = {config: await getBlockConfigs(block)};
if (data.error && data.errorMessage) {
return data;
}
data.helpers = {
include_partial: (filesPath) => handlebarLayoutsPath(filesPath),
}
if (isIframe) {
data.iframeMode = true;
}
data.staticFilesPath = `/block/${block.project}/${block.name}`;
return data;
}
+30
View File
@@ -0,0 +1,30 @@
import {
getBlockData,
getBlockName,
getBlockVariations,
getListOfDesignPreviewFiles,
} from "../helpers.js";
import path from "path";
import fs from "fs/promises";
export default async function (req, res, next) {
let variationName = req.query.name ? req.query.name : 'default';
const block = getBlockName(req.query.block);
const blockDir = path.join('blocks', '@' + block.project, block.name);
const dataFiles = await getBlockVariations(blockDir);
const data = await getBlockData(variationName, {projectPath: blockDir});
let designPreviewFiles = [];
try {
designPreviewFiles = getListOfDesignPreviewFiles(variationName, await fs.readdir(path.join(blockDir, 'design', 'preview')), block);
} catch (err) {
console.log('Preview Design doesn\'t exist');
}
return res.json({
dataOptions: dataFiles,
designPreview: designPreviewFiles,
data,
});
}
+47
View File
@@ -0,0 +1,47 @@
import {getBlockConfigs, getBlockName, getBlockVariations, handlebarLayoutsPath, verifyVersion} from "../helpers.js";
import path from "path";
import {REGISTRY_URL} from "@axe-web/create-block/env.js";
export async function index(req, res, next) {
const blockName = req.query.block;
const block = getBlockName(blockName);
const blockDir = path.join('blocks', '@' + block.project, block.name);
const dataFiles = await getBlockVariations(blockDir);
const data = await getBlockConfigs(Object.assign({}, block, {dataFiles}));
if (data.error && data.errorMessage) {
// TODO: Throw Error.
}
data.helpers = {
include_partial: (filesPath) => handlebarLayoutsPath('.', filesPath),
}
// TODO: Make sure we sync block before we start working on it.
try {
const verifiedVersion = await verifyVersion(blockName, blockDir, REGISTRY_URL);
if (!verifiedVersion && !req.query['ignoreVersionSync']) {
return res.render('sync', data);
}
} catch (err) {
const errorMessage = "Can't verify block version.";
console.log(errorMessage, err);
return next(new Error(errorMessage));
}
data.blockName = blockName;
data.baseView = 'alignfull';
data.port = `/view/${data.baseView}`;
let previewFrameUrl = ``; // This variable is used in `*.hbs` and it will be updated once BrowserSync is ready.
data.previewFrameUrl = `${previewFrameUrl}${data.port}`;
data.previewFrameUrlNewWindow = `${previewFrameUrl}${data.port}`;
data.shareUrl = '';
data.viewMode = process.env.VIEW_MODE ?? false;
res.render('index', data);
}
+7
View File
@@ -0,0 +1,7 @@
export default async function (req, res, next) {
if (process.env.VIEW_MODE) {
throw new Error("Can't publish in view mode.");
}
}
+23
View File
@@ -0,0 +1,23 @@
export function staticFiles(req, res) {
const fileName = req.params[0];
const path = `blocks/@${req.params.project}/${req.params.blockName}/src/${fileName}`;
return deliverStaticFile(path, res);
}
export function previewFiles(req, res) {
const fileName = req.params[0];
const path = `blocks/@${req.params.project}/${req.params.blockName}/design/${fileName}`;
return deliverStaticFile(path, res);
}
function deliverStaticFile(path, res) {
// If file doesn't exist, return 404.
res.sendFile(path, {root: './'}, function (err) {
if (err) {
res.send('File not found.');
res.status(err.status).end();
}
});
}
+11
View File
@@ -0,0 +1,11 @@
import {syncFilesWithCloud} from "../helpers.js";
export default async function (req, res, next) {
try {
await syncFilesWithCloud(req.body.block, null, true);
} catch (err) {
return res.status(500).json({status: 500, message: err.message});
}
res.json({status: 200, message: 'Successfully synced!'});
}
+24 -345
View File
@@ -1,356 +1,35 @@
#!/usr/bin/env node
import path from 'path';
import fetch from "node-fetch";
import express from 'express';
import {create} from 'express-handlebars';
import browserSync from 'browser-sync';
import config from 'config';
import gulp from 'gulp';
import babel from "gulp-babel";
import uglify from "gulp-uglify";
import rename from "gulp-rename";
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';
import {init} from "./inc/server.js";
import {index} from "./routes/index.js";
import dataRoute from "./routes/data.js";
import baseView from "./routes/base-view.js";
import syncRoute from "./routes/sync.js";
import publishRoute from "./routes/publish.js";
import {ViewSync} from "./inc/view-sync.js";
import {previewFiles, staticFiles} from "./routes/static-files.js";
import {setupWatcher} from "./inc/changes-watcher.js";
/**
* Constants
*/
const {expressApp, httpServer} = init();
const PRODUCTION_REGISTRY_URL = 'https://blocks-registery.axe-web.com';
const viewSync = new ViewSync(httpServer);
setupWatcher(viewSync);
const {isDev, modulesPath, projectPath, developmentBlockName} = getConfigs();
const blocksRegistry = isDev ? 'http://localhost:3020' : PRODUCTION_REGISTRY_URL;
const PORT = process.env.PORT || 3010;
/**
* 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: ['.'], 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);
}
}
httpServer.listen(PORT, '0.0.0.0', () => {
console.log(`Server app listening on port ${PORT}`)
});
app.engine('.hbs', hbs.engine);
app.set('view engine', '.hbs');
app.set('views', path.join(modulesPath, 'layouts'));
// Middleware
// expressApp.use(setHeaders);
//
// Routes
//
expressApp.get('/', index);
expressApp.get('/data', dataRoute);
expressApp.get('/view/:baseView', baseView);
expressApp.get('/design/:project/:blockName/*', previewFiles);
expressApp.get('/block/:project/:blockName/*', staticFiles);
expressApp.post('/sync', syncRoute);
expressApp.get('/publish', publishRoute);
app.get('/', 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 baseView = config.has('baseView') ? config.get('baseView') : 'container';
const baseViewUrl = `view/${baseView}`;
data.helpers = {
port,
include_partial: (filesPath) => handlebarLayoutsPath(modulesPath, filesPath),
baseView,
previewFrameUrl: `${previewFrameUrl}/${baseViewUrl}`,
}
res.render('index', data);
});
app.get('/view/:baseView', async (req, res) => {
let jsonFileName = req.query.data ? req.query.data : 'default';
const data = await getBlockConfigs(jsonFileName, {includeConfigs: true, projectPath, modulesPath, dataFiles});
if (data.error && data.errorMessage) {
return res.send(data.errorMessage);
}
const blockName = config.has('blockName') ? config.get('blockName') : developmentBlockName;
data.helpers = {
include_partial: (filesPath) => handlebarLayoutsPath(modulesPath, filesPath),
include_block_template: () => handlebarLayoutsPath(projectPath, 'src', `${blockName}.template`),
section_class: `${blockName}--${jsonFileName}`,
base_url: '/'
}
const baseView = req.params.baseView ?? 'container';
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;
}
}
res.json({success: true});
await fs.unlink(path.join(projectPath, 'dist.zip'));
});
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,
});
});
// 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) {
// console.log(event, file);
console.log(message);
}
if (extension) {
extension = "*." + extension;
}
bs.reload(extension);
}
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()).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(path.posix.join(projectPath, 'src')));
}
function buildStyleFiles(done) {
return gulp.src(path.join(projectPath, 'src/**/*.scss'), {base: path.posix.join(projectPath, 'src')})
.pipe(sourcemaps.init({}))
.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(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) {
return dataFiles
.filter((fileName) => fileName.split('.').pop() === 'json')
.map((fileName) => {
const splitName = fileName.split('.');
splitName.pop();
return splitName.join('');
})
.sort();
}
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.
}
+10
View File
@@ -0,0 +1,10 @@
/**
* FUTURE TESTS.
*
* # ENV
* In `blocks-builder` service, we update MODULE_PATH and PROJECT_PATH environment variables before we run platform
* bundle build process. Actually before we call buildExportFiles().
*
* We have to make sure that this logic is working properly and stable.
*
*/