VENOM-10: WIP
|
|
@ -1 +1 @@
|
|||
:focus{outline:0}*{box-sizing:border-box}body,html{width:100vw;height:100vh;overflow:hidden;padding:0;margin:0;background:#1f2857;background:linear-gradient(144deg,#1f2954 25%,#000 50%,#5e002c 80%);background-attachment:fixed;color:#fff;font-family:sans-serif;font-size:16px}.hide{display:none!important}v-table{display:block}v-table v-table-footer,v-table v-table-header,v-table v-table-row{display:grid;grid-auto-columns:1fr;grid-auto-flow:column;-moz-column-gap:20px;column-gap:20px}v-table v-table-header h2{text-align:center}v-table v-table-row{margin:10px 0}v-table .grid12{grid-template-columns:repeat(12,[col-start] 1fr);-moz-column-gap:5px;column-gap:5px}v-table .grid12 .col-1to4{grid-column:1/3}v-table .grid12 .col-1to6{grid-column:1/6}v-table .grid12 .col-5to6{grid-column:5/6}v-table .grid12 .col-6to11{grid-column:6/11}v-table .grid12 .col-6to9{grid-column:6/9}v-table .grid12 .col-9to10{grid-column:9/10}v-table .grid12 .col-11to13{grid-column:11/13}v-label,v-option,v-options,v-select{display:inline-block;box-sizing:border-box}v-select{display:block;position:relative;margin-bottom:.5rem}v-label{padding:.5rem 1rem;background-color:#3949ab;color:#fff;cursor:pointer;display:block;border-radius:4px}v-options{position:absolute;display:flex;border-radius:4px;flex-direction:column;overflow:hidden;max-height:0;top:.5rem;left:.5rem;background-color:#3949ab;z-index:1000;cursor:pointer;color:#fff;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}v-option{padding:.5rem 1rem;min-width:200px}v-option:not([disabled]):hover{background-color:rgba(0,0,0,.3)}v-option[selected]{background-color:#3949ab;color:#fff}v-option[disabled]{color:#aaa}main{display:flex;height:100vh;overflow:hidden}.menu{width:220px;background-color:#1b1b1b;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);flex-shrink:0;display:flex;flex-direction:column}.menu .logo{text-align:center;font-family:monospace}.menu div[data-link]{padding:.75rem .5rem;position:relative}.menu div[data-link]:after{background-color:#3949ab;content:"";position:absolute;left:0;bottom:0;height:3px;width:100%;transform:scaleX(0);transition:transform .4s;transform-origin:left}.menu div[data-link]:hover:after{transform:scaleX(1)}.menu div[data-link]:last-child{margin-top:auto}.menu div[data-link].active{font-weight:700}.content-area{padding:0 1%;margin:1rem 1.5rem;flex-grow:1;overflow-y:auto;width:100%;max-height:100%;background:rgba(27,27,27,.5)}.content-area .inline{display:inline}.content-area .inline div{display:inline}.content-area .btn-line{margin-top:35px}.content-area .btn-line div{text-align:right}.content-area .btn-line div button{display:inline;margin-left:10px;min-width:100px}.content-area textarea{background:rgba(27,27,27,.5);color:#fff;margin:15px 0 0 0;font-family:sans-serif;font-size:1.1rem;min-width:100%}.content-area .modules div{padding:6px 20px 6px 0}.content-area .icon-cont div{display:inline-block;vertical-align:middle;margin-top:5px}.content-area .icon-cont .icon{width:18px;height:18px;background-size:cover;margin:0 15px 0 0;fill:#fff}.content-area .icon-cont .icon-add{background-image:url(../images/icon/ic_add_circle_outline_24px.svg)}.content-area .icon-cont .icon-edit{background-image:url(../images/icon/ic_edit_24px.svg)}.content-area .icon-cont .icon-visibility{background-image:url(../images/icon/ic_visibility_24px.svg)}.content-area .icon-cont .icon-delete{background-image:url(../images/icon/ic_delete_24px.svg)}.content-area .icon-cont .icon-arrow-back{background-image:url(../images/icon/ic_arrow_back_24px.svg);width:24px;height:24px}.content-area .description{margin-top:25px}.content-area .fix-pad{padding-top:14px}
|
||||
main{display:flex;height:100vh;overflow:hidden}.menu{width:220px;background-color:#1b1b1b;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);flex-shrink:0;display:flex;flex-direction:column}.menu .logo{text-align:center;font-family:monospace}.menu div[data-link]{padding:.75rem .5rem;position:relative}.menu div[data-link]:after{background-color:#3949ab;content:"";position:absolute;left:0;bottom:0;height:3px;width:100%;transform:scaleX(0);transition:transform .4s;transform-origin:left}.menu div[data-link]:hover:after{transform:scaleX(1)}.menu div[data-link]:last-child{margin-top:auto}.menu div[data-link].active{font-weight:700}.content-area{padding:0 1%;margin:1rem 1.5rem;flex-grow:1;overflow-y:auto;width:100%;max-height:100%;background:rgba(27,27,27,.5)}.content-area .inline{display:inline}.content-area .inline div{display:inline}.content-area .btn-line{margin-top:35px}.content-area .btn-line div{text-align:right}.content-area .btn-line div button{display:inline;margin-left:10px;min-width:100px}.content-area textarea{background:rgba(27,27,27,.5);color:#fff;margin:15px 0 0 0;font-family:sans-serif;font-size:1.1rem;min-width:100%}.content-area .modules div{padding:6px 20px 6px 0}.content-area .fix-pad{padding-top:14px}
|
||||
1
public/theme/admin/icon-sprite.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg"><symbol viewBox="0 0 24 24" id="vt-add" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></symbol><symbol viewBox="0 0 24 24" id="vt-arrow-back" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></symbol><symbol viewBox="0 0 24 24" id="vt-check" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M19 3H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V5a2 2 0 00-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></symbol><symbol viewBox="0 0 24 24" id="vt-delete" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/><path d="M0 0h24v24H0z" fill="none"/></symbol><symbol viewBox="0 0 24 24" id="vt-edit" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a.996.996 0 000-1.41l-2.34-2.34a.996.996 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></symbol><symbol viewBox="0 0 24 24" id="vt-visibility" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></symbol></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 271 B |
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
||||
|
Before Width: | Height: | Size: 173 B |
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
|
||||
|
Before Width: | Height: | Size: 244 B |
|
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="#fff" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 239 B |
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
|
||||
|
Before Width: | Height: | Size: 262 B |
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
|
||||
|
Before Width: | Height: | Size: 322 B |
59
public/theme/admin/js/components.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
class Component {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.start();
|
||||
}
|
||||
|
||||
handle(data, ds) {
|
||||
}
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
getUrl(ds) {
|
||||
return '';
|
||||
}
|
||||
|
||||
start() {
|
||||
if (window.routerIsReady) {
|
||||
router.addComponent(this.name || VUtils.tempId(), this);
|
||||
this.init();
|
||||
} else {
|
||||
window.addEventListener('routerReady', this.start.bind(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
class RolesComponent extends Component {
|
||||
constructor() {
|
||||
super("/roles");
|
||||
this.tpl = "rolesList";
|
||||
this.tpl2 = "roleEdit";
|
||||
this._url = "/admin/api/roles";
|
||||
}
|
||||
|
||||
async handle(data, ds) {
|
||||
let meTpl = ds.id ? this.tpl2 : this.tpl;
|
||||
await tpl.loadTemplate(meTpl);
|
||||
return await tpl.renderOn(meTpl, data.content);
|
||||
}
|
||||
|
||||
getUrl(ds) {
|
||||
let url = this._url;
|
||||
if (ds.id) {
|
||||
url += '/' + ds.id;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
(() => {
|
||||
// init all Components ;)
|
||||
new RolesComponent();
|
||||
|
||||
if (routerIsReady) {
|
||||
document.body.dispatchEvent(new CustomEvent('triggerRouter'));
|
||||
} else {
|
||||
document.addEventListener('routerReady', e => {
|
||||
document.body.dispatchEvent(new CustomEvent('triggerRouter'));
|
||||
})
|
||||
}
|
||||
})();
|
||||
1
public/theme/admin/js/components.min.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
class Component{constructor(t){this.name=t,this.start()}handle(t,e){}init(){}getUrl(t){return""}start(){window.routerIsReady?(router.addComponent(this.name||VUtils.tempId(),this),this.init()):window.addEventListener("routerReady",this.start.bind(this))}}class RolesComponent extends Component{constructor(){super("/roles"),this.tpl="rolesList",this.tpl2="roleEdit",this._url="/admin/api/roles"}async handle(t,e){let n=e.id?this.tpl2:this.tpl;return await tpl.loadTemplate(n),await tpl.renderOn(n,t.content)}getUrl(t){let e=this._url;return t.id&&(e+="/"+t.id),e}}new RolesComponent,routerIsReady?document.body.dispatchEvent(new CustomEvent("triggerRouter")):document.addEventListener("routerReady",t=>{document.body.dispatchEvent(new CustomEvent("triggerRouter"))});
|
||||
|
|
@ -85,7 +85,9 @@ class VUtils {
|
|||
};
|
||||
Node.prototype.createNew = function (tag, options) {
|
||||
let el = document.createElement(tag);
|
||||
el.classList.add(...VUtils.get(options.classes, []));
|
||||
if (options.classes) {
|
||||
el.classList.add(...VUtils.get(options.classes, []));
|
||||
}
|
||||
el.id = VUtils.get(options.id, '');
|
||||
el.innerHTML = VUtils.get(options.content, "");
|
||||
VUtils.mergeKeys(options.dataset, el.dataset);
|
||||
|
|
@ -123,6 +125,7 @@ class VUtils {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
VUtils.makePublic();
|
||||
|
||||
|
||||
|
|
@ -199,6 +202,7 @@ class VRipple {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rippler = new VRipple();
|
||||
|
||||
|
||||
|
|
@ -426,40 +430,29 @@ class FormHandler {
|
|||
}
|
||||
|
||||
(function () {
|
||||
class VButton extends HTMLElement {
|
||||
class VInput extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
self.id = self.id || VUtils.tempId();
|
||||
self.label = document.createElement('label');
|
||||
self.input = document.createElement('input');
|
||||
self.errorBox = document.createElement('span');
|
||||
self.errorBox.classList.add('error');
|
||||
self.errorBox.innerHTML = self.dataset.error;
|
||||
self.label.setAttribute('for', self.id);
|
||||
self.input.id = self.id;
|
||||
self.input.type = self.getAttribute('type') || 'text';
|
||||
self.label.innerHTML = self.dataset.label;
|
||||
self.input.value = self.innerHTML.trim();
|
||||
let val = self.innerHTML;
|
||||
self.innerHTML = '';
|
||||
self.input.required = self.hasAttribute('required');
|
||||
self.input.name = self.getAttribute('name');
|
||||
self.appendChild(self.input)
|
||||
self.appendChild(self.label)
|
||||
self.appendChild(self.errorBox);
|
||||
self.input.addMultiListener('change input', self.cb.bind(self));
|
||||
let input = self.input = self.createNew('input', {id: self.id})
|
||||
let label = self.createNew('label', {content: self.dataset.label});
|
||||
self.createNew('span', {classes: 'error', content: self.dataset.error});
|
||||
label.setAttribute('for', self.id);
|
||||
input.type = self.getAttribute('type') || 'text';
|
||||
input.value = val.trim();
|
||||
input.required = self.hasAttribute('required');
|
||||
input.name = self.getAttribute('name');
|
||||
input.addMultiListener('change input', self.cb.bind(self));
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
let cl = this.classList;
|
||||
if (this.input.value === "") {
|
||||
cl.remove('focus')
|
||||
} else {
|
||||
cl.add('focus')
|
||||
}
|
||||
this.cb({currentTarget: this.input}, true);
|
||||
}
|
||||
|
||||
cb(e) {
|
||||
cb(e, noInvalid) {
|
||||
let el = e.currentTarget
|
||||
let errorMessage = $('.error-message', el.find('form'));
|
||||
if (errorMessage) {
|
||||
|
|
@ -475,13 +468,15 @@ class FormHandler {
|
|||
cl.add('valid');
|
||||
cl.remove('invalid');
|
||||
} else {
|
||||
cl.remove('valid');
|
||||
cl.add('invalid');
|
||||
if (!noInvalid) {
|
||||
cl.remove('valid');
|
||||
cl.add('invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("v-button", VButton);
|
||||
customElements.define("v-input", VInput);
|
||||
|
||||
if ($('#login')) {
|
||||
new FormHandler('form#login', 'body', () => {
|
||||
|
|
@ -673,4 +668,518 @@ class FormHandler {
|
|||
}
|
||||
|
||||
customElements.define("v-editor", VEditor);
|
||||
})();
|
||||
(() => {
|
||||
class Router {
|
||||
constructor(options) {
|
||||
let self = this;
|
||||
self.options = options;
|
||||
document.body.addDelegatedEventListener('click', '[data-link]', (e, el) => {
|
||||
e.preventDefault();
|
||||
$$('[data-link].active').forEach(ex => (ex.classList.remove('active')));
|
||||
let loader = $('.loader').classList;
|
||||
loader.remove('hide');
|
||||
this.handleRouting(el.dataset).then(e => {
|
||||
loader.add('hide');
|
||||
el.classList.add('active');
|
||||
});
|
||||
})
|
||||
document.body.addEventListener('triggerRouter', e => {
|
||||
this.handle(sessionStorage.getItem('url'));
|
||||
})
|
||||
window.addEventListener('popstate', e => {
|
||||
this.handle(e.state);
|
||||
})
|
||||
self.components = {};
|
||||
window.dispatchEvent(new CustomEvent('routerReady'));
|
||||
window.routerIsReady = true;
|
||||
}
|
||||
|
||||
handle(item) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
item = JSON.parse(item);
|
||||
this.handleRouting(item.data).then(r => {
|
||||
let it = $('[data-link="' + item.data.link + '"]');
|
||||
if (it) {
|
||||
it.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async handleRouting(dataset) {
|
||||
try {
|
||||
let url = dataset.link,
|
||||
comp = this.components[url];
|
||||
if (url === "") return null;
|
||||
if (comp) url = comp.getUrl(dataset);
|
||||
let data = await this.handleRequest(url, true); // we know the admin backend only returns json so we cheat a bit :)
|
||||
if (data.reload) {
|
||||
return location.reload();
|
||||
}
|
||||
comp = comp || this.components[data.component] || null;
|
||||
if (comp) {
|
||||
sessionStorage.setItem('url', JSON.stringify({data: dataset}));
|
||||
comp.handle(data, dataset).then(r => {
|
||||
$(this.options.toReplace).innerHTML = r;
|
||||
history.pushState(JSON.stringify({data: dataset}), document.title);
|
||||
});
|
||||
} else {
|
||||
await alert("Error");
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
async handleRequest(url, forceJSON) {
|
||||
url = url.trim();
|
||||
if (url === '') return;
|
||||
// await ;)
|
||||
return await fetch(url, {
|
||||
credentials: "same-origin"
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw `URL is Status: ${res.status}`;
|
||||
}
|
||||
let c = res.headers.get("Content-Type");
|
||||
if (c.indexOf('json') !== -1 || forceJSON) return res.json();
|
||||
if (c.indexOf('text') !== -1) return res.text();
|
||||
return res.blob();
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
addComponent(name, component) {
|
||||
this.components[name] = component;
|
||||
}
|
||||
}
|
||||
|
||||
window.router = new Router({
|
||||
toReplace: '.content-area'
|
||||
})
|
||||
})();
|
||||
'use strict';
|
||||
|
||||
class VTpeLCore {
|
||||
constructor(options = {}) {
|
||||
this.templates = {};
|
||||
this.dir = options.path || '/tpl/';
|
||||
this.suffix = options.suffix || 'tpl';
|
||||
this.path = options.template || `${this.dir}%s.${this.suffix}`;
|
||||
this.cache = options.cache === undefined ? true : options.cache;
|
||||
}
|
||||
|
||||
async loadTemplate(name) {
|
||||
if (this.templates[name]) {
|
||||
return null;
|
||||
}
|
||||
let path = this.path.replace('%s', name);
|
||||
let rawData = await fetch(path, {cache: "force-cache"});
|
||||
if (rawData.ok) {
|
||||
let data = await rawData.text();
|
||||
this.addTpl(name, data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async loadArray(names) {
|
||||
for (let name of names) {
|
||||
await this.loadTemplate(name);
|
||||
}
|
||||
}
|
||||
|
||||
addTpl(name, content) {
|
||||
let temp = this.templates[name] = new VTpeLTemplate(name, content, this);
|
||||
temp.parseContent(this.cache);
|
||||
}
|
||||
|
||||
async renderOn(name, data) {
|
||||
if (this.templates[name]) {
|
||||
return await this.templates[name].render(data);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
'use strict';
|
||||
|
||||
const VParserTypes = {
|
||||
content: 0,
|
||||
variable: 1,
|
||||
for: 2,
|
||||
forEach: 3,
|
||||
forContent: 4,
|
||||
forEnd: 5,
|
||||
if: 6,
|
||||
ifContent: 7,
|
||||
ifEnd: 8,
|
||||
assign: 9,
|
||||
include: 10,
|
||||
none: -1,
|
||||
};
|
||||
|
||||
|
||||
class VTpeLParser {
|
||||
constructor(name, content) {
|
||||
let self = this;
|
||||
self.name = name;
|
||||
self.legex = content.trim();
|
||||
self.index = 0;
|
||||
self.content = '';
|
||||
self.parsed = [];
|
||||
self.contexts = [0];
|
||||
}
|
||||
|
||||
tokenize() {
|
||||
let self = this;
|
||||
for (self.index = 0; self.index < self.legex.length; self.index++) {
|
||||
let i = self.index,
|
||||
char = self.legex.charAt(i);
|
||||
if (self.nextContains('/*', i, true)) {
|
||||
self.extract('*/', VParserTypes.none)
|
||||
} else if (self.nextContains('// ', i, true)) {
|
||||
self.extract("\n", VParserTypes.none);
|
||||
} else if (self.nextContains('<!--', i, true)) {
|
||||
self.extract('-->', VParserTypes.none);
|
||||
} else if (self.nextContains('{for(', i, true)) {
|
||||
self.extract(')}', VParserTypes.for);
|
||||
self.contexts.push(VParserTypes.for);
|
||||
} else if (self.nextContains('{include(', i, true)) {
|
||||
self.extract(')}', VParserTypes.include);
|
||||
self.contexts.push(VParserTypes.include);
|
||||
} else if (self.nextContains('{foreach(', i, true)) {
|
||||
self.extract(')}', VParserTypes.forEach);
|
||||
self.contexts.push(VParserTypes.forEach);
|
||||
} else if (self.nextContains('{/for}', i, true)) {
|
||||
self.addType(VParserTypes.forEnd);
|
||||
self.contexts.pop();
|
||||
} else if (self.nextContains('{if(', i, true)) {
|
||||
self.extract(')}', VParserTypes.if);
|
||||
self.contexts.push(VParserTypes.if);
|
||||
} else if (self.nextContains('{/if}', i, true)) {
|
||||
self.addType(VParserTypes.ifEnd);
|
||||
self.contexts.pop();
|
||||
} else if (self.nextContains('$${', i, true)) {
|
||||
self.extract('}', VParserTypes.assign);
|
||||
} else if (self.nextContains('${', i, true)) {
|
||||
self.extract('}', VParserTypes.variable);
|
||||
} else {
|
||||
self.content += char;
|
||||
}
|
||||
}
|
||||
self.addType(VParserTypes.content);
|
||||
return self.parsed;
|
||||
}
|
||||
|
||||
addType(type) {
|
||||
let self = this;
|
||||
let content = self.content.replace(/^\n+|\n+$/g, ''),
|
||||
instructions = self.findInstructions(type);
|
||||
self.content = '';
|
||||
if (type !== VParserTypes.none) {
|
||||
if (type === VParserTypes.content && content === '') {
|
||||
return null;
|
||||
}
|
||||
return self.parsed.push({
|
||||
content: content,
|
||||
type: type,
|
||||
context: self.contexts[self.contexts.length - 1],
|
||||
instructions: instructions
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
nextContains(find, index, add = false) {
|
||||
let count = this.nextContainsRaw(this.legex, find, index);
|
||||
if (add && count > 0) {
|
||||
this.index += count;
|
||||
}
|
||||
return count > 0 || count === -1;
|
||||
}
|
||||
|
||||
nextContainsRaw(raw, find, index) {
|
||||
if (typeof find === "string") {
|
||||
find = find.split("");
|
||||
}
|
||||
let count = find.length;
|
||||
if (count < 1) {
|
||||
return -1;
|
||||
}
|
||||
for (let i = 0; i < count; i++) {
|
||||
let nc = raw.charAt(index + i);
|
||||
if ((find[i] === '\n' && nc === undefined)) {
|
||||
return count;
|
||||
}
|
||||
if (find[i] !== nc) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
extract(findUntil = '}', type = 1) {
|
||||
let self = this;
|
||||
self.addType(0);
|
||||
findUntil = findUntil.split("")
|
||||
let content = '',
|
||||
index = self.index,
|
||||
legex = self.legex,
|
||||
firstFind = findUntil.shift();
|
||||
for (let i = self.index; i < legex.length; i++) {
|
||||
let char = legex.charAt(i);
|
||||
if (char === firstFind && self.nextContains(findUntil, i + 1)) {
|
||||
console.debug(`[Parser][${index} > ${i}] >> ${content}`);
|
||||
self.index = i + findUntil.length;
|
||||
self.content = content.trim();
|
||||
self.addType(type);
|
||||
return;
|
||||
}
|
||||
content += char;
|
||||
}
|
||||
if (firstFind === "\n") {
|
||||
self.index = legex.length;
|
||||
self.content = content.trim();
|
||||
self.addType(type);
|
||||
return
|
||||
}
|
||||
throw 'Template variable at Position: ' + index + ' not closed!';
|
||||
}
|
||||
|
||||
// @todo implement split... is needed for if statements and math stuff or get it stupid!
|
||||
getOperator(string) {
|
||||
let operators = [];
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
if (this.nextContainsRaw(string, "(", i)) {
|
||||
let innerCon = '';
|
||||
for (let x = 0; x < string.length; x++) {
|
||||
let char = string.charAt(i + x);
|
||||
if (char === ')') {
|
||||
break;
|
||||
}
|
||||
innerCon += char;
|
||||
}
|
||||
operators = [...operators, this.getOperator(innerCon)];
|
||||
} else {
|
||||
}
|
||||
}
|
||||
return operators;
|
||||
}
|
||||
|
||||
findInstructions(type) {
|
||||
if (type === VParserTypes.if) {
|
||||
return this.getOperator(this.content);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
'use strict';
|
||||
|
||||
class VTepLInterpreter {
|
||||
constructor(parser, core) {
|
||||
this.parser = parser;
|
||||
this.data = [];
|
||||
this.content = '';
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async render(data) {
|
||||
let self = this;
|
||||
self.data = data;
|
||||
let newData = await self.interpreter(self.parser.parsed);
|
||||
self.data = [];
|
||||
return newData[0];
|
||||
}
|
||||
|
||||
async interpreter(parsed, index = 0) {
|
||||
let self = this;
|
||||
let types = VParserTypes;
|
||||
let tplCont = '';
|
||||
for (let i = index; i < parsed.length; i++) {
|
||||
let item = parsed[i],
|
||||
content = item.content;
|
||||
switch (item.type) {
|
||||
case types.content:
|
||||
tplCont += content;
|
||||
break;
|
||||
case types.variable:
|
||||
tplCont += self.getVariable(content)
|
||||
break;
|
||||
case types.assign:
|
||||
let data = content.split("="),
|
||||
key = data.shift();
|
||||
self.setVariable(data.join("=").trim(), key.trim());
|
||||
break;
|
||||
case types.forEach:
|
||||
let d = await this.handleForEach(item, parsed, i);
|
||||
i = d[0];
|
||||
tplCont += d[1];
|
||||
break;
|
||||
case types.for:
|
||||
let fd = await this.handleFor(item, parsed, i);
|
||||
i = fd[0];
|
||||
tplCont += fd[1];
|
||||
break;
|
||||
case types.if:
|
||||
let id = await this.handleIf(item, parsed, i);
|
||||
i = id[0];
|
||||
tplCont += id[1];
|
||||
break;
|
||||
case types.include:
|
||||
tplCont += await this.handleInclude(item);
|
||||
break;
|
||||
case types.ifEnd:
|
||||
tplCont += content;
|
||||
return [tplCont, i];
|
||||
case types.forEnd:
|
||||
tplCont += content;
|
||||
return [tplCont, i];
|
||||
default:
|
||||
console.warn("Invalid Type found");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [tplCont, parsed.length];
|
||||
}
|
||||
|
||||
getVariable(variable) {
|
||||
variable = variable.toString();
|
||||
if (this.data[variable]) {
|
||||
return this.data[variable];
|
||||
}
|
||||
let split = variable.split("."),
|
||||
prevVar = this.data;
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
prevVar = prevVar[split[i]] || prevVar;
|
||||
}
|
||||
if (typeof prevVar === 'string') {
|
||||
return prevVar;
|
||||
}
|
||||
if (typeof prevVar === 'number') {
|
||||
return prevVar.toString();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
setVariable(value, variable) {
|
||||
let c = this.getVariable(value);
|
||||
if (c !== '') {
|
||||
value = c;
|
||||
}
|
||||
this.data[variable] = value;
|
||||
}
|
||||
|
||||
async handleForEach(item, parsed, i) {
|
||||
let content = item.content.split(" as ");
|
||||
let root = this.getVariable(content[0].trim());
|
||||
let addTo = 0,
|
||||
isInvalid = false;
|
||||
if (root === '') {
|
||||
isInvalid = true;
|
||||
root = {invalid: "true"};
|
||||
}
|
||||
let d = Object.keys(root),
|
||||
raw = '',
|
||||
varContent = content[1].trim().split(",");
|
||||
for (let x of d) {
|
||||
if (varContent.length === 2) {
|
||||
this.setVariable(x, varContent[1]);
|
||||
}
|
||||
this.setVariable(root[x], varContent[0]);
|
||||
let data = await this.interpreter(parsed, i + 1);
|
||||
addTo = data[1];
|
||||
raw += data[0];
|
||||
}
|
||||
if (isInvalid) {
|
||||
raw = '';
|
||||
}
|
||||
return [addTo, raw];
|
||||
}
|
||||
|
||||
async handleInclude(item) {
|
||||
let split = item.content.split(";"),
|
||||
name = split.shift(),
|
||||
data = {};
|
||||
await this.core.loadTemplate(name);
|
||||
for (let item of split) {
|
||||
let d = item.split("="),
|
||||
index = d.shift(),
|
||||
dat = d.join("=");
|
||||
if (dat.startsWith("$")) {
|
||||
dat = this.getVariable(dat.substr(1, dat.length));
|
||||
}
|
||||
data[index] = dat;
|
||||
}
|
||||
return await this.core.renderOn(name, data);
|
||||
}
|
||||
|
||||
async handleFor(item, parsed, ind) {
|
||||
let content = item.content.split(" as "),
|
||||
addTo = 0,
|
||||
count = content[0].trim().split(".."),
|
||||
max = parseInt(count[1]),
|
||||
min = parseInt(count[0]),
|
||||
newContent = '';
|
||||
for (let i = min; i < max; i++) {
|
||||
this.setVariable(i.toString(), content[1]);
|
||||
let data = await this.interpreter(parsed, ind + 1);
|
||||
addTo = data[1];
|
||||
newContent += data[0];
|
||||
}
|
||||
return [addTo, newContent];
|
||||
}
|
||||
|
||||
async handleIf(item, parsed, i) {
|
||||
let data = await this.interpreter(parsed, i + 1);
|
||||
return [data[1], data[0]];
|
||||
}
|
||||
}
|
||||
|
||||
'use strict';
|
||||
|
||||
class VTpeLTemplate {
|
||||
constructor(name, content, core) {
|
||||
this.name = name;
|
||||
this.tpl = content;
|
||||
this.parser = new VTpeLParser(name, content);
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async render(data = {}) {
|
||||
return await new VTepLInterpreter(this.parser, this.core).render(data);
|
||||
}
|
||||
|
||||
parseContent(cache) {
|
||||
if (cache) {
|
||||
let storage = localStorage.getItem(this.name);
|
||||
if (storage) {
|
||||
this.parser.parsed = JSON.parse(storage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.parser.tokenize();
|
||||
if (cache) {
|
||||
localStorage.setItem(this.name, JSON.stringify(this.parser.parsed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(() => {
|
||||
window.tpl = new VTpeLCore({
|
||||
template: '/admin/api/templateLoader?tpl=%s',
|
||||
cache: !document.body.hasAttribute('debug')
|
||||
});
|
||||
//preload includes to make sure they are loaded always :)
|
||||
window.tpl.loadArray([
|
||||
'includes/btn',
|
||||
'includes/input',
|
||||
'includes/select',
|
||||
'includes/svg',
|
||||
])
|
||||
})();
|
||||