21 Commits

Author SHA1 Message Date
roman a22a179eff Added nested blocks support (inner_blocks). ACF Only. 2025-05-12 17:28:39 -06:00
roman 95e0cae3ed Merge with latest. 2025-05-11 17:29:57 -06:00
roman 92e4f1ea2d version upgrade 2025-03-29 20:27:56 -06:00
roman 0062bb4506 1.0.33 2025-03-29 20:21:32 -06:00
roman 5dd326be0f Pass $args parameter to content hook. 2025-03-29 20:20:51 -06:00
roman 00efc1677d 1.0.33 2024-12-08 18:44:15 -07:00
roman 38c49a64ad Make sure all images are updated on Global Blocks. 2024-12-08 18:43:39 -07:00
roman 044f721d34 Updated logic of mobile/tablet sizes & scrollbars. 2024-06-23 20:24:57 -06:00
roman 74f4b11ad6 Updated logic of mobile/tablet sizes & scrollbars. 2024-06-23 20:21:44 -06:00
roman 1b2cf2697f Updated logic of mobile/tablet sizes & scrollbars. 2024-06-23 20:18:16 -06:00
roman bcad45d1e8 Temporary disabled ngrok (share live URL option) 2024-06-23 01:26:14 -06:00
roman 3daa725309 Updated logic of Elementor's build. Support Elementor_Widget Trait. 2024-05-24 15:34:12 -06:00
roman 438619c4d9 Added axeweb-block classNames to blocks in Elementor 2024-05-21 12:50:58 -06:00
roman 1b2795fa40 Merge pull request 'Update path to static files of ACF block.' (#13) from wordpress-build into master
Reviewed-on: AXE-WEB/block-dev-tool#13
2024-05-21 09:44:13 +00:00
roman 31786581bd Update path to static files of ACF block. 2024-05-21 03:38:18 -06:00
roman e25f1aa4f1 Merge pull request '1.0.32' (#12) from wordpress-build into master
Reviewed-on: AXE-WEB/block-dev-tool#12
2024-05-21 09:05:32 +00:00
roman f3cd0e5876 1.0.32 2024-05-21 03:03:24 -06:00
roman d6b0ff866b Merge pull request 'Updated the bundle of WordPress block.' (#11) from wordpress-build into master
Reviewed-on: AXE-WEB/block-dev-tool#11
2024-05-21 09:02:22 +00:00
roman 4a7ddf7d29 Updated the bundle of WordPress block. 2024-05-21 03:01:46 -06:00
roman 03a8833f46 Merge pull request 'fix windows archive issue' (#10) from dev into master
Reviewed-on: AXE-WEB/block-dev-tool#10
2024-04-15 12:20:32 +00:00
roman bf8f0e857d fix windows archive issue 2024-04-15 00:07:18 +03:00
44 changed files with 779 additions and 869 deletions
-19
View File
@@ -1,19 +0,0 @@
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
-9
View File
@@ -9,17 +9,8 @@ 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-component-manager',
title: 'WordPress (Component Manager)'
}, {
name: 'wordpress-elementor',
title: 'WordPress Elementor'
}, {
name: 'hubspot',
title: 'Hubspot'
+42 -113
View File
@@ -14,7 +14,6 @@ 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);
@@ -62,16 +61,14 @@ export async function getBlockData(jsonFileName = 'default', {projectPath} = {js
return data;
}
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.
export function getBlockConfigs(args = {modulesPath: '', dataFiles: []}) {
const updatedConfig = Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object.
{
projectDir: '', dataFiles: args.dataFiles.map((name) => {
projectDir: args.modulesPath, dataFiles: args.dataFiles.map((name) => {
return {
name,
};
}), remToPx: 16,
}), remToPx: config.has('remToPx') ? config.get('remToPx') : 16,
});
// Avoid cache conflict on Global Project css/js files.
@@ -84,13 +81,13 @@ export async function getBlockConfigs(args) {
}
export function getBlockName(name = '') {
// Remove all @ from the beginning of the string.
name = name.replace(/^@/, '');
if (name.startsWith('@')) {
name = name.substring(1);
}
const arr = name.split('/');
return {
blockName: `@${arr[0]}/${arr[1]}`,
project: arr[0],
name: arr[1],
};
@@ -129,46 +126,33 @@ export function capitalize(str) {
export async function zipProject(srcDir, outputFileName = 'dist.zip') {
// create a file to stream archive data to.
const output = await fsExtra.createWriteStream(outputFileName);
const archive = archiver('zip', {});
// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
output.on('close', function () {
console.log(archive.pointer() + ' total bytes');
console.log('archiver has been finalized and the output file descriptor has closed.');
return new Promise((resolve, reject) => {
// Create a file to write the archive data
const archive = archiver('zip', {
zlib: {level: 9} // Compression level
});
// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
output.on('end', function () {
console.log('Data has been drained');
});
// good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on('warning', function (err) {
if (err.code === 'ENOENT') {
// log warning
} else {
// throw error
throw err;
}
});
// good practice to catch this error explicitly
// Listen for errors on the archiver
archive.on('error', function (err) {
throw err;
reject(err);
});
// pipe archive data to the file
// Handle closure of the output file stream
output.on('close', function () {
console.log(`Archive created successfully and it is ${archive.pointer()} total bytes`);
resolve();
});
// Pipe archive data to the file
archive.pipe(output);
// append files from a subdirectory, putting its contents at the root of archive
// Add files to the archive
archive.directory(srcDir, false);
// finalize the archive (ie we are done appending files but streams have to finish yet)
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
await archive.finalize();
// Finalize the archive - this is very important to ensure the file stream is finished
archive.finalize();
});
}
export async function buildExportFiles(blockName, platform) {
@@ -264,7 +248,7 @@ export async function isFileEmpty(filePath, ignoreComments = false) {
export function replaceNames(content, images, uploadedImages) {
images.forEach((image, index) => {
content = content.replace(image, uploadedImages[index].fileName);
content = content.replaceAll(image, uploadedImages[index].fileName);
});
return content;
@@ -278,7 +262,7 @@ export async function getBlockFromCloud(blockName, blocksRegistry) {
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}]`
const message = "⚠️ Block not found, please contact administrator."
throw new Error(message);
}
@@ -290,15 +274,12 @@ export async function getBlockFromCloud(blockName, blocksRegistry) {
return responseData;
}
export async function verifyVersion(blockName, projectPath, blocksRegistry) {
const block = await getBlockFromCloud(blockName, blocksRegistry);
if (!block) {
throw new Error(`Block not found. [${blockName}]`);
}
export async function verifyVersion(projectPath, blocksRegistry) {
const blockJson = await readJSONFile(path.join(projectPath, `block.json`));
if (blockJson.error) {
return false; // Block doesn't exist locally.
const blockName = getBlockName(blockJson.name);
if (typeof blockJson.version === 'undefined' || !blockName.name) {
return true;
}
/*
@@ -306,28 +287,25 @@ export async function verifyVersion(blockName, projectPath, blocksRegistry) {
* Let's detect the latest version.
*/
const block = await getBlockFromCloud('@' + blockJson.name, blocksRegistry);
return block.version === blockJson.version;
}
export async function syncFilesWithCloud(blockName, bs, sourceFiles = false) {
const block = await getBlockFromCloud(blockName, REGISTRY_URL);
export async function syncFilesWithCloud(jsonBlockPath, bs, sourceFiles = false) {
const blockJson = await readJSONFile(jsonBlockPath);
const blockName = blockJson.name.startsWith('@') ? blockJson.name : `@${blockJson.name}`;
let fileExists = false;
try {
await readJSONFile(path.join('blocks', '@' + block.project, block.name, 'block.json'));
fileExists = true;
} catch (e) {
// Dir doesn't exist.
}
bs.pause();
// TODO: Pause watcher of the block?
blockName = `@${block.project}/${block.name}`;
// Looks like it takes time to pause the browser-sync server, so delay(setTimeout) is necessary.
await new Promise((resolve) => setTimeout(() => resolve(), 1000));
await new Promise((resolve) => {
const args = sourceFiles ? '--source' : '';
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) => {
// exec(`BLOCK_NAME=${blockName} node ${createBlockModulePath}/create-block.js sync ${args}`, (err, stdout, stderr) => {
exec(`node ${createBlockModulePath}/create-block.js sync ${args}`, (err, stdout, stderr) => {
if (err || stderr) {
const message = err || stderr;
console.error('Error:', message);
@@ -339,54 +317,5 @@ export async function syncFilesWithCloud(blockName, bs, sourceFiles = false) {
});
});
// 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}`,
};
});
bs.resume();
}
-109
View File
@@ -1,109 +0,0 @@
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
@@ -1,4 +0,0 @@
export function setHeaders(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
}
-53
View File
@@ -1,53 +0,0 @@
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
@@ -1,50 +0,0 @@
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});
});
}
}
-1
View File
@@ -1,4 +1,3 @@
<!doctype html>
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
-1
View File
@@ -1,4 +1,3 @@
<!doctype html>
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
-1
View File
@@ -1,4 +1,3 @@
<!doctype html>
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
+2 -4
View File
@@ -1,4 +1,3 @@
<!doctype html>
<html lang="en">
<head>
@@ -8,13 +7,12 @@
<title>Block Development Tool</title>
</head>
<body class="{{#if viewMode }}view-mode{{/if}}">
<body>
{{> (include_partial "layouts/partials/toolbar") }}
<script>
window.devTool = {
blockName: '{{ blockName }}',
previewFrameUrl: '{{ previewFrameUrl }}',
{{#if publicUrl}}
publicUrl: true,
@@ -23,7 +21,7 @@
</script>
<div class="preview">
<iframe id="preview_frame" src="{{ previewFrameUrl }}?iframe=true&block={{blockName}}" class="breakpoint"></iframe>
<iframe id="preview_frame" src="{{ previewFrameUrl }}?iframe=true" class="breakpoint"></iframe>
</div>
<script src="/scripts/dist/index.min.js"></script>
+1 -1
View File
@@ -10,5 +10,5 @@
<link rel="stylesheet" href="{{ this }}">
{{/each}}
{{/if}}<link rel="stylesheet" href="/styles/page--view.css">{{# if config.blockName}}
<link rel="stylesheet" id="block-stylesheet" href="{{staticFilesPath}}/styles/{{ config.blockName }}.min.css?v=1">{{/if}}
<link rel="stylesheet" href="/styles/{{ config.blockName }}.min.css">{{/if}}
</head>
+1 -1
View File
@@ -10,5 +10,5 @@
{{/each}}
{{/if}}
{{#if config.blockName }}
<script id="block-script" src="{{staticFilesPath}}/scripts/{{ config.blockName }}.min.js?v=1"></script>
<script src="/scripts/{{ config.blockName }}.min.js"></script>
{{/if}}
+1 -1
View File
@@ -15,6 +15,6 @@
{{#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>
<a href="{{ previewFrameUrl }}" target="_blank" class="open_in_new_tab" title="Open in New Window"></a>
</div>
</header>
+1 -1
View File
@@ -1 +1 @@
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")}))}()}();
let e;window.initBlock=function(e="",n="",r){t=function(){document.querySelectorAll(n).forEach((e=>r(e)))},t()};let t,n={};function r(r={}){r.template&&(e=r.template),r.data&&(n=r.data),e&&function(e,n,r){const a=Handlebars.compile(e);let o;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(){return"/"})),Handlebars.registerHelper("inner_blocks",(function(e){const t=e.data.root.inner_blocks;return t?new Handlebars.SafeString(t):""}));try{o=a(n)}catch(e){o=`<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=o,t&&t()}(e,n||{},document.getElementById("hbs-container"))}!function(){function e(){const e=new URLSearchParams(window.location.search),t={};for(const[n,r]of e)t[n]=r;return t}!function(){const t=new URLSearchParams({name:e().data||"default"});fetch(`/data?${t}`).then((e=>e.json())).then((e=>{n=e.data,r({data:n})}))}(),window.addEventListener("message",(function(e){const t=e.data,a="dataUpdate:";if("string"==typeof t&&t.startsWith(a))try{n=JSON.parse(t.substring(a.length)),r({data:n})}catch(e){console.log("Error parsing incoming data.",e)}}))}(),function(){const e=window.io.connect();e.on("error",(function(e){console.log(e)})),e.on("templateUpdate",(function(e){r({template:e.template})}))}();
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+12 -50
View File
@@ -17,8 +17,6 @@ function initBlock(blockName = '', selector = '', cb) {
// Data Updates Listeners.
(function () {
const block = getQueryParams().block;
loadDataOptions();
listenToDataOptionsUpdates();
@@ -52,10 +50,7 @@ function initBlock(blockName = '', selector = '', cb) {
}
function loadDataOptions() {
const queryParameters = new URLSearchParams({
block,
name: getQueryParams().data || 'default'
});
const queryParameters = new URLSearchParams({name: getQueryParams().data || 'default'});
fetch(`/data?${queryParameters}`)
.then((response) => response.json())
.then((response) => {
@@ -63,12 +58,14 @@ function initBlock(blockName = '', selector = '', cb) {
updateBlock({data});
})
}
})();
// Listen to Template updates.
(function () {
initSocket();
function initSocket() {
const socket = window.io.connect('', {query: `block=${block}`});
const socket = window.io.connect();
socket.on('error', function (err) {
console.log(err);
@@ -81,45 +78,8 @@ function initBlock(blockName = '', selector = '', cb) {
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) {
@@ -156,13 +116,16 @@ function initBlock(blockName = '', selector = '', cb) {
});
Handlebars.registerHelper('base_url', function () {
const list = ['block'];
return '/';
});
if (block) {
list.push(block.substr(1));
Handlebars.registerHelper('inner_blocks', function (options) {
const content = options.data.root['inner_blocks'];
if (!content) {
return '';
}
return ['', ...list, ''].join('/');
return new Handlebars.SafeString(content);
});
let html;
@@ -181,4 +144,3 @@ function initBlock(blockName = '', selector = '', cb) {
reload();
}
}
})();
+18 -19
View File
@@ -46,25 +46,17 @@ function SyncScreen() {
</>
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');
setLoading(true);
let data = {};
try {
const response = await fetch('/sync', {
method: 'POST',
body: JSON.stringify({block: getQueryParams().block}),
body: JSON.stringify({ignore: true}),
headers: {
'Content-Type': 'application/json'
}
});
data = await response.json();
} catch (err) {
setError('Error: ' + err.message);
@@ -81,21 +73,28 @@ function SyncScreen() {
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 = {};
async function runSyncLogic() {
setLoading('syncing');
for (const [key, value] of urlParams) {
params[key] = value;
let data = {};
try {
const response = await fetch('/sync', {method: 'POST'});
data = await response.json();
} catch (err) {
setError('Error: ' + err.message);
setLoading(false);
return;
}
return params;
if (data.status !== 200) {
setError(data.message);
setLoading(false);
return;
}
setTimeout(() => window.location.reload(), 1000);
}
}
init();
@@ -169,7 +169,7 @@ function DataOptions(props = {}) {
}
async function fetchDataOptions(name = 'default') {
const queryParameters = new URLSearchParams({name, block: window.devTool.blockName});
const queryParameters = new URLSearchParams({name});
const response = await fetch(`/data?${queryParameters}`);
return await response.json();
}
@@ -130,7 +130,8 @@ export function DesignPreview({previewOption = {widthDimension: 0}}) {
return 0;
}
const scrollbarOffset = 7;
// const scrollbarOffset = 7; // Looks like browser scrollbar is not included in the width calculation anymore.
const scrollbarOffset = 0;
return window.innerWidth / 2 - previewOption.widthDimension / 2 - scrollbarOffset;
}
-1
View File
@@ -43,7 +43,6 @@ 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);
+2 -1
View File
@@ -65,7 +65,8 @@ function Responsive(props = {}) {
function updateController() {
let frameBreakpoint = breakpoint;
if (typeof frameBreakpoint !== 'string') {
const scrollbarWidth = 15;
// const scrollbarWidth = 15; // Looks like browser's logic was changed and now scrollbar is not included in the width.
const scrollbarWidth = 0;
frameBreakpoint = (scrollbarWidth + frameBreakpoint) + 'px';
}
-1
View File
@@ -1,4 +1,3 @@
<!doctype html>
<html lang="en">
{{> (include_partial "layouts/partials/head") }}
-3
View File
@@ -8,9 +8,6 @@ body {
font-size: 1rem;
background-color: #F7FAFC;
}
body.view-mode #publish-btn {
display: none;
}
.btn {
--btn-color: #333;
+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;;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"}
{"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;;;AAGF;EACE;EACA;EACA;EACA;EAGA;EACA;EAEA;EACA;EACA;EACA;;AAEA;EACE;EAEA;EACA;;;AHiEJ;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"}
-6
View File
@@ -8,12 +8,6 @@ body {
font-size: 1rem;
//overflow: none;
background-color: #F7FAFC;
&.view-mode {
#publish-btn {
display: none;
}
}
}
@import "buttons";
-1
View File
@@ -1,4 +1,3 @@
<!doctype html>
<html lang="en">
<head>
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@axe-web/block-dev-tool",
"version": "1.0.31",
"version": "1.0.36",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@axe-web/block-dev-tool",
"version": "1.0.31",
"version": "1.0.36",
"license": "ISC",
"dependencies": {
"@axe-web/create-block": "^1.1.30",
+4 -5
View File
@@ -1,16 +1,15 @@
{
"name": "@axe-web/block-dev-tool",
"version": "1.0.31",
"version": "1.0.36",
"author": {
"name": "AXE-WEB",
"email": "office@axe-web.com",
"url": "https://axe-web.com/"
},
"scripts": {
"info": "node debug.js",
"dev": "node server.js",
"view-mode": "VIEW_MODE=true node server.js",
"build-platform": "BLOCK_NAME=swiper-test MODULE_PATH= node ./build.js",
"info": "BLOCK_NAME=ebooks-resources MODULE_PATH= node debug.js",
"dev": "BLOCK_NAME=ebooks-resources MODULE_PATH= node server.js",
"build-platform": "BLOCK_NAME=ebooks-resources MODULE_PATH= node ./build.js",
"dev-dev-tool": "rollup --config rollup.config.js --watch",
"build-dev-tool": "rollup --config rollup.config.js"
},
+2 -8
View File
@@ -35,14 +35,6 @@ trait Custom_Handlebars {
return $context;
} );
$this->add_handlebar( 'safe_html', function ( $context ) {
if ( function_exists( 'wp_kses_post' ) ) {
return wp_kses_post( $context );
}
return $context;
} );
}
public function add_handlebar( $key, $func ): void {
@@ -91,6 +83,8 @@ class Component_Builder {
private function get_handlebars_template( $path = '' ): string {
$template = file_get_contents( $path );
$template = preg_replace( '/{{inner_blocks}}/', '<InnerBlocks/>', $template );
$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,
@@ -2,19 +2,13 @@
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>;
class <%= blockClassModel %>_Component <% if (isComponentManager || isElementor) { %>extends \Core\Component <% } %>{
class <%= blockClassModel %>_Component extends \Core\Component {
const VERSION = '<%= version %>';
public $block_project = '<%= ownerFilename %>';
public $block_name = '<%= blockFilename %>';
public $hook_prefix = 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>';
<% if (!isComponentManager && !isElementor) { %>public function __construct() {
parent::__construct();
// add_action( 'wp_enqueue_scripts', [ $this, 'register_assets' ] );
add_action( 'after_setup_theme', [ $this, 'register_assets' ] );
}
<% } %>function register_assets(): void {
function register_assets(): void {
$base_path = plugin_dir_url( __FILE__ ); // In Plugins
// $base_path = get_template_directory_uri() . '/components/partials/<%= blockFilename %>/'; // In Theme
@@ -22,12 +16,14 @@ class <%= blockClassModel %>_Component <% if (isComponentManager || isElementor)
wp_register_style( 'block-<%= blockFilename %>', $base_path . 'templates/styles/<%= blockFilename %>.min.css', $style_deps, self::VERSION );
$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( 'block-<%= blockFilename %>', $base_path . 'templates/scripts/<%= blockFilename %>.min.js', $script_deps, self::VERSION, true );
}
public function prepare_for_print() {
if ( ! is_admin() ) {
wp_enqueue_style( 'block-<%= blockFilename %>' );
wp_enqueue_script( 'block-<%= blockFilename %>' );
}<% } %>
}
}
public function get_content( $args = [] ): string {
@@ -37,8 +33,8 @@ class <%= blockClassModel %>_Component <% if (isComponentManager || isElementor)
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( $args, self::class );
return apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::content', $output );
}<% if (isElementor) { %>
return apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::content', $output, $args );
}
function register_custom_logic(): void {
add_action( 'elementor/widgets/widgets_registered', [ $this, 'register_elementor_widget' ] );
@@ -47,9 +43,25 @@ class <%= blockClassModel %>_Component <% if (isComponentManager || 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() );
}<% } %>
}
<% if (isComponentManager) { %><%= blockClassModel %>_Component::get_instance();<% } else {
%>new <%= blockClassModel %>_Component();<% } %>
function register_acf_block() {
$this->register_block( __DIR__ . "/<%= blockFilename %>.block.json", [
'style_assets' => [
[
'name' => 'block-<%= blockFilename %>',
'url' => $this->get_assets_path_url( 'templates/styles/<%= blockFilename %>.min.css' ),
]
],
'script_assets' => [
[
'name' => 'block-<%= blockFilename %>',
'url' => $this->get_assets_path_url( 'templates/scripts/<%= blockFilename %>.min.js' ),
]
]
] );
}
}
<%= blockClassModel %>_Component::get_instance();
// new <%= blockClassModel %>_Component();
@@ -1,62 +0,0 @@
<?php
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>;
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 {
$default_args = apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::default_args', [] ); // Not really practical.
$args = apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::prepare_args', array_merge( $default_args, $args ) );
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( $args, self::class );
return apply_filters( 'axeweb_blocks/<%= ownerFilename %>/<%= blockFilename %>::content', $output );
}
<% if (!include_acf_block && !include_native_gutenberg_block) { %>function register_assets(): void {
$version = get_plugin_data( __DIR__ . "/../../scytale-custom-blocks.php" )['Version']; // In Plugins
// $version = \Core\Global_Functions::get_current_version_number(); // In Theme
// $base_path = get_template_directory_uri() . '/components/partials/<%= blockFilename %>/';
wp_enqueue_style( 'block-<%= blockFilename %>', $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_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_native_gutenberg_block) { %> function register_native_gutenberg_block() {
register_block_type( __DIR__ . '/templates/gutenberg-block/block.json' );<% if (include_script) { %>
add_action( 'wp_enqueue_scripts', function () {
$asset_file_front = include( plugin_dir_path( __FILE__ ) . '/templates/gutenberg-block/build/front.asset.php' );
wp_enqueue_script(
'gutenberg-<%= blockFilename %>-scripts-front',
$this->get_assets_path_url( 'templates/gutenberg-block/build/front.js' ),
$asset_file_front['dependencies'],
$asset_file_front['version'],
true
);
} );<% } %>
}<% } %>
}
<%= blockClassModel %>_Component::get_instance();
@@ -1,14 +0,0 @@
<?php
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
class <%= blockClassModel %>_API {
static function prepare_args( $args = [] ) {
// $args = array_merge( [], $args );
return $args;
}
}
@@ -1,17 +0,0 @@
<?php
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
/**
* Component's default args.
*/
class <%= blockClassModel %>_Defaults {
public static function default_args(): array {
$args = <%- defaultData %>;
// $args['base_url'] = \Core\Global_Functions::get_file_url( __DIR__ . '/../templates/' );
return $args;
}
}
@@ -3,6 +3,8 @@
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
class <%= blockClassModel %>_Elementor_Widget extends \Elementor\Widget_Base {
use \Core\Traits\Elementor_Widget;
const PLUGIN_NAME = '<%= blockFilename %>';
public function get_name() {
@@ -35,12 +37,8 @@ class <%= blockClassModel %>_Elementor_Widget extends \Elementor\Widget_Base {
$component = new \AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\<%= blockClassModel %>_Component();
$args = self::prepare( $settings );
$args = $this->prepare( $settings );
$component->render( $args );
}
public static function prepare( $args ): array {
// Prepare $args for render function.
return $args;
}
}
-37
View File
@@ -10,10 +10,6 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
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', args.platform);
@@ -37,12 +33,7 @@ export async function buildWordPress(blockName, args = {}) {
ownerClass: owner.replace(/ /ig, '_'),
ownerFilename: owner.toLowerCase().replace(/ /ig, '-'),
templateFormat: 'php',
include_acf_block: isBlock,
include_native_gutenberg_block: false,
include_script: true,
include_elementor_widget: isElementor,
isElementor,
isComponentManager,
});
await copyFile(blockFilePath, path.join(distPath, data.blockFilename + '.block.json'));
@@ -84,15 +75,6 @@ export async function buildWordPress(blockName, args = {}) {
json: await readJSONFile(path.join(projectPath, 'data', 'default.json'), "utf8"),
});
// await createFiles(Object.assign({}, data, {defaultData: phpDataObject}), [{
// from: `templates/helpers/Template_Defaults.php`,
// to: `helpers/${data.blockClassModel}_Defaults.php`,
// }], {
// pathDist: distPath,
// generatorsPath: path.join(__dirname),
// });
if (isElementor) {
await createFiles(data, [{
from: `templates/helpers/Template_Elementor_Widget.php`,
to: `helpers/${data.blockClassModel}_Elementor_Widget.php`,
@@ -100,25 +82,7 @@ export async function buildWordPress(blockName, args = {}) {
pathDist: distPath,
generatorsPath: path.join(__dirname)
});
}
if (isBlock) {
await createFiles(data, [{
from: `templates/Template_Component.php`,
to: `${data.blockClassModel}_Component.php`,
}], {
pathDist: distPath,
generatorsPath: path.join(__dirname)
});
// await createFiles(data, [{
// from: `templates/helpers/Template_API.php`,
// to: `helpers/${data.blockClassModel}_API.php`,
// }], {
// pathDist: distPath,
// generatorsPath: path.join(__dirname)
// });
} else {
await createFiles(data, [{
from: `templates/Template_Basic_Component.php`,
to: `${data.blockClassModel}_Component.php`,
@@ -127,7 +91,6 @@ export async function buildWordPress(blockName, args = {}) {
generatorsPath: path.join(__dirname)
});
}
}
export function execCommand(cmd = '') {
return new Promise((resolve, reject) => {
-43
View File
@@ -1,43 +0,0 @@
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
@@ -1,30 +0,0 @@
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
@@ -1,47 +0,0 @@
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
@@ -1,7 +0,0 @@
export default async function (req, res, next) {
if (process.env.VIEW_MODE) {
throw new Error("Can't publish in view mode.");
}
}
-23
View File
@@ -1,23 +0,0 @@
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
@@ -1,11 +0,0 @@
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!'});
}
+591 -24
View File
@@ -1,35 +1,602 @@
#!/usr/bin/env node
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";
import {PRODUCTION_REGISTRY_URL} from "./env.js";
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,
getBlockData,
getBlockName,
getConfigs,
getImagesList,
isFileEmpty,
readJSONFile,
removeCommentsFromCss,
removeCommentsFromJs,
replaceNames,
syncFilesWithCloud,
uploadFile,
verifyVersion,
zipProject
} from "./helpers.js";
import PluginError from 'plugin-error';
import {Server} from "socket.io";
import {createServer} from 'http';
// import {authtoken, connect} from "ngrok";
import bodyParser from "body-parser";
const {expressApp, httpServer} = init();
/**
* Constants
*/
const viewSync = new ViewSync(httpServer);
setupWatcher(viewSync);
const {isDev, modulesPath, projectPath, developmentBlockName} = getConfigs();
const blocksRegistry = isDev ? 'http://localhost:3020' : PRODUCTION_REGISTRY_URL;
const DevToolToken = 'D9lgz0TvzXCnp0xnwVBL109DaAR6Puk6F7YewDhgmP8='; // Temporary token for development purposes.
const PORT = process.env.PORT || 3010;
const dataFiles = await getDataFiles(projectPath);
const DEFAULT_VIEW_LAYOUT = 'alignfull';
httpServer.listen(PORT, '0.0.0.0', () => {
console.log(`Server app listening on port ${PORT}`)
/**
* State
*/
let port = 3000; // This variable is used in `*.hbs` and it will be updated once BrowserSync is ready.
let previewFrameUrl = `/`; // This variable is used in `*.hbs` and it will be updated once BrowserSync is ready.
let shareUrl = '';
const sessions = [];
let ignoreVersionSync = false;
let bs;
/**
* Init server
*/
const app = express();
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}))
// parse application/json
app.use(bodyParser.json())
const httpServer = createServer(app);
initSessionsServer(httpServer);
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;
}
}
});
// Middleware
// expressApp.use(setHeaders);
app.engine('.hbs', hbs.engine);
app.set('view engine', '.hbs');
app.set('views', path.join(modulesPath, 'layouts'));
//
// 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, next) => {
const data = getBlockConfigs({modulesPath, dataFiles});
if (data.error && data.errorMessage) {
return res.send(data.errorMessage);
}
const baseView = config.has('baseView') ? config.get('baseView') : DEFAULT_VIEW_LAYOUT;
const baseViewUrl = `view/${baseView}`;
data.helpers = {
include_partial: (filesPath) => handlebarLayoutsPath(modulesPath, filesPath),
}
try {
const verifiedVersion = await verifyVersion(projectPath, blocksRegistry);
if (!verifiedVersion && !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.baseView = baseView;
data.port = `/${baseViewUrl}`;
data.previewFrameUrl = `${previewFrameUrl}/${baseViewUrl}`;
data.shareUrl = shareUrl;
// TODO: Need to review this logic, conflicts with the browsersync work after "/sync" action.
// if (req.headers.referer) {
// // NGROK, public URL
// data.shareUrl = undefined; // Link already shared.
// data.previewFrameUrl = `/${baseViewUrl}`;
// data.publicUrl = true;
// }
res.render('index', data);
});
app.get('/view/:baseView', (req, res) => {
const data = getDataOfFrame(!!req.query.iframe);
if (data.error && data.errorMessage) {
return res.send(data.errorMessage);
}
const baseView = req.params.baseView ?? DEFAULT_VIEW_LAYOUT;
res.render(baseView, data)
});
app.get('/publish', async (req, res) => {
const data = await readJSONFile(path.join(projectPath, `block.json`));
// Trigger build on the registry server only if the type of the unit is `foundation` or `component`.
const uploadStaticFiles = ['foundation', 'component'].includes(data.type);
// Prepare list of static files for the registry server.
if (uploadStaticFiles) {
data.static_files = {
css: getBlockName(data.name).name + '.min.css',
js: getBlockName(data.name).name + '.min.js',
images: await getImagesList(path.join(projectPath, 'src', 'images')),
}
if (await isFileEmpty(path.join(projectPath, `src/scripts`, data.static_files.js), true)) {
delete data.static_files.js;
}
if (!data.static_files.images.length) {
delete data.static_files.images;
}
}
let responseData = {
uploadBundleUrl: undefined,
staticFilesUrls: {}
};
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;
}
// Start files uploading process.
try {
if (responseData.uploadBundleUrl) {
await zipProject(path.join(projectPath, 'src'), path.join(projectPath, 'dist.zip'));
await uploadFile(path.join(projectPath, 'dist.zip'), responseData.uploadBundleUrl); // Bundle
}
if (uploadStaticFiles) {
if (responseData.staticFilesUrls.css) {
await uploadFile(
path.join(projectPath, 'src/styles', data.static_files.css),
responseData.staticFilesUrls.css.uploadUrl,
(content) => {
if (responseData.staticFilesUrls.images) {
content = replaceNames(content, data.static_files.images, responseData.staticFilesUrls.images);
}
removeCommentsFromCss(content)
return content;
}); // CSS
}
if (responseData.staticFilesUrls.js) {
await uploadFile(
path.join(projectPath, 'src/scripts', data.static_files.js),
responseData.staticFilesUrls.js.uploadUrl,
(data) => removeCommentsFromJs(data)
); // JS
}
if (responseData.staticFilesUrls.images) {
for (let i = 0; i < data.static_files.images.length; i++) {
await uploadFile(
path.join(projectPath, 'src/images', data.static_files.images[i]),
responseData.staticFilesUrls.images[i].uploadUrl,
); // Images
}
}
}
} catch (err) {
// TODO: Need to update the registry server.
await fs.unlink(path.join(projectPath, 'dist.zip')); // Remove local bundle
res.json({success: false, message: err.message});
return;
}
await fs.unlink(path.join(projectPath, 'dist.zip')); // Remove local bundle
// Trigger project's global files build on the registry server.
if (uploadStaticFiles) {
try {
await triggerGlobalProjectFilesBuild(getBlockName(data.name).project);
} catch (err) {
res.json({success: false, message: 'Something wrong with Project Builder.'});
return;
}
}
try {
await syncFilesWithCloud(path.join(projectPath, 'block.json'), bs);
} catch (err) {
res.json({success: false, message: err});
return;
}
res.json({success: true});
});
app.get('/data', async (req, res) => {
let jsonDataFileName = req.query.name ? req.query.name : 'default';
const dataFiles = await getDataFiles(projectPath);
const data = await getBlockData(jsonDataFileName, {projectPath});
let designPreviewFiles = [];
try {
designPreviewFiles = getListOfDesignPreviewFiles(jsonDataFileName, await fs.readdir(path.join(projectPath, 'design', 'preview')));
} catch (err) {
console.log('Preview Design doesn\'t exist');
}
return res.json({
dataOptions: dataFiles,
designPreview: designPreviewFiles,
data,
});
});
app.post('/sync', async (req, res) => {
if (req.body['ignore']) {
ignoreVersionSync = true;
res.json({status: 200, message: 'Version upgrade is ignored.'});
return;
}
try {
await syncFilesWithCloud(path.join(projectPath, `block.json`), bs, true);
} catch (err) {
res.status(500).json({status: 500, message: err});
}
res.json({status: 200, message: 'Successfully synced!'});
});
// 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')));
// Custom Middleware
app.use(setHeaders);
// Setup Gulp
await buildAssetFiles();
// BrowserSync
shareUrl = await getShareableUrl();
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 getDataOfFrame(isIframe = false) {
const data = {config: getBlockConfigs({modulesPath, dataFiles})};
if (data.error && data.errorMessage) {
return data;
}
data.helpers = {
include_partial: (filesPath) => handlebarLayoutsPath(modulesPath, filesPath),
}
if (isIframe) {
data.iframeMode = true;
}
return data;
}
function startBrowserSync() {
return new Promise((resolve, reject) => {
const listener = httpServer.listen(0, async () => {
const PORT = listener.address().port;
console.log(`The web server has started on port ${PORT}`);
// BS is global variable.
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(path.join(projectPath, "src/**/*.hbs"), function () {
return syncTemplate(sessions);
});
const args = {
proxy: `http://localhost:${PORT}`,
open: false
};
if (shareUrl) {
args.socket = {
domain: shareUrl
};
}
bs.init(args, (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(rename({extname: '.min.js'}))
.pipe(uglify())
.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 () {
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.
}
function initSessionsServer(httpServer) {
const io = new Server(httpServer);
io.on('connection', async (socket) => {
sessions.push(socket);
await syncTemplate(sessions);
});
return httpServer;
}
function setHeaders(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
next();
}
async function syncTemplate(sessions = []) {
const blockName = config.has('blockName') ? config.get('blockName') : developmentBlockName;
const hbsTemplate = await fs.readFile(handlebarLayoutsPath(projectPath, 'src', `${blockName}.template.hbs`), 'utf8');
sessions.forEach(socket => {
socket.emit('templateUpdate', {template: hbsTemplate});
});
}
async function getShareableUrl() {
let domain = PRODUCTION_REGISTRY_URL;
let data = {}
try {
const response = await fetch(`${domain}/dev-tool/?devToolToken=${DevToolToken}`);
data = await response.json();
} catch (e) {
console.log('Error:', `Can't load data from DevTool endpoint: ${domain}`);
}
if (data.statusCode !== 200) {
console.log('Reply from DevTool endpoint:', data.message);
return null;
}
let url;
try {
// await authtoken(data.ngrokApiToken);
// url = await connect(port);
} catch (e) {
console.log('Error:', `Can't connect to ngrok, probably wrong token.`);
}
return url;
}
async function triggerGlobalProjectFilesBuild(project) {
const response = await fetch(`${blocksRegistry}/project-files`, {
method: 'POST',
body: JSON.stringify({project}),
headers: {'Content-Type': 'application/json'}
});
return response.json();
}
async function getDataFiles(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.');
}
return dataFiles;
}