feat(server) layout handling; placekeeper wip
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { LayServer } from '../src';
|
import { LayServer } from '../src';
|
||||||
import {resolve} from 'path';
|
import {resolve} from 'path';
|
||||||
import Html from '../src/Views/Html';
|
import Html from '../src/Views/Html';
|
||||||
|
import Markdown from '../src/Views/Markdown';
|
||||||
|
|
||||||
const APP_PORT = Bun.env.APP_PORT ?? Bun.env.PORT ?? 3000;
|
const APP_PORT = Bun.env.APP_PORT ?? Bun.env.PORT ?? 3000;
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ class InfoController {
|
|||||||
|
|
||||||
const server = new LayServer({
|
const server = new LayServer({
|
||||||
viewsPath: resolve(__dirname, 'views'),
|
viewsPath: resolve(__dirname, 'views'),
|
||||||
|
layout: 'blank.html',
|
||||||
loaders: new Map([[
|
loaders: new Map([[
|
||||||
'html', new Html()
|
'html', new Html()
|
||||||
]]),
|
]]),
|
||||||
@@ -43,7 +45,7 @@ const server = new LayServer({
|
|||||||
function Main(server) {
|
function Main(server) {
|
||||||
server.get('/', async (req, res) => {
|
server.get('/', async (req, res) => {
|
||||||
return res.json({message: 'hello world'})
|
return res.json({message: 'hello world'})
|
||||||
}, { domain: 'api.tipedi.local' });
|
}, { domain: 'example.layc.dev' });
|
||||||
server.all('/data', (req, res) => {
|
server.all('/data', (req, res) => {
|
||||||
let respBody = `your ${req.method} request was well recieved`;
|
let respBody = `your ${req.method} request was well recieved`;
|
||||||
switch (req.method) {
|
switch (req.method) {
|
||||||
@@ -86,6 +88,14 @@ server.get('/', async (req, res) => {
|
|||||||
return res.render('index');
|
return res.render('index');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.get('/blog/:slug', async (req, res) => {
|
||||||
|
return res.send('index.html', { layout: 'blank.html' });
|
||||||
|
});
|
||||||
|
|
||||||
|
server.use(async (req, res) => {
|
||||||
|
return res.send('batman');
|
||||||
|
})
|
||||||
|
|
||||||
server.listen(APP_PORT, () => {
|
server.listen(APP_PORT, () => {
|
||||||
console.log(`server listen on ${APP_PORT}`);
|
console.log(`server listen on ${APP_PORT}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,37 +1,96 @@
|
|||||||
import MimeTypeMap from './MimeTypeMap';
|
import MimeTypeMap from './MimeTypeMap';
|
||||||
|
|
||||||
export default class LayResponse {
|
export default class LayResponse {
|
||||||
routing = undefined;
|
#routing = undefined;
|
||||||
body;
|
#body = 'Not Implemented';
|
||||||
statusCode;
|
#file = undefined;
|
||||||
headerList = new Map();
|
#redirectUrl = undefined;
|
||||||
mimeType;
|
#statusCode = 502;
|
||||||
options = {
|
#headerList = new Map();
|
||||||
|
#mimeType = 'text/plain';
|
||||||
|
#options = {
|
||||||
disableFileCheck: false,
|
disableFileCheck: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(routing) {
|
constructor(routing) {
|
||||||
this.routing = routing;
|
this.#routing = routing;
|
||||||
this.body = 'Not Implemented';
|
|
||||||
this.statusCode = 502;
|
|
||||||
this.headerList = new Map();
|
|
||||||
this.mimeType = 'text/plain';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(str) {
|
type(key) {
|
||||||
this.type('text');
|
if (!MimeTypeMap.has(key)) {
|
||||||
this.body = str;
|
if(this.#mimeType != 'text/plain') this.#mimeType = MimeTypeMap.get('text');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.mimeType = MimeTypeMap.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
status(value = null) {
|
||||||
|
if (isNaN(value)) throw new TypeError('status has to be a number');
|
||||||
|
this.#statusCode = value || 502;
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect(value = null, status = 302) {
|
||||||
|
if (value === null) {
|
||||||
|
return this.#redirectUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#redirectUrl = value;
|
||||||
|
this.status(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
headers() {
|
||||||
|
if (!this.#headerList.get('Content-Type')) {
|
||||||
|
this.#headerList.set('Content-Type', `${this.#mimeType}`);
|
||||||
|
}
|
||||||
|
return this.#headerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
header(key, value) {
|
||||||
|
this.#headerList.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async build(options = {}) {
|
||||||
|
if (this.#redirectUrl) {
|
||||||
|
return Response.redirect(this.#redirectUrl, this.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.#file) return new Response(this.#file);
|
||||||
|
|
||||||
|
if(['text/plain'].includes(this.#mimeType)) {
|
||||||
|
await this.handleLayout(this.#body, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(this.#body, {
|
||||||
|
status: this.status(),
|
||||||
|
headers: this.headers(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async send() {
|
||||||
|
await this.text(...arguments);
|
||||||
|
|
||||||
|
if (typeof(arguments[0]) != 'string' || !arguments[0]) return;
|
||||||
|
if (this.#options.disableFileCheck) return;
|
||||||
|
|
||||||
if (this.options.disableFileCheck) return;
|
|
||||||
try {
|
try {
|
||||||
const temp = Bun.file(str);
|
const temp = Bun.file(arguments[0]);
|
||||||
|
|
||||||
if (await temp.exists()) {
|
if (await temp.exists()) {
|
||||||
this.file = str;
|
this.#file = temp;
|
||||||
}
|
}
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async text() {
|
||||||
|
let [value, ...rest] = arguments;
|
||||||
|
if (typeof value != 'string') {
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.type('text');
|
||||||
|
await this.handleLayout(value, ...rest);
|
||||||
|
}
|
||||||
|
|
||||||
json(data, options = {}) {
|
json(data, options = {}) {
|
||||||
let { replacer, space } = options;
|
let { replacer, space } = options;
|
||||||
if (!Array.isArray(replacer) && typeof replacer != 'function') {
|
if (!Array.isArray(replacer) && typeof replacer != 'function') {
|
||||||
@@ -41,72 +100,30 @@ export default class LayResponse {
|
|||||||
space = 0;
|
space = 0;
|
||||||
}
|
}
|
||||||
this.type('json');
|
this.type('json');
|
||||||
this.body = JSON.stringify(data, replacer, space);
|
this.#body = JSON.stringify(data, replacer, space);
|
||||||
}
|
|
||||||
|
|
||||||
type(key) {
|
|
||||||
if (!MimeTypeMap.has(key)) {
|
|
||||||
this.mimeType = MimeTypeMap.get('text');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.mimeType = MimeTypeMap.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
status(value = null) {
|
|
||||||
if (isNaN(value)) throw new Error('status has to be a number');
|
|
||||||
this.statusCode = value || 502;
|
|
||||||
}
|
|
||||||
|
|
||||||
text(value = null) {
|
|
||||||
if (typeof value != 'string') {
|
|
||||||
value = JSON.stringify(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.type('text');
|
|
||||||
this.body = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
json(value = null) {
|
|
||||||
this.text(value);
|
|
||||||
this.type('json');
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(value = null, status = 302) {
|
|
||||||
if (value === null) {
|
|
||||||
return this.redirectUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.redirectUrl = value;
|
|
||||||
this.status(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
headers() {
|
|
||||||
if (!this.headerList.get('Content-Type')) {
|
|
||||||
this.headerList.set('Content-Type', `${this.mimeType}`);
|
|
||||||
}
|
|
||||||
return this.headerList;
|
|
||||||
}
|
|
||||||
|
|
||||||
header(key, value) {
|
|
||||||
this.headerList.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
build(options = {}) {
|
|
||||||
if (this.redirectUrl) {
|
|
||||||
return Response.redirect(this.redirectUrl, this.status());
|
|
||||||
}
|
|
||||||
return new Response(this.body, {
|
|
||||||
status: this.status(),
|
|
||||||
headers: this.headers(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async render(path, data = {}) {
|
async render(path, data = {}) {
|
||||||
const loader = await this.routing.getServer().load(path);
|
const loader = await this.#routing.getServer().load(path);
|
||||||
const content = await loader.parse(data, {
|
const content = await loader.parse(data);
|
||||||
status: this.status(),
|
|
||||||
headers: this.headers(),
|
|
||||||
});
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleLayout(content, options = {}) {
|
||||||
|
this.#body = String(content);
|
||||||
|
if(!options.hasOwnProperty('layout') || typeof options.layout !== 'string') {
|
||||||
|
if(this.#routing.getServer().hasLayout()) {
|
||||||
|
options.layout = this.#routing.getServer().getLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!options.layout) return
|
||||||
|
|
||||||
|
const layout = await this.#routing.getServer().load(options.layout);
|
||||||
|
if(layout) {
|
||||||
|
this.#body = await layout.parse({ ...(options?.data || {}), content: String(content) }, options);
|
||||||
|
this.#mimeType = layout.mimeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ import Logger from './Utils/Logger';
|
|||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
export default class LayServer extends BaseRouter {
|
export default class LayServer extends BaseRouter {
|
||||||
controllers = new Map();
|
#controllers = new Map();
|
||||||
loaders = new Map();
|
#loaders = new Map();
|
||||||
proc = null;
|
#proc = null;
|
||||||
publicFiles;
|
#publicFiles;
|
||||||
routing = null;
|
#routing = null;
|
||||||
options = new Map([
|
#options = new Map([
|
||||||
|
['layout', undefined],
|
||||||
['loaders', undefined],
|
['loaders', undefined],
|
||||||
['publicDir', undefined],
|
['publicDir', undefined],
|
||||||
['viewsPath', undefined],
|
['viewsPath', undefined],
|
||||||
@@ -40,15 +41,15 @@ export default class LayServer extends BaseRouter {
|
|||||||
const optionKeys = Object.getOwnPropertyNames(options);
|
const optionKeys = Object.getOwnPropertyNames(options);
|
||||||
|
|
||||||
for (const key of optionKeys) {
|
for (const key of optionKeys) {
|
||||||
if (!this.options.has(key)) continue;
|
if (!this.#options.has(key)) continue;
|
||||||
if (key == 'loaders') continue;
|
if (key == 'loaders') continue;
|
||||||
const valueType = typeof this.options.get(key);
|
const valueType = typeof this.#options.get(key);
|
||||||
if (
|
if (
|
||||||
typeof options[key] !== 'undefined' &&
|
typeof options[key] !== 'undefined' &&
|
||||||
(typeof options[key] == valueType ||
|
(typeof options[key] == valueType ||
|
||||||
valueType == 'undefined')
|
valueType == 'undefined')
|
||||||
)
|
)
|
||||||
this.options.set(key, options[key]);
|
this.#options.set(key, options[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.hasOwnProperty('loaders')) {
|
if (options.hasOwnProperty('loaders')) {
|
||||||
@@ -58,10 +59,10 @@ export default class LayServer extends BaseRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.routing = new LayRouting(this);
|
this.#routing = new LayRouting(this);
|
||||||
|
|
||||||
if(options.router) {
|
if(options.router) {
|
||||||
this.routing.addRouter(options.router);
|
this.#routing.addRouter(options.router);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.hasOwnProperty('controllers')) {
|
if (options.hasOwnProperty('controllers')) {
|
||||||
@@ -72,13 +73,13 @@ export default class LayServer extends BaseRouter {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!options.publicFiles &&
|
!options.publicFiles &&
|
||||||
this.options.discoverPublicFiles &&
|
this.#options.discoverPublicFiles &&
|
||||||
this.options.publicDir
|
this.#options.publicDir
|
||||||
)
|
)
|
||||||
this.indexPublicFiles(this.options.publicDir);
|
this.indexPublicFiles(this.#options.publicDir);
|
||||||
|
|
||||||
if (!this.options.viewsEngine && this.options.viewsPath) {
|
if (!this.#options.viewsEngine && this.#options.viewsPath) {
|
||||||
this.options.viewsEngine = new HtmlEngine();
|
this.#options.viewsEngine = new HtmlEngine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,9 +90,9 @@ export default class LayServer extends BaseRouter {
|
|||||||
* @returns {*} - The current value of the option, or undefined if the option does not exist.
|
* @returns {*} - The current value of the option, or undefined if the option does not exist.
|
||||||
*/
|
*/
|
||||||
option(key, value = undefined) {
|
option(key, value = undefined) {
|
||||||
if (!this.options.has(key)) return undefined;
|
if (!this.#options.has(key)) return undefined;
|
||||||
if (value !== undefined) this.option.set(key, value);
|
if (value !== undefined) this.option.set(key, value);
|
||||||
return this.options.get(key);
|
return this.#options.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,11 +106,11 @@ export default class LayServer extends BaseRouter {
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
'publicFiles option has to be an array or undefined'
|
'publicFiles option has to be an array or undefined'
|
||||||
);
|
);
|
||||||
if (!this.publicFiles || !Array.isArray(this.publicFiles))
|
if (!this.#publicFiles || !Array.isArray(this.#publicFiles))
|
||||||
this.publicFiles = [];
|
this.#publicFiles = [];
|
||||||
for (let path of filePaths) {
|
for (let path of filePaths) {
|
||||||
if (typeof path != 'string') continue;
|
if (typeof path != 'string') continue;
|
||||||
this.publicFiles.push(path);
|
this.#publicFiles.push(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,11 +140,11 @@ export default class LayServer extends BaseRouter {
|
|||||||
|
|
||||||
// if the given controller is just an anonymous function defining the routes itself
|
// if the given controller is just an anonymous function defining the routes itself
|
||||||
if (!controller?.prototype?.constructor?.name) {
|
if (!controller?.prototype?.constructor?.name) {
|
||||||
controller(this.routing);
|
controller(this.#routing);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.controllers.set(
|
this.#controllers.set(
|
||||||
controller?.name,
|
controller?.name,
|
||||||
this.instantiateController(controller)
|
this.instantiateController(controller)
|
||||||
);
|
);
|
||||||
@@ -156,9 +157,9 @@ export default class LayServer extends BaseRouter {
|
|||||||
* @returns {Object} - The instantiated controller.
|
* @returns {Object} - The instantiated controller.
|
||||||
*/
|
*/
|
||||||
instantiateController(controller) {
|
instantiateController(controller) {
|
||||||
this.routing.setNamespace(controller.namespace);
|
this.#routing.setNamespace(controller.namespace);
|
||||||
const _temp = new controller(this.routing);
|
const _temp = new controller(this.#routing);
|
||||||
if (controller.namespace) this.routing.setNamespace(null);
|
if (controller.namespace) this.#routing.setNamespace(null);
|
||||||
return _temp;
|
return _temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,10 +182,10 @@ export default class LayServer extends BaseRouter {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!url.pathname.endsWith('/') &&
|
!url.pathname.endsWith('/') &&
|
||||||
Array.isArray(this.publicFiles) &&
|
Array.isArray(this.#publicFiles) &&
|
||||||
this.publicFiles.length > 0
|
this.#publicFiles.length > 0
|
||||||
) {
|
) {
|
||||||
let path = this.publicFiles.find(
|
let path = this.#publicFiles.find(
|
||||||
(v) => url.pathname.substring(1) === v
|
(v) => url.pathname.substring(1) === v
|
||||||
);
|
);
|
||||||
if (path) {
|
if (path) {
|
||||||
@@ -192,7 +193,7 @@ export default class LayServer extends BaseRouter {
|
|||||||
return new Response(
|
return new Response(
|
||||||
Bun.file(
|
Bun.file(
|
||||||
resolvePath(
|
resolvePath(
|
||||||
this.options.get('publicDir') ??
|
this.#options.get('publicDir') ??
|
||||||
`${import.meta.dir}/public`,
|
`${import.meta.dir}/public`,
|
||||||
path
|
path
|
||||||
)
|
)
|
||||||
@@ -207,11 +208,11 @@ export default class LayServer extends BaseRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.enforceTrailingSlash && !url.pathname.endsWith('/')) {
|
if (this.#options.enforceTrailingSlash && !url.pathname.endsWith('/')) {
|
||||||
return Response.redirect(`${url.toString()}/`, 302);
|
return Response.redirect(`${url.toString()}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = await this.routing.handle(req);
|
let resp = await this.#routing.handle(req);
|
||||||
if (resp instanceof Response) return resp;
|
if (resp instanceof Response) return resp;
|
||||||
return new Response('Not Found', { status: 404 });
|
return new Response('Not Found', { status: 404 });
|
||||||
}
|
}
|
||||||
@@ -254,7 +255,7 @@ export default class LayServer extends BaseRouter {
|
|||||||
* @returns {Promise<string|null>} - The path of the view file, or null if not found.
|
* @returns {Promise<string|null>} - The path of the view file, or null if not found.
|
||||||
*/
|
*/
|
||||||
async getViewPath(filepath) {
|
async getViewPath(filepath) {
|
||||||
let path = this.options.get('viewsPath');
|
let path = this.#options.get('viewsPath');
|
||||||
if (!path.endsWith('/')) path += '/';
|
if (!path.endsWith('/')) path += '/';
|
||||||
|
|
||||||
const filename = filepath.split('/').pop().toString();
|
const filename = filepath.split('/').pop().toString();
|
||||||
@@ -290,7 +291,7 @@ export default class LayServer extends BaseRouter {
|
|||||||
const fullFilePath = await this.getViewPath(filepath);
|
const fullFilePath = await this.getViewPath(filepath);
|
||||||
if (!fullFilePath) return new DefaultLoader(filepath);
|
if (!fullFilePath) return new DefaultLoader(filepath);
|
||||||
const filename = fullFilePath.split('/').pop().toString();
|
const filename = fullFilePath.split('/').pop().toString();
|
||||||
const ext = filename.substring(filename.indexOf('.') + 1);
|
const ext = filename.substring(filename.lastIndexOf('.') + 1);
|
||||||
|
|
||||||
if (this.loaders.size > 0) {
|
if (this.loaders.size > 0) {
|
||||||
for (const keys of this.loaders.keys()) {
|
for (const keys of this.loaders.keys()) {
|
||||||
@@ -302,7 +303,21 @@ export default class LayServer extends BaseRouter {
|
|||||||
return new DefaultLoader(filepath);
|
return new DefaultLoader(filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(path, cb) {
|
add(path, cb) {
|
||||||
this.routing.add(...arguments)
|
this.#routing.add(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
use (cb) {
|
||||||
|
this.add('*', cb, arguments[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasLayout() {
|
||||||
|
return typeof(this.#options.get('layout')) == 'string' && this.#options.get('layout').length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayout() {
|
||||||
|
if(!this.#options.has('layout')) return undefined;
|
||||||
|
|
||||||
|
return this.#options.get('layout');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,4 +97,8 @@ export default class LayRouting extends BaseRouter {
|
|||||||
this.routers.get(options.domain).add (path, cb, options);
|
this.routers.get(options.domain).add (path, cb, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use (cb) {
|
||||||
|
this.add('*', cb, arguments[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,16 @@ export default class Router extends BaseRouter {
|
|||||||
if (path.endsWith('/')) path = path.substring(0, path.length - 1);
|
if (path.endsWith('/')) path = path.substring(0, path.length - 1);
|
||||||
|
|
||||||
let currentNode = this.routes;
|
let currentNode = this.routes;
|
||||||
|
let fallbackNode = currentNode.has('*') ? currentNode.get('*') : undefined;
|
||||||
let paramsList = [];
|
let paramsList = [];
|
||||||
|
|
||||||
if (path != '') {
|
if (path != '') {
|
||||||
let segments = path.split('/');
|
let segments = path.split('/');
|
||||||
for (let index = 0; index < segments.length; index++) {
|
for (let index = 0; index < segments.length; index++) {
|
||||||
const segment = segments[index];
|
const segment = segments[index];
|
||||||
|
if (currentNode.has('*')) {
|
||||||
|
fallbackNode = currentNode.get('*');
|
||||||
|
}
|
||||||
|
|
||||||
if (currentNode.has(segment)) {
|
if (currentNode.has(segment)) {
|
||||||
currentNode = currentNode.get(segment);
|
currentNode = currentNode.get(segment);
|
||||||
@@ -51,7 +55,7 @@ export default class Router extends BaseRouter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentNode = null;
|
currentNode = fallbackNode ?? null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,21 +102,24 @@ export default class Router extends BaseRouter {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await reqContext.request.parseBody();
|
await reqContext.request.parseBody();
|
||||||
const retVal = await handle.callback(
|
let retVal = await handle.callback(
|
||||||
reqContext.request,
|
reqContext.request,
|
||||||
reqContext.response
|
reqContext.response
|
||||||
);
|
);
|
||||||
if (typeof retVal != 'undefined') {
|
if (typeof retVal != 'undefined') {
|
||||||
if (retVal instanceof Response) {
|
if (retVal instanceof Promise) {
|
||||||
|
retVal = await retVal;
|
||||||
|
} else if (retVal instanceof Response) {
|
||||||
return retVal;
|
return retVal;
|
||||||
} else if (typeof retVal == 'string') {
|
} else if (typeof retVal == 'string') {
|
||||||
reqContext.response.send(retVal);
|
await reqContext.response.text(retVal);
|
||||||
} else {
|
} else {
|
||||||
reqContext.response.json(retVal);
|
reqContext.response.json(retVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Logger(err);
|
Logger(err);
|
||||||
|
console.log(err);
|
||||||
return new Response('Error', { status: 500 });
|
return new Response('Error', { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +130,7 @@ export default class Router extends BaseRouter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(reqContext.response.body, {
|
return await reqContext.response.build();
|
||||||
status: reqContext.response.status() || 200,
|
|
||||||
headers: reqContext.response.headers() || {},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(route, callback) {
|
add(route, callback) {
|
||||||
|
|||||||
@@ -1,51 +1,16 @@
|
|||||||
import Logger from './Logger';
|
import Logger from './Logger';
|
||||||
|
|
||||||
|
const TOKEN_PAIRS = [
|
||||||
|
['{{','}}']
|
||||||
|
]
|
||||||
|
|
||||||
export default class Placekeeper {
|
export default class Placekeeper {
|
||||||
static init(content) {
|
static init(content) {
|
||||||
const sections = Array();
|
const sections = Array();
|
||||||
const variables = new Map();
|
const variables = new Map();
|
||||||
const currentSection = new Map();
|
const currentSection = new Map();
|
||||||
|
|
||||||
for (let idx = 0; idx < content.length; idx++) {
|
console.log('content to array', Placekeeper.contentToArray(content));
|
||||||
const char = content[idx];
|
|
||||||
if (char == '{') {
|
|
||||||
if (content[idx + 1] == '{') {
|
|
||||||
if (currentSection.has('start')) currentSection.clear(); // everything before this was not a section then
|
|
||||||
// begins a new section meant to be for a variable but still can be an operational section
|
|
||||||
// @example: {{#if variableName}} {{#for variableName}} {{#while variableName}}
|
|
||||||
currentSection.set('start', idx);
|
|
||||||
}
|
|
||||||
} else if (currentSection.has('start')) {
|
|
||||||
if (char == '}') {
|
|
||||||
if (content[idx + 1] == '}') {
|
|
||||||
// ends a section
|
|
||||||
currentSection.set('end', idx + 2);
|
|
||||||
currentSection.set(
|
|
||||||
'content',
|
|
||||||
content
|
|
||||||
.substring(
|
|
||||||
currentSection.get('start'),
|
|
||||||
currentSection.get('end')
|
|
||||||
)
|
|
||||||
.toString()
|
|
||||||
.trim()
|
|
||||||
);
|
|
||||||
|
|
||||||
currentSection.set(
|
|
||||||
'variables',
|
|
||||||
Placekeeper.parseVariables(
|
|
||||||
currentSection.get('content')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
sections.push(new Map(currentSection));
|
|
||||||
currentSection.clear();
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger('content to array', Placekeeper.contentToArray(content));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
variables,
|
variables,
|
||||||
@@ -53,37 +18,47 @@ export default class Placekeeper {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static contentToArray(content) {
|
static walkToEnd(content, endToken = undefined) {
|
||||||
const parts = content.split(/\{\{([^}]+)\}\}|\{(#[a-zA-Z]+)\s*([^}]+)\}/);
|
let result = {
|
||||||
const result = [];
|
stack: [],
|
||||||
let stack = [];
|
length: 0
|
||||||
function processStack() {
|
};
|
||||||
const block = stack.pop();
|
for(let idx = 0; idx < content.length; idx++) {
|
||||||
result.push(new Function('data', 'with(data){return `'+ block.content.join('') + '`;}'));
|
const char = content[idx];
|
||||||
}
|
const token = TOKEN_PAIRS.find((t) => {
|
||||||
|
if(!t[0].startsWith(char)) return false;
|
||||||
|
return content.substring(idx, idx + t[0].length) == t[0];
|
||||||
|
});
|
||||||
|
|
||||||
// Process each part of the split content
|
if(token) {
|
||||||
for (let i = 0; i < parts.length; i++) {
|
// found new start token walk to end:
|
||||||
if (parts[i] === '#' && ['for', 'if', 'else', 'elseif', 'while'].includes(parts[i + 1])) {
|
idx += token[0].length
|
||||||
// Start of control structure
|
const subset = Placekeeper.walkToEnd(content.substring(idx), token[1]);
|
||||||
stack.push({ type: parts[i + 1], content: [] });
|
|
||||||
i += 2; // Skip the control structure identifier
|
|
||||||
} else if (parts[i] === '/#' && ['for', 'if', 'while'].includes(stack[stack.length - 1].type)) {
|
|
||||||
// End of control structure
|
|
||||||
i++;
|
|
||||||
processStack();
|
|
||||||
} else if (stack.length > 0) {
|
|
||||||
// Inside a control structure
|
|
||||||
stack[stack.length - 1].content.push(parts[i]);
|
|
||||||
} else if (parts[i] !== undefined && parts[i] !== '#end') {
|
|
||||||
// Directly executable expression or static string
|
|
||||||
result.push(parts[i].startsWith('\n') ? '\n' + parts[i].trimStart() : parts[i].trimEnd());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// is a subset convertable to a function?
|
||||||
|
// can't we convert the whole tree to one function so its only one call instead of iteration on runtime?
|
||||||
|
|
||||||
|
result.stack.push(subset.stack);
|
||||||
|
result.length += subset.length;
|
||||||
|
idx += subset.length + token[1].length - 1;
|
||||||
|
} else {
|
||||||
|
if(endToken && endToken.startsWith(char) && content.substring(idx, idx + endToken.length)) {
|
||||||
|
// found end token
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(result.stack.length == 0 || Array.isArray(result.stack[result.stack.length - 1])) result.stack.push('');
|
||||||
|
result.stack[result.stack.length - 1] += char;
|
||||||
|
result.length += char.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static contentToArray(content) {
|
||||||
|
return Placekeeper.walkToEnd(content);
|
||||||
|
}
|
||||||
|
|
||||||
static variableRegex = new RegExp(/[\w\d]/, 'i');
|
static variableRegex = new RegExp(/[\w\d]/, 'i');
|
||||||
|
|
||||||
static parseVariables(content) {
|
static parseVariables(content) {
|
||||||
@@ -109,6 +84,8 @@ export default class Placekeeper {
|
|||||||
currentVar = null;
|
currentVar = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return variables;
|
||||||
}
|
}
|
||||||
|
|
||||||
static parse(content, sections) {
|
static parse(content, sections) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default class HtmlEngine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
mimeType: file.type || 'text/html',
|
||||||
parse: (data = null, options = {}) =>
|
parse: (data = null, options = {}) =>
|
||||||
this.parse(filepath, data, options),
|
this.parse(filepath, data, options),
|
||||||
};
|
};
|
||||||
@@ -50,17 +51,11 @@ export default class HtmlEngine {
|
|||||||
let parsed = Placekeeper.parse(view.content, view.sections);
|
let parsed = Placekeeper.parse(view.content, view.sections);
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
parsed = parsed.replace(
|
parsed = parsed.replace(
|
||||||
new RegExp(`{{${key}}}`, 'g'),
|
new RegExp(`{{\s?${key}\s?}}`, 'g'),
|
||||||
value.toString()
|
value.toString()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new Response(parsed, {
|
|
||||||
...(options || {}),
|
return parsed;
|
||||||
headers: {
|
|
||||||
...(options.headers || {}),
|
|
||||||
'Content-Type': view.mimeType,
|
|
||||||
'Content-Length': view.size,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class MarkdownEngine {
|
|||||||
['sections', sections],
|
['sections', sections],
|
||||||
['variables', variables],
|
['variables', variables],
|
||||||
['metadata', metadata],
|
['metadata', metadata],
|
||||||
['mimeType', file.type || 'text/html'],
|
['mimeType', file.type || 'text/markdown'],
|
||||||
['size', file.size],
|
['size', file.size],
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
@@ -42,6 +42,7 @@ export default class MarkdownEngine {
|
|||||||
console.log(filepath, this.#internalCache.get(filepath));
|
console.log(filepath, this.#internalCache.get(filepath));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
mimeType: file.type || 'text/markdown',
|
||||||
parse: (data = null, options = {}) =>
|
parse: (data = null, options = {}) =>
|
||||||
this.parse(filepath, data, options),
|
this.parse(filepath, data, options),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user