Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4192d16b7a | |||
| 12abd35cfa | |||
| 073ed52932 | |||
| 8f2605be9d | |||
| 9e7860542b | |||
| f48b2a3274 | |||
| b76f83d4fa | |||
| 977ec0003c | |||
| 0e91c42e89 | |||
| 0a4fd3608f | |||
| 11152dda87 | |||
| 246c9ddd35 | |||
| 164ef7433a | |||
| a6decb9cfb | |||
| 9c85a89a8f | |||
| 58ed3de9dc | |||
| 3de4a4abb6 | |||
| 256450226b | |||
| 51db3da072 | |||
| 05c5697bc2 | |||
| 01e4160040 | |||
| 81f5166043 | |||
| a1929fe33c | |||
| 840c002834 | |||
| f4474fb7e4 | |||
| 8ca6cb0365 | |||
| e2553358be | |||
| 34dbff5701 | |||
| c408f2f5b3 |
+19
@@ -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
|
||||
+240
-10
@@ -9,6 +9,12 @@ 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);
|
||||
@@ -26,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 {
|
||||
@@ -44,33 +51,46 @@ function getErrorHtml(message = '', errorMessage = '') {
|
||||
}
|
||||
|
||||
export async function getBlockData(jsonFileName = 'default', {projectPath} = {jsonFileName: 'default'}) {
|
||||
let data = await readJSONFile(path.join(projectPath, 'data', `${jsonFileName}.json`));
|
||||
const filePath = path.join(projectPath, 'data', `${jsonFileName}.json`);
|
||||
const data = await readJSONFile(filePath);
|
||||
|
||||
if (data.error) {
|
||||
return data;
|
||||
console.log(filePath, data.errorMessage.replace(/<[^>]*>?/gm, ''));
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getBlockConfigs(args = {modulesPath: '', dataFiles: []}) {
|
||||
return Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object.
|
||||
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: args.modulesPath, dataFiles: args.dataFiles.map((name) => {
|
||||
projectDir: '', dataFiles: args.dataFiles.map((name) => {
|
||||
return {
|
||||
name,
|
||||
};
|
||||
}), remToPx: config.has('remToPx') ? config.get('remToPx') : 16,
|
||||
}), 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 = '') {
|
||||
if (name.startsWith('@')) {
|
||||
name = name.substring(1);
|
||||
}
|
||||
// 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],
|
||||
};
|
||||
@@ -98,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);
|
||||
@@ -160,3 +180,213 @@ export async function buildExportFiles(blockName, platform) {
|
||||
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}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export function setHeaders(req, res, next) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
next();
|
||||
}
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
{{> (include_partial "layouts/partials/head") }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
{{> (include_partial "layouts/partials/head") }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
{{> (include_partial "layouts/partials/head") }}
|
||||
|
||||
+4
-2
@@ -1,3 +1,4 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
@@ -7,12 +8,13 @@
|
||||
<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,
|
||||
@@ -21,7 +23,7 @@
|
||||
</script>
|
||||
|
||||
<div class="preview">
|
||||
<iframe id="preview_frame" src="{{ previewFrameUrl }}?iframe=true" 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>
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
<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" id="block-stylesheet" href="{{staticFilesPath}}/styles/{{ config.blockName }}.min.css?v=1">{{/if}}
|
||||
</head>
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
<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.4.5/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}}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<div class="page_toolbar__left"></div>
|
||||
|
||||
<div class="page_toolbar__middle">
|
||||
<div>
|
||||
Version: <b>1rem = {{ config.version }}px</b>
|
||||
<div style="display: none">
|
||||
Version: {{ config.version }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
{{#if styleGuideUrl}}
|
||||
<a href="{{ styleGuideUrl }}" target="_blank" class="palette" title="Open Style Guide"></a>
|
||||
{{/if}}
|
||||
<a href="{{ previewFrameUrl }}" target="_blank" class="open_in_new_tab" title="Open in New Window"></a>
|
||||
<a href="{{ previewFrameUrl }}?block={{blockName}}" target="_blank" class="open_in_new_tab" title="Open in New Window"></a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
+1
-167
@@ -1,167 +1 @@
|
||||
window.initBlock = initBlock;
|
||||
|
||||
let template;
|
||||
let data = {};
|
||||
let reload;
|
||||
|
||||
// Blocks Initialization.
|
||||
function initBlock(blockName = '', selector = '', cb) {
|
||||
reload = function () {
|
||||
document.querySelectorAll(selector).forEach((el) => cb(el));
|
||||
};
|
||||
|
||||
reload();
|
||||
}
|
||||
|
||||
// Scrollbars / Frame resizes notifications.
|
||||
(function () {
|
||||
let height;
|
||||
|
||||
handleHeightChange(); // Initial frame's height setup.
|
||||
setupResizeListener(); // Listen to frame's height changes.
|
||||
|
||||
///
|
||||
|
||||
function setupResizeListener() {
|
||||
const resizeObserver = new ResizeObserver(handleHeightChange);
|
||||
resizeObserver.observe(document.body);
|
||||
}
|
||||
|
||||
function handleHeightChange() {
|
||||
const updatedHeight = getCurrentHeight();
|
||||
|
||||
if (height === updatedHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RESIZE_CODE = 'resize:';
|
||||
height = updatedHeight;
|
||||
window.parent.postMessage(RESIZE_CODE + JSON.stringify({height}), '*');
|
||||
}
|
||||
|
||||
function getCurrentHeight() {
|
||||
return document.querySelector('#hbs-container').scrollHeight;
|
||||
}
|
||||
})();
|
||||
|
||||
// Data Updates Listeners.
|
||||
(function () {
|
||||
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;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function loadDataOptions() {
|
||||
const queryParameters = new URLSearchParams({name: getQueryParams().data || 'default'});
|
||||
fetch(`/data?${queryParameters}`)
|
||||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
data = response.data; // Update state.
|
||||
updateBlock({data});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// Listen to Template updates.
|
||||
(function () {
|
||||
initSocket();
|
||||
|
||||
function initSocket() {
|
||||
const socket = window.io.connect();
|
||||
|
||||
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});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
function updateBlock(args = {}) {
|
||||
if (args.template) {
|
||||
template = args.template; // Update state.
|
||||
}
|
||||
|
||||
if (args.data) {
|
||||
data = args.data; // Update state.
|
||||
}
|
||||
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderBlock(template, data || {}, document.getElementById("hbs-container"));
|
||||
}
|
||||
|
||||
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 () {
|
||||
return '/';
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=frame-index.min.js.map
|
||||
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")}))}()}();
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+15
-9917
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+21
File diff suppressed because one or more lines are too long
@@ -1,21 +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))
|
||||
let height = Number.parseInt(data.height)
|
||||
if (height > 20000) {
|
||||
height = 20000; // Limit max height.
|
||||
}
|
||||
previewFrame.style.height = height + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
export function getPreviewFrame() {
|
||||
return document.getElementById('preview_frame');
|
||||
}
|
||||
@@ -15,48 +15,10 @@ function initBlock(blockName = '', selector = '', cb) {
|
||||
reload();
|
||||
}
|
||||
|
||||
// Scrollbars / Frame resizes notifications.
|
||||
(function () {
|
||||
let height;
|
||||
const debug = false;
|
||||
|
||||
handleHeightChange(); // Initial frame's height setup.
|
||||
setupResizeListener(); // Listen to frame's height changes.
|
||||
|
||||
///
|
||||
|
||||
function setupResizeListener() {
|
||||
const resizeObserver = new ResizeObserver(handleHeightChange);
|
||||
resizeObserver.observe(document.body);
|
||||
}
|
||||
|
||||
function handleHeightChange() {
|
||||
const updatedHeight = getCurrentHeight();
|
||||
|
||||
if (debug) {
|
||||
console.log('Height Updates', 'Old vs New: ' + height, updatedHeight);
|
||||
}
|
||||
|
||||
if (height === updatedHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RESIZE_CODE = 'resize:';
|
||||
height = updatedHeight;
|
||||
window.parent.postMessage(RESIZE_CODE + JSON.stringify({height}), '*');
|
||||
|
||||
if (debug) {
|
||||
console.log('Resize message sent: ', height)
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentHeight() {
|
||||
return document.querySelector('#hbs-container').scrollHeight;
|
||||
}
|
||||
})();
|
||||
|
||||
// Data Updates Listeners.
|
||||
(function () {
|
||||
const block = getQueryParams().block;
|
||||
|
||||
loadDataOptions();
|
||||
listenToDataOptionsUpdates();
|
||||
|
||||
@@ -90,7 +52,10 @@ function initBlock(blockName = '', selector = '', cb) {
|
||||
}
|
||||
|
||||
function loadDataOptions() {
|
||||
const queryParameters = new URLSearchParams({name: getQueryParams().data || 'default'});
|
||||
const queryParameters = new URLSearchParams({
|
||||
block,
|
||||
name: getQueryParams().data || 'default'
|
||||
});
|
||||
fetch(`/data?${queryParameters}`)
|
||||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
@@ -98,14 +63,12 @@ function initBlock(blockName = '', selector = '', cb) {
|
||||
updateBlock({data});
|
||||
})
|
||||
}
|
||||
})();
|
||||
|
||||
// Listen to Template updates.
|
||||
(function () {
|
||||
// Listen to Template updates.
|
||||
initSocket();
|
||||
|
||||
function initSocket() {
|
||||
const socket = window.io.connect();
|
||||
const socket = window.io.connect('', {query: `block=${block}`});
|
||||
|
||||
socket.on('error', function (err) {
|
||||
console.log(err);
|
||||
@@ -118,60 +81,104 @@ function initBlock(blockName = '', selector = '', cb) {
|
||||
socket.on('templateUpdate', function (args) {
|
||||
updateBlock({template: args.template});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
function updateBlock(args = {}) {
|
||||
if (args.template) {
|
||||
template = args.template; // Update state.
|
||||
socket.on('scriptUpdate', function (args) {
|
||||
const tag = updateTag('block-script');
|
||||
tag.onload = () => {
|
||||
updateBlock({template: args.template});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('styleUpdate', function (args) {
|
||||
updateTag('block-stylesheet');
|
||||
});
|
||||
}
|
||||
|
||||
if (args.data) {
|
||||
data = args.data; // Update state.
|
||||
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;
|
||||
}
|
||||
|
||||
if (!template) {
|
||||
return;
|
||||
function updateBlock(args = {}) {
|
||||
if (args.template) {
|
||||
template = args.template; // Update state.
|
||||
}
|
||||
|
||||
if (args.data) {
|
||||
data = args.data; // Update state.
|
||||
}
|
||||
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderBlock(template, data || {}, document.getElementById("hbs-container"));
|
||||
}
|
||||
|
||||
renderBlock(template, data || {}, document.getElementById("hbs-container"));
|
||||
}
|
||||
function renderBlock(templateHbs, jsonData, target) {
|
||||
const template = Handlebars.compile(templateHbs);
|
||||
|
||||
function renderBlock(templateHbs, jsonData, target) {
|
||||
const template = Handlebars.compile(templateHbs);
|
||||
/**
|
||||
* Handlebars Helpers
|
||||
*/
|
||||
Handlebars.registerHelper('esc_attr', function (attr) {
|
||||
return attr;
|
||||
});
|
||||
|
||||
/**
|
||||
* Handlebars Helpers
|
||||
*/
|
||||
Handlebars.registerHelper('esc_attr', function (attr) {
|
||||
return attr;
|
||||
});
|
||||
Handlebars.registerHelper('esc_url', function (attr) {
|
||||
return attr;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('esc_url', function (attr) {
|
||||
return attr;
|
||||
});
|
||||
Handlebars.registerHelper('esc_html', function (attr) {
|
||||
return attr;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('esc_html', function (attr) {
|
||||
return attr;
|
||||
});
|
||||
Handlebars.registerHelper('base_url', function () {
|
||||
const list = ['block'];
|
||||
|
||||
Handlebars.registerHelper('base_url', function () {
|
||||
return '/';
|
||||
});
|
||||
if (block) {
|
||||
list.push(block.substr(1));
|
||||
}
|
||||
|
||||
let html;
|
||||
return ['', ...list, ''].join('/');
|
||||
});
|
||||
|
||||
try {
|
||||
html = template(jsonData);
|
||||
} catch (e) {
|
||||
html = `<div style="max-width: 1280px; margin: 1rem auto;">
|
||||
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();
|
||||
target.innerHTML = html;
|
||||
if (reload) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -125,12 +125,13 @@ function DataOptions(props = {}) {
|
||||
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});
|
||||
updateState({dataText: JSON.stringify(data, null, 2), data, errorMessage: null, loading: false});
|
||||
updateIframe(data);
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -168,7 +169,7 @@ function DataOptions(props = {}) {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -43,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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
{{> (include_partial "layouts/partials/head") }}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
.swiper {
|
||||
&-slide {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
height: initial;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;;;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;;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"}
|
||||
@@ -8,6 +8,12 @@ body {
|
||||
font-size: 1rem;
|
||||
//overflow: none;
|
||||
background-color: #F7FAFC;
|
||||
|
||||
&.view-mode {
|
||||
#publish-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import "buttons";
|
||||
|
||||
@@ -2,24 +2,8 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
main .swiper-slide {
|
||||
width: initial;
|
||||
}
|
||||
main .swiper-wrapper {
|
||||
height: initial;
|
||||
}
|
||||
|
||||
.body--iframe {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.body--iframe main {
|
||||
overflow-y: auto;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.fullscreen_layout {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["page--view.scss","_page--view-swiper.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;;ACPA;EACE;;AAGF;EACE;;;ADSJ;EACE;;AAEA;EAGE;;;AAIJ;EACE;EACA;EACA;EACA","file":"page--view.css"}
|
||||
{"version":3,"sourceRoot":"","sources":["page--view.scss"],"names":[],"mappings":"AAAA;EACE;;;AAIF;EACE;;;AAGF;EACE;EACA;EACA;EACA","file":"page--view.css"}
|
||||
@@ -2,25 +2,9 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden;
|
||||
|
||||
// Fixes scrolling issues of swiperJS. Should be included in all projects.
|
||||
@import "page--view-swiper";
|
||||
}
|
||||
|
||||
// iFrame mode
|
||||
.body--iframe {
|
||||
overflow-y: hidden;
|
||||
|
||||
main {
|
||||
// If you change to "overflow: initial", the margin-top/bottom of first/last element will be not included.
|
||||
// Test on fresh block setup where heading has margin-top.
|
||||
overflow-y: auto;
|
||||
}
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.fullscreen_layout {
|
||||
@@ -29,4 +13,3 @@ main {
|
||||
background-image: url('https://i.ibb.co/pjwL8D1/shapelined-JBKdviwe-XI-unsplash.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
Generated
+116
-8123
File diff suppressed because it is too large
Load Diff
+9
-5
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "@axe-web/block-dev-tool",
|
||||
"version": "1.0.27",
|
||||
"version": "1.0.31",
|
||||
"author": {
|
||||
"name": "AXE-WEB",
|
||||
"email": "office@axe-web.com",
|
||||
"url": "https://axe-web.com/"
|
||||
},
|
||||
"scripts": {
|
||||
"info": "NODE_ENV=development BLOCK_NAME=header MODULE_PATH= node debug.js",
|
||||
"dev": "NODE_ENV=development BLOCK_NAME=header MODULE_PATH= node server.js",
|
||||
"build-platform": "NODE_ENV=development BLOCK_NAME=header MODULE_PATH= node ./build.js",
|
||||
"dev-dev-tool": "NODE_ENV=development rollup --config rollup.config.js --watch",
|
||||
"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",
|
||||
"dev-dev-tool": "rollup --config rollup.config.js --watch",
|
||||
"build-dev-tool": "rollup --config rollup.config.js"
|
||||
},
|
||||
"engines": {
|
||||
@@ -19,8 +20,10 @@
|
||||
"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",
|
||||
@@ -37,6 +40,7 @@
|
||||
"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",
|
||||
|
||||
@@ -26,6 +26,10 @@ export async function createDistFolder(blockName, projectPath = '') {
|
||||
}
|
||||
|
||||
export function getBlockFields(block = {}, type = 'content') {
|
||||
if (!block['field_groups']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fields_group = block['field_groups'].find((group) => group.name === type);
|
||||
const fields = [];
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ class Component_Builder {
|
||||
$this->register_default_handlebar_helpers();
|
||||
|
||||
$this->add_handlebar( 'base_url', function ( $context ) {
|
||||
$path = join( '/', [ 'blocks', $this->block_project, $this->block_name, 'templates' ] );
|
||||
return plugins_url( $path . '/', 'axeweb-blocks-library/axeweb-blocks-library.php' );
|
||||
$path = join( '/', [ $this->block_project, $this->block_name, 'templates' ] );
|
||||
return join( '/', [ get_site_url(),'wp-content', 'axe-web-blocks', $path, '' ] );
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ class <%= blockClassModel %>_Component extends \Core\Component {
|
||||
// $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'], self::VERSION );<% if (include_script) { %>
|
||||
wp_enqueue_script( 'block-<%= blockFilename %>', plugins_url( 'templates/scripts/<%= blockFilename %>.min.js', __FILE__ ), ['assets-script'], self::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 %>' );
|
||||
}<% } %>
|
||||
@@ -32,13 +32,13 @@ class <%= blockClassModel %>_Component extends \Core\Component {
|
||||
'style_assets' => [
|
||||
[
|
||||
'name' => '<%= blockFilename %>',
|
||||
'url' => plugins_url( 'templates/styles/<%= blockFilename %>.min.css', __FILE__ ),
|
||||
'url' => $this->get_assets_path_url( 'templates/styles/<%= blockFilename %>.min.css' ),
|
||||
]
|
||||
],
|
||||
'script_assets' => [
|
||||
[
|
||||
'name' => '<%= blockFilename %>',
|
||||
'url' => plugins_url( 'templates/scripts/<%= blockFilename %>.min.js', __FILE__ ),
|
||||
'url' => $this->get_assets_path_url( 'templates/scripts/<%= blockFilename %>.min.js' ),
|
||||
]
|
||||
]
|
||||
] );
|
||||
@@ -50,7 +50,7 @@ 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
|
||||
|
||||
@@ -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";
|
||||
@@ -68,10 +68,17 @@ export async function buildWordPress(blockName, args = {}) {
|
||||
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"),
|
||||
|
||||
@@ -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()
|
||||
]
|
||||
}];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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!'});
|
||||
}
|
||||
@@ -1,441 +1,35 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
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 {getBlockData, getBlockConfigs, getConfigs, readJSONFile, zipProject} from "./helpers.js";
|
||||
import PluginError from 'plugin-error';
|
||||
import {Server} from "socket.io";
|
||||
import {createServer} from 'http';
|
||||
import {authtoken, connect} from "ngrok";
|
||||
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 {isDev, modulesPath, projectPath, developmentBlockName} = getConfigs();
|
||||
const blocksRegistry = isDev ? 'http://localhost:3020' : PRODUCTION_REGISTRY_URL;
|
||||
const DevToolToken = 'D9lgz0TvzXCnp0xnwVBL109DaAR6Puk6F7YewDhgmP8='; // Temporary token for development purposes.
|
||||
const viewSync = new ViewSync(httpServer);
|
||||
setupWatcher(viewSync);
|
||||
|
||||
const dataFiles = prepareListOfDataFiles(await fs.readdir(path.join(projectPath, 'data')));
|
||||
const PORT = process.env.PORT || 3010;
|
||||
|
||||
/**
|
||||
* 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 = [];
|
||||
|
||||
/**
|
||||
* Init server
|
||||
*/
|
||||
|
||||
const app = express();
|
||||
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;
|
||||
}, 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('/', (req, res) => {
|
||||
const data = getBlockConfigs({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 = {
|
||||
include_partial: (filesPath) => handlebarLayoutsPath(modulesPath, filesPath),
|
||||
}
|
||||
|
||||
data.baseView = baseView;
|
||||
data.port = `/${baseViewUrl}`;
|
||||
data.previewFrameUrl = `${previewFrameUrl}/${baseViewUrl}`;
|
||||
// data.previewFrameUrl = `/${baseViewUrl}`;
|
||||
data.shareUrl = shareUrl;
|
||||
|
||||
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 = {config: getBlockConfigs({modulesPath, dataFiles})};
|
||||
if (data.error && data.errorMessage) {
|
||||
return res.send(data.errorMessage);
|
||||
}
|
||||
|
||||
data.helpers = {
|
||||
include_partial: (filesPath) => handlebarLayoutsPath(modulesPath, filesPath),
|
||||
}
|
||||
|
||||
if (!!req.query.iframe) {
|
||||
data.iframeMode = true;
|
||||
}
|
||||
|
||||
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 = {
|
||||
uploadUrl: undefined
|
||||
};
|
||||
|
||||
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'), path.join(projectPath, 'dist.zip'));
|
||||
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 getBlockData(jsonDataFileName, {projectPath});
|
||||
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')));
|
||||
|
||||
// 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 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}`);
|
||||
|
||||
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(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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user