feat(server) layout handling; placekeeper wip
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { LayServer } from '../src';
|
||||
import {resolve} from 'path';
|
||||
import Html from '../src/Views/Html';
|
||||
import Markdown from '../src/Views/Markdown';
|
||||
|
||||
const APP_PORT = Bun.env.APP_PORT ?? Bun.env.PORT ?? 3000;
|
||||
|
||||
@@ -36,6 +37,7 @@ class InfoController {
|
||||
|
||||
const server = new LayServer({
|
||||
viewsPath: resolve(__dirname, 'views'),
|
||||
layout: 'blank.html',
|
||||
loaders: new Map([[
|
||||
'html', new Html()
|
||||
]]),
|
||||
@@ -43,7 +45,7 @@ const server = new LayServer({
|
||||
function Main(server) {
|
||||
server.get('/', async (req, res) => {
|
||||
return res.json({message: 'hello world'})
|
||||
}, { domain: 'api.tipedi.local' });
|
||||
}, { domain: 'example.layc.dev' });
|
||||
server.all('/data', (req, res) => {
|
||||
let respBody = `your ${req.method} request was well recieved`;
|
||||
switch (req.method) {
|
||||
@@ -86,6 +88,14 @@ server.get('/', async (req, res) => {
|
||||
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, () => {
|
||||
console.log(`server listen on ${APP_PORT}`);
|
||||
});
|
||||
|
||||
@@ -1,37 +1,96 @@
|
||||
import MimeTypeMap from './MimeTypeMap';
|
||||
|
||||
export default class LayResponse {
|
||||
routing = undefined;
|
||||
body;
|
||||
statusCode;
|
||||
headerList = new Map();
|
||||
mimeType;
|
||||
options = {
|
||||
#routing = undefined;
|
||||
#body = 'Not Implemented';
|
||||
#file = undefined;
|
||||
#redirectUrl = undefined;
|
||||
#statusCode = 502;
|
||||
#headerList = new Map();
|
||||
#mimeType = 'text/plain';
|
||||
#options = {
|
||||
disableFileCheck: false,
|
||||
};
|
||||
|
||||
constructor(routing) {
|
||||
this.routing = routing;
|
||||
this.body = 'Not Implemented';
|
||||
this.statusCode = 502;
|
||||
this.headerList = new Map();
|
||||
this.mimeType = 'text/plain';
|
||||
this.#routing = routing;
|
||||
}
|
||||
|
||||
async send(str) {
|
||||
this.type('text');
|
||||
this.body = str;
|
||||
type(key) {
|
||||
if (!MimeTypeMap.has(key)) {
|
||||
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 {
|
||||
const temp = Bun.file(str);
|
||||
const temp = Bun.file(arguments[0]);
|
||||
|
||||
if (await temp.exists()) {
|
||||
this.file = str;
|
||||
this.#file = temp;
|
||||
}
|
||||
} 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 = {}) {
|
||||
let { replacer, space } = options;
|
||||
if (!Array.isArray(replacer) && typeof replacer != 'function') {
|
||||
@@ -41,72 +100,30 @@ export default class LayResponse {
|
||||
space = 0;
|
||||
}
|
||||
this.type('json');
|
||||
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(),
|
||||
});
|
||||
this.#body = JSON.stringify(data, replacer, space);
|
||||
}
|
||||
|
||||
async render(path, data = {}) {
|
||||
const loader = await this.routing.getServer().load(path);
|
||||
const content = await loader.parse(data, {
|
||||
status: this.status(),
|
||||
headers: this.headers(),
|
||||
});
|
||||
const loader = await this.#routing.getServer().load(path);
|
||||
const content = await loader.parse(data);
|
||||
|
||||
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
|
||||
*/
|
||||
export default class LayServer extends BaseRouter {
|
||||
controllers = new Map();
|
||||
loaders = new Map();
|
||||
proc = null;
|
||||
publicFiles;
|
||||
routing = null;
|
||||
options = new Map([
|
||||
#controllers = new Map();
|
||||
#loaders = new Map();
|
||||
#proc = null;
|
||||
#publicFiles;
|
||||
#routing = null;
|
||||
#options = new Map([
|
||||
['layout', undefined],
|
||||
['loaders', undefined],
|
||||
['publicDir', undefined],
|
||||
['viewsPath', undefined],
|
||||
@@ -40,15 +41,15 @@ export default class LayServer extends BaseRouter {
|
||||
const optionKeys = Object.getOwnPropertyNames(options);
|
||||
|
||||
for (const key of optionKeys) {
|
||||
if (!this.options.has(key)) continue;
|
||||
if (!this.#options.has(key)) continue;
|
||||
if (key == 'loaders') continue;
|
||||
const valueType = typeof this.options.get(key);
|
||||
const valueType = typeof this.#options.get(key);
|
||||
if (
|
||||
typeof options[key] !== 'undefined' &&
|
||||
(typeof options[key] == valueType ||
|
||||
valueType == 'undefined')
|
||||
)
|
||||
this.options.set(key, options[key]);
|
||||
this.#options.set(key, options[key]);
|
||||
}
|
||||
|
||||
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) {
|
||||
this.routing.addRouter(options.router);
|
||||
this.#routing.addRouter(options.router);
|
||||
}
|
||||
|
||||
if (options.hasOwnProperty('controllers')) {
|
||||
@@ -72,13 +73,13 @@ export default class LayServer extends BaseRouter {
|
||||
|
||||
if (
|
||||
!options.publicFiles &&
|
||||
this.options.discoverPublicFiles &&
|
||||
this.options.publicDir
|
||||
this.#options.discoverPublicFiles &&
|
||||
this.#options.publicDir
|
||||
)
|
||||
this.indexPublicFiles(this.options.publicDir);
|
||||
this.indexPublicFiles(this.#options.publicDir);
|
||||
|
||||
if (!this.options.viewsEngine && this.options.viewsPath) {
|
||||
this.options.viewsEngine = new HtmlEngine();
|
||||
if (!this.#options.viewsEngine && this.#options.viewsPath) {
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
return this.options.get(key);
|
||||
return this.#options.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,11 +106,11 @@ export default class LayServer extends BaseRouter {
|
||||
throw new Error(
|
||||
'publicFiles option has to be an array or undefined'
|
||||
);
|
||||
if (!this.publicFiles || !Array.isArray(this.publicFiles))
|
||||
this.publicFiles = [];
|
||||
if (!this.#publicFiles || !Array.isArray(this.#publicFiles))
|
||||
this.#publicFiles = [];
|
||||
for (let path of filePaths) {
|
||||
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 (!controller?.prototype?.constructor?.name) {
|
||||
controller(this.routing);
|
||||
controller(this.#routing);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.controllers.set(
|
||||
this.#controllers.set(
|
||||
controller?.name,
|
||||
this.instantiateController(controller)
|
||||
);
|
||||
@@ -156,9 +157,9 @@ export default class LayServer extends BaseRouter {
|
||||
* @returns {Object} - The instantiated controller.
|
||||
*/
|
||||
instantiateController(controller) {
|
||||
this.routing.setNamespace(controller.namespace);
|
||||
const _temp = new controller(this.routing);
|
||||
if (controller.namespace) this.routing.setNamespace(null);
|
||||
this.#routing.setNamespace(controller.namespace);
|
||||
const _temp = new controller(this.#routing);
|
||||
if (controller.namespace) this.#routing.setNamespace(null);
|
||||
return _temp;
|
||||
}
|
||||
|
||||
@@ -181,10 +182,10 @@ export default class LayServer extends BaseRouter {
|
||||
|
||||
if (
|
||||
!url.pathname.endsWith('/') &&
|
||||
Array.isArray(this.publicFiles) &&
|
||||
this.publicFiles.length > 0
|
||||
Array.isArray(this.#publicFiles) &&
|
||||
this.#publicFiles.length > 0
|
||||
) {
|
||||
let path = this.publicFiles.find(
|
||||
let path = this.#publicFiles.find(
|
||||
(v) => url.pathname.substring(1) === v
|
||||
);
|
||||
if (path) {
|
||||
@@ -192,7 +193,7 @@ export default class LayServer extends BaseRouter {
|
||||
return new Response(
|
||||
Bun.file(
|
||||
resolvePath(
|
||||
this.options.get('publicDir') ??
|
||||
this.#options.get('publicDir') ??
|
||||
`${import.meta.dir}/public`,
|
||||
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);
|
||||
}
|
||||
|
||||
let resp = await this.routing.handle(req);
|
||||
let resp = await this.#routing.handle(req);
|
||||
if (resp instanceof Response) return resp;
|
||||
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.
|
||||
*/
|
||||
async getViewPath(filepath) {
|
||||
let path = this.options.get('viewsPath');
|
||||
let path = this.#options.get('viewsPath');
|
||||
if (!path.endsWith('/')) path += '/';
|
||||
|
||||
const filename = filepath.split('/').pop().toString();
|
||||
@@ -290,7 +291,7 @@ export default class LayServer extends BaseRouter {
|
||||
const fullFilePath = await this.getViewPath(filepath);
|
||||
if (!fullFilePath) return new DefaultLoader(filepath);
|
||||
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) {
|
||||
for (const keys of this.loaders.keys()) {
|
||||
@@ -302,7 +303,21 @@ export default class LayServer extends BaseRouter {
|
||||
return new DefaultLoader(filepath);
|
||||
}
|
||||
|
||||
async add(path, cb) {
|
||||
this.routing.add(...arguments)
|
||||
add(path, cb) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let currentNode = this.routes;
|
||||
let fallbackNode = currentNode.has('*') ? currentNode.get('*') : undefined;
|
||||
let paramsList = [];
|
||||
|
||||
if (path != '') {
|
||||
let segments = path.split('/');
|
||||
for (let index = 0; index < segments.length; index++) {
|
||||
const segment = segments[index];
|
||||
if (currentNode.has('*')) {
|
||||
fallbackNode = currentNode.get('*');
|
||||
}
|
||||
|
||||
if (currentNode.has(segment)) {
|
||||
currentNode = currentNode.get(segment);
|
||||
@@ -51,7 +55,7 @@ export default class Router extends BaseRouter {
|
||||
continue;
|
||||
}
|
||||
|
||||
currentNode = null;
|
||||
currentNode = fallbackNode ?? null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -98,21 +102,24 @@ export default class Router extends BaseRouter {
|
||||
|
||||
try {
|
||||
await reqContext.request.parseBody();
|
||||
const retVal = await handle.callback(
|
||||
let retVal = await handle.callback(
|
||||
reqContext.request,
|
||||
reqContext.response
|
||||
);
|
||||
if (typeof retVal != 'undefined') {
|
||||
if (retVal instanceof Response) {
|
||||
if (retVal instanceof Promise) {
|
||||
retVal = await retVal;
|
||||
} else if (retVal instanceof Response) {
|
||||
return retVal;
|
||||
} else if (typeof retVal == 'string') {
|
||||
reqContext.response.send(retVal);
|
||||
await reqContext.response.text(retVal);
|
||||
} else {
|
||||
reqContext.response.json(retVal);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
Logger(err);
|
||||
console.log(err);
|
||||
return new Response('Error', { status: 500 });
|
||||
}
|
||||
|
||||
@@ -123,10 +130,7 @@ export default class Router extends BaseRouter {
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(reqContext.response.body, {
|
||||
status: reqContext.response.status() || 200,
|
||||
headers: reqContext.response.headers() || {},
|
||||
});
|
||||
return await reqContext.response.build();
|
||||
}
|
||||
|
||||
add(route, callback) {
|
||||
|
||||
@@ -1,51 +1,16 @@
|
||||
import Logger from './Logger';
|
||||
|
||||
const TOKEN_PAIRS = [
|
||||
['{{','}}']
|
||||
]
|
||||
|
||||
export default class Placekeeper {
|
||||
static init(content) {
|
||||
const sections = Array();
|
||||
const variables = new Map();
|
||||
const currentSection = new Map();
|
||||
|
||||
for (let idx = 0; idx < content.length; idx++) {
|
||||
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));
|
||||
console.log('content to array', Placekeeper.contentToArray(content));
|
||||
|
||||
return {
|
||||
variables,
|
||||
@@ -53,37 +18,47 @@ export default class Placekeeper {
|
||||
};
|
||||
}
|
||||
|
||||
static contentToArray(content) {
|
||||
const parts = content.split(/\{\{([^}]+)\}\}|\{(#[a-zA-Z]+)\s*([^}]+)\}/);
|
||||
const result = [];
|
||||
let stack = [];
|
||||
function processStack() {
|
||||
const block = stack.pop();
|
||||
result.push(new Function('data', 'with(data){return `'+ block.content.join('') + '`;}'));
|
||||
}
|
||||
static walkToEnd(content, endToken = undefined) {
|
||||
let result = {
|
||||
stack: [],
|
||||
length: 0
|
||||
};
|
||||
for(let idx = 0; idx < content.length; idx++) {
|
||||
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
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (parts[i] === '#' && ['for', 'if', 'else', 'elseif', 'while'].includes(parts[i + 1])) {
|
||||
// Start of control structure
|
||||
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());
|
||||
}
|
||||
}
|
||||
if(token) {
|
||||
// found new start token walk to end:
|
||||
idx += token[0].length
|
||||
const subset = Placekeeper.walkToEnd(content.substring(idx), token[1]);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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 parseVariables(content) {
|
||||
@@ -109,6 +84,8 @@ export default class Placekeeper {
|
||||
currentVar = null;
|
||||
}
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
static parse(content, sections) {
|
||||
|
||||
@@ -29,6 +29,7 @@ export default class HtmlEngine {
|
||||
});
|
||||
|
||||
return {
|
||||
mimeType: file.type || 'text/html',
|
||||
parse: (data = null, options = {}) =>
|
||||
this.parse(filepath, data, options),
|
||||
};
|
||||
@@ -50,17 +51,11 @@ export default class HtmlEngine {
|
||||
let parsed = Placekeeper.parse(view.content, view.sections);
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
parsed = parsed.replace(
|
||||
new RegExp(`{{${key}}}`, 'g'),
|
||||
new RegExp(`{{\s?${key}\s?}}`, 'g'),
|
||||
value.toString()
|
||||
);
|
||||
}
|
||||
return new Response(parsed, {
|
||||
...(options || {}),
|
||||
headers: {
|
||||
...(options.headers || {}),
|
||||
'Content-Type': view.mimeType,
|
||||
'Content-Length': view.size,
|
||||
},
|
||||
});
|
||||
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class MarkdownEngine {
|
||||
['sections', sections],
|
||||
['variables', variables],
|
||||
['metadata', metadata],
|
||||
['mimeType', file.type || 'text/html'],
|
||||
['mimeType', file.type || 'text/markdown'],
|
||||
['size', file.size],
|
||||
])
|
||||
);
|
||||
@@ -42,6 +42,7 @@ export default class MarkdownEngine {
|
||||
console.log(filepath, this.#internalCache.get(filepath));
|
||||
|
||||
return {
|
||||
mimeType: file.type || 'text/markdown',
|
||||
parse: (data = null, options = {}) =>
|
||||
this.parse(filepath, data, options),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user