json-object-editor
Version:
JOE the Json Object Editor | Platform Edition
599 lines (526 loc) • 22.1 kB
JavaScript
console.time('Sites on '+JOE.webconfig.sitesPort);
var compress = require('compression');
var modulename = JOE.Utils.color('[sites]','module');
var express = require('express');
var server;
if(JOE.webconfig.sitesPort != JOE.webconfig.port){
server = express();
}else{
server = JOE.Server;
}
var http = require('http');
var https = require('https'),
pem = require('pem');
var fs = require('fs');
var SitesServer = {
};
JOE.filesDir = filesDir = JOE.appDir+'/'+JOE.webconfig.filesDir+'/';
JOE.Utils.setupFileFolder(filesDir,'files');
// function setupFileFolder(){
// if (!fs.existsSync(filesDir)){
// fs.mkdirSync(filesDir);
// }
// console.log('files stored in: '+filesDir);
// }
// setupFileFolder();
function getInclude(req,res,next){
var payload = {
WEBCONFIG:JOE.webconfig,
SETTING:JOE.Cache.settings
};
var id = req.params.id;
var file = JOE.Data.include.where({_id:id})[0]||{content:'file not found'};
var content = file.content;
if(file.fillTemplate){
content = fillTemplate(content,payload);
}
switch(file.filetype) {
case 'js':
res.set('Content-Type', 'application/javascript');
break;
case 'css':
res.set('Content-Type', 'text/css');
break;
}
res.send(content);
}
server.use(compress());
server.use('/'+JOE.webconfig.filesDir+'/',express.static(JOE.filesDir));
server.use(JOE.webconfig.joepath,express.static(JOE.joedir));
server.get(['/_include/:id'],getInclude);
function findDynamicPage(originalURL,site,req){//returns a page or false
var sitepages = JOE.Data.page.where({site:site._id,dynamic:true});
console.log(modulename+' '+site.name,sitepages.length);
var dynamic_pieces = originalURL.replace(site.url,'').split('/').condense();
var piecelength = dynamic_pieces.length;
var testpath,testpagepieces,mixins,testpage,tpp,match;
console.log(modulename+' dynamics',dynamic_pieces);
for(var p = 0; p < sitepages.length; p++){
match = true;
testpage = sitepages[p];
testpagepieces = testpage['path'].split('/').condense();
if(piecelength == testpagepieces.length){
logit(testpagepieces);
testpath = [];
mixins = {};
for(var i = 0;i < piecelength; i++){
var tpp = testpagepieces[i];
if(dynamic_pieces[i] != tpp){
if(tpp.indexOf(':') == -1){
match = false;
break;
}
mixins[tpp.replace(':','')] = dynamic_pieces[i];
}
}
/*
testpage['path'].split('/').condense().map(function(piece,i){
if(piece.indexOf(':') == -1){
testpath.push(piece);
}else{
mixins[piece.replace(':','')] = dynamic_pieces[i];
}
})
//console.log(testpath);
testpath = testpath.join('/');
if(originalURL.indexOf(testpath) != -1){
return ({page:testpage,mixins:mixins})
}*/
if(match){
logit('dynamic page found: '+testpage.name);
return ({page:testpage,mixins:mixins});
}
}
}
//go through all dynamic pages, findt he one that matches the pattern minus any :
/*
for(var neg = piecelength-1;neg > 0; neg--){
testpath = dynamic_pieces.slice(0,neg).join('/');
console.log('testpath',testpath);
foundpage = sitepages.where({path:testpath});
if(foundpage.length){
if(foundpage.length == 0){
return {page:foundpage[0],mixins:dynamic_pieces.slice(neg)}
}
}
//get mixins
}
*/
//go backwards through pieces until we've found the page'
return false;
}
var Layouts = {
}
var Editor = {
wrap:function(content,tag,data){
if(Editor[tag]){
tag = Editor[tag];
}
var data = data ||{};
var h = '<'+tag+' ';
for(var d in data){
h+= 'data-'+d+'="'+data[d]+'"';
}
h+='>'+content+'</'+tag+'>';
//logit(tag, h);
return h;
},
block_tag:'jpe-block',
layout_tag:'jpe-layout',
section_tag:'jpe-section',
gui:function(obj,specs){
var specs = $c.merge({},specs||{});
var bt ='';
if(obj.use_template){
bt = JOE.Cache.findByID('block',obj.use_template);
}
let wrapper = '<jpe-info data-id="'+obj._id+'" '
+(specs.src && ` data-src="${specs.src}"`||'')
+(specs.section && ` data-section="${specs.section}"`||'')
+(bt && " data-block_template='"+bt._id+"' data-block_template_name='"+bt.name+"' ")
+' data-schema="'+obj.itemtype+'" data-name="'+obj.name+'"></jpe-info>';
return wrapper;
},
styles:'<link rel="stylesheet" href="/JsonObjectEditor/css/page-editor-styles.css">',
scripts:'<script src="/JsonObjectEditor/js/page-editor-scripts.js"></script>',
menu:'<joe-editor-menu onclick="window.jpe.toggleEditorMode();"></joe-editor-menu>'
}
var Sections = {
get:function(template_str,editor){
var sects=[];
var sect_str;
((template_str||'').match(/\$\{this.SECTION.[a-zA-Z0-9\_\-]*}/g)||[]).map(function(mt){
sect_str = mt.replace('${this.SECTION.','').replace('}','');
sects.push(sect_str);
});
return sects;
}
}
var Blocks = {
render:function(block,specs,includes,dataOverloads={}){
var specs = $c.merge({},specs||{});
var editor = specs.editor || false;
var block_template;
var inc = includes || [];
//logit('editor-mode: '+specs.editor);
//var wrapper = !!specs.wrapper || tr
if($c.isCuid(block)){
block = JOE.Cache.findByID('block',block);
block_template = block.template;
dataOverloads.block = block;
}
if(block.template_type == "module"){
block_template = JOE.Utils.requireFromString(block_template,block._id)(dataOverloads);
}
if(block.use_template){
var templateblock = JOE.Cache.findByID('block',block.use_template);
block_template = templateblock.template;
if(templateblock.template_type == "module"){
// dataOverloads.block_template = block_template;
block_template = JOE.Utils.requireFromString(block_template,templateblock._id)(dataOverloads);
}
inc = inc.concat(templateblock.includes || []);
}
inc = inc.concat(block.includes || []);
//TODO: pass info in here to add to block on the fly
block_template = block_template
//replace foreach with non
.replace(/\$\{foreach/g,'_!{foreach')
.replace(/\$\{end\sforeach\}/g,'_!{end foreach}')
.replace(/\$\{item/g,'_!{item')
//replace blocks with non
.replace(/\$\{this\.BLOCK\./g,'__{this.BLOCK.')
.replace(/\$\{this\.BLOCK_TEMPLATE\./g,'__{this.BLOCK_TEMPLATE.')
//replace all others
.replace(/\$\{this\./g,'_!{this.')
//.replace(/\_\!\{this\.DYNAMIC/g,'${this.DYNAMIC')
//switch blocks back
.replace(/\_\_\{this\.BLOCK\./g,'${this.BLOCK.')
.replace(/\_\_\{this\.BLOCK\_TEMPLATE\./g,'${this.BLOCK_TEMPLATE.')
//filltemplate
block_template = block_template = fillTemplate(block_template,
Object.assign({BLOCK:block,BLOCK_TEMPLATE:templateblock},dataOverloads)
)
//switch others back
.replace(/\_\!\{this\./g,'${this.')
.replace(/\_\!\{/g,'${')
//logit(block);
var blockstr = ''
+((editor && '<'+Editor.block_tag+' data-cuid="'+block._id+'">')||'')
+block_template
+((editor && Editor.gui(block,{src:specs.src,section:specs.section})+'</'+Editor.block_tag+'>')||'');
//logit(block.name,blockstr,specs);
//return blockstr;
return {content:blockstr,includes:inc};
},
page:function(page,layout,sections,editor,includes,dataOverloads){
if($c.isCuid(page)){
page = JOE.Cache.findByID('page',page);
}
if($c.isCuid(layout)){
layout = JOE.Cache.findByID('layout',layout);
}
var sect_array = Sections.get(layout.template,editor);
sect_array = sect_array.concat(Sections.get(page.content,editor));
sect_array.condense(true);
var sections = sections || {};
sect_array.map(function(b){
sections[b] = sections[b] || '';
});
//console.log('blocks',$c.isArray(page.blocks));
//logit('sections:',sect_array,layout.template);
//CHECK IF BLOCK IN PAGE + PAGE SET TO SYNC, OTHERWISE ADD TO BLOCKS
function getSectionContentFromBlock(block){
var payload = Blocks.render(block.block,{editor:editor,src:block.src,section:block.section},includes,dataOverloads);
sections[block.section] = sections[block.section] || '';
sections[block.section] += payload.content;
return {includes:payload.includes || []}
}
(page.blocks || []).map(function(block){
//TODO:Remove uneccesary Setness here
var p = getSectionContentFromBlock(block);
includes = includes.concat(p.includes);
includes = Array.from(new Set(includes));
});
if(page.syncLayoutBlocks){
(layout.blocks || []).map(function(block){
if(!page.blocks.where({block:block.block}).length){
var s = getSectionContentFromBlock(block);
includes = includes.concat(s.includes);
}
});
}
if(editor){
var sec;
for(var s in sections){
sec = sections[s];
//logit(s);
sec = Editor.wrap(sec,'section_tag',{section:s});
sections[s] = sec;
}
}
return {sections:sections,includes:includes};
},
layout:function(layout,sections,editor){
if($c.isCuid(layout)){
layout = JOE.Cache.findByID('layout',layout);
}
var sect_array = Sections.get(layout.template);
var sections = sections || {};
sect_array.map(function(b){
sections[b] = sections[b] || '';
});
//logit('sections:',sect_array,layout.template);
(layout.blocks || []).map(function(block){
sections[block.section] = sections[block.section] || '';
sections[block.section] += Blocks.render(block.block,{editor:editor,src:block.src});
})
return sections;
}
}
SitesServer.parseRoute = function(req,res,next){
const origin = req?.headers?.referer && new URL(req.headers.referer).origin;
var routeStartTimer = new Date().getTime();
var mixins = {};
var DYNAMIC={};
var section_content;
var editor = false;
var includes = [];
var payload = this.payload = {
siteRoute:req.params.siteRoute,
originalURL:req._parsedUrl.pathname,//req.originalUrl
regURL:req.url
};
if(req.query.editor){
//console.log('editor mode');
editor = true;
}
//logit('sites running',payload);
var sites = JOE.Data.site || [],siteurl,site,route,stest;
for(var s = 0, tot= sites.length; s<tot;s++){
stest = sites[s].url[0] == '/'?sites[s].url:'/'+sites[s].url;
//logit('site url test: '+stest);
//if the url matches the original URL and is not just a forward slash
if(payload.originalURL.indexOf(stest) == 0 && stest != '/'){
siteurl = stest;
site = payload.site = sites[s];
route = payload.siteRoute = payload.originalURL.replace(siteurl,'');
}
}
var default_site;
if(!payload.site){
default_site = JOE.Data.site.where({url:''})[0]||null;
if(default_site){
site = payload.site = default_site;
route = payload.siteRoute = payload.originalURL.replace(siteurl,'');
}
}
logit('\n\r'+modulename+new Date()+' siteurl: '+siteurl+' site: '+(site && site.name)||'','route: '+route);
if(!payload.site){
payload.site = 'no site found';
}else{
includes = includes.concat(payload.site.includes || []);
//includes = payload.site.includes||[];
//get datasets
var ds_source = (payload.site.datasets || []);
var datasets = {};
ds_source.map(function(ds){
datasets[ds] = JOE.Data[ds];
});
var route = payload.siteRoute || '';
var routeWithSlash = route.startsWith('/') ? route : '/' + route;
var routeWithoutSlash = route.startsWith('/') ? route.slice(1) : route;
var page = JOE.Data.page.where({$and:[{site:site._id},{path:{$in:[routeWithSlash,routeWithoutSlash]}}]})[0]
|| findDynamicPage(payload.originalURL,site,req);
if(!page){
logit('page not found: '+route);
page =
(payload.site.homepage && JOE.Data.page.where({_id:payload.site.homepage})[0])
|| false;
}
if(page.mixins){
mixins = page.mixins;
page = page.page;
console.log(modulename+' mixins',mixins);
}
// //look for dynamic content
// if(payload.originalURL.indexOf(':') != -1){
// dynamic = true;
// var dynamic_pieces = payload.originalURL.split('/');
// console.log(dynamic_pieces);
// }
if(!page){
res.send(payload);
return;
}
page = $c.merge({},page);
includes = includes.concat(page.includes || []);
payload.page = page;
/*DYNAMIC PAGE HANDLER*/
var hash="";
if(page.dynamic && page.content_items && page.content_items.length){
//find mixins
logit('content items: '+page.content_items);
page.content_items.map(function(ci){
if(mixins[ci.reference]){
DYNAMIC[ci.reference] = $J.get(mixins[ci.reference]);
}
if(DYNAMIC[ci.reference] && DYNAMIC[ci.reference].itemtype != ci.itemtype){
delete DYNAMIC[ci.reference];
console.log(modulename+' dynamic itemtype mismatch',ci.itemtype,DYNAMIC[ci.reference].itemtype);
}
})
//use ":" for particular parameter
//or throw 404
}
var layout = JOE.Cache.findByID(page.layout)||false;//JOE.Data.layout.where({_id:page.layout})[0]||false;
//layout sections
//section_content = Blocks.layout(layout,section_content,editor);
var block_data = Blocks.page(page,layout,section_content,editor,includes,
{DYNAMIC});
section_content = block_data.sections;
includes = includes.concat(block_data.includes);
//logit('section_content',section_content);
/*PLUGIN HANDLER*/
var pluginstr = '';
if(page.content_type && page.content_type =='plugin' && page.plugin){
var plugin = page.plugin;
var data = {};
page.plugin_params.map(function(param){
data[param.param] = param.value;
})
data = $c.merge(data,req.query);
var method = page.plugin_method || 'default';
console.log(modulename+' running '+JOE.Utils.color('['+plugin+']','plugin')+' > '+method);
var errors = [];
var pluginClass = JOE.Apps.plugins[plugin];
if(!pluginClass){
errors.push('plugin "'+plugin+'" not initialized')
}
else if(!pluginClass[method]){
errors.push('method "'+method+'" not found')
}
if(errors.length){
if(!layout){
res.jsonp({errors:errors,failedat:'Server'});
return;
}else{
pluginstr = {errors:errors,failedat:'Server'};
}
}
try{
var response = pluginClass[method](data,req);
if(response && response.errors){
res.jsonp(response.errors);
return;
}
if(!layout){
if(typeof response == "string"){
res.send(response);
}else{
res.jsonp(response);
}
}else{
pluginstr = response;
}
}catch(e){
res.jsonp({errors:e});
}
if(!layout){
return;
}
}
/*MODULE HANDLER*/
if(layout){
logit(`has layout ${new Date().getTime() - routeStartTimer}`);
//layout includes
includes = includes.concat(layout.includes || []);
logit('includes: '+includes.length);
includes = Array.from(new Set(includes));
var INC = '',incobj;
includes.map(function(i){
incobj = JOE.Data.include.where({_id:i})[0]||{filetype:'notfound'};
var url='/_include/'+incobj._id;
switch(incobj.filetype){
case 'css':
INC += '<link rel="Stylesheet" type="text/css" href="'+url+'"/>';
break;
case 'js':
INC += '<script src="'+url+'" type="application/javascript"></script>';
break;
}
});
var content = {
DYNAMIC:DYNAMIC,
INCLUDES:INC,
PAGE:page,
LAYOUT:layout,
SITE:site,
JOEPATH:JOE.webconfig.joepath,
DATA:datasets,
PLUGIN:{str:pluginstr},
WEBCONFIG:JOE.webconfig,
SECTION:section_content||{},
REQUEST:req,
ORIGIN:origin
};
if (pluginstr){
content.PAGE.content = pluginstr;
}
if(page.content_type && page.content_type =='module'){
console.log(`modules > ${new Date().getTime() - routeStartTimer}`);
//run function with data on module
page.content = JOE.Utils.requireFromString(page.content,page._id)(content);
}
if(editor){
//TODO: do this for other page vars to?
page.content = Editor.wrap(page.content,'jpe-content',{name:'page content'})
}
var template = layout.template.replace('${this.PAGE.content}',content.PAGE.content);
var html = fillTemplate(template,content);
if(editor){
html = html.replace('</head>',Editor.styles+'</head>')
.replace('</body>',Editor.gui(layout)+Editor.gui(page)+Editor.menu
+'<script>var jsc_hostname = "'+JOE.webconfig.hostname+'"; '
+'var jsc_port = '+JOE.webconfig.port+';'
+'var jsc_svgs={};'
+'jsc_svgs.page = \''+JOE.Schemas.schema.page.menuicon+'\';'
+'jsc_svgs.block = \''+JOE.Schemas.schema.block.menuicon+'\';'
+'jsc_svgs.layout = \''+JOE.Schemas.schema.layout.menuicon+'\';'
+'</script>'
+Editor.scripts+'</body>');
}
res.send(html);
logit(`sent html ${new Date().getTime() - routeStartTimer}`);
return;
}
}
res.send(payload);
var routeEndTimer = new Date().getTime() - routeStartTimer;
logit(`route served in:${routeEndTimer}ms`);
}
server.get(['/*','/*/:siteRoute'],SitesServer.parseRoute);
if(JOE.webconfig.sitesPort != JOE.webconfig.port){
http.Server(server);
server.listen(JOE.webconfig.sitesPort,function(){
//console.log('sites listening on '+JOE.webconfig.sitesPort);
console.log(modulename+' on '+JOE.webconfig.sitesPort);
});
var httpsPort = JOE.webconfig.httpsSitesPort;
if(httpsPort){
https.Server({key: JOE.Pem.serviceKey, cert: JOE.Pem.certificate}, server).listen(httpsPort);
console.log(modulename+' https on '+httpsPort);
// pem.createCertificate({selfSigned:true}, function(err, keys){
// if(err){
// console.log(modulename+' cert error',err);
// }
// https.Server({key: keys.serviceKey, cert: keys.certificate}, server).listen(httpsPort);
// console.log(modulename+' https on '+httpsPort);
// });
}
}
//else{
//}
SitesServer.findDynamicPage = findDynamicPage;
SitesServer.server = server;
module.exports = SitesServer;