/**
* @module decorator
*/
import {isString, checkType, getFullDocPath} from "./utils.js";
/**
* @description Layout decorator.
*/
class Decorator {
/**
* @param {Logger} logger
* @param {Compiler} compiler
* @return {Decorator}
*/
constructor(logger, compiler) {
this.logger = logger;
this.compiler = compiler;
this._ = new Map();
}
/**
* @callback decorateCallback
* @param {Object} ctx
* @return {String}
*/
/**
* @description Register a decorate function.
* @param {String} layout
* @param {decorateCallback|String} fn If string, will call Compiler on this
* filename while decorating.
* @param {Object} [ctx=null] Custom context if you want to pass something to
* this decorator. This is deprecated and you should use helper function with
* layout filter.
*/
register(layout, fn, ctx = null) {
checkType(layout, "layout", "String");
checkType(fn, "fn", "Function", "String");
if (ctx != null) {
this.logger.warn("Hikaru suggests you to pass context for specific layouts by using helper function with layout filter because passing context while registering decorator function is deprecated!");
}
this._.set(layout, {layout, fn, ctx});
}
/**
* @description Unregister a decorate function.
* @param {String} layout
*/
unregister(layout) {
checkType(layout, "layout", "String");
this._.delete(layout);
}
/**
* @description Decorate input file with layout.
* @param {File} file
* @return {String}
*/
async decorate(file, ctx) {
const layout = this.getFileLayout(file);
if (layout != null) {
this.logger.debug(`Hikaru is decorating \`${
this.logger.cyan(getFullDocPath(file))
}\` with layout \`${
this.logger.blue(layout)
}\`...`);
const handler = this._.get(layout);
if (handler == null) {
throw new Error(`Decorator for \`${layout}\` is not registered.`);
}
let fn = handler["fn"];
if (isString(fn)) {
fn = await this.compiler.compile(handler["fn"]);
}
// We support two methods to access file properties, first the file
// properties will be merged with context properties, so you could
// directly access them via property names, and then you could also access
// file properties via `file` object, this is recommended because it is
// easier to prevent the single `file` property name from conflicting and
// it is always accessible.
return fn(Object.assign(file, handler["ctx"], ctx, {"file": file}));
}
return file["content"];
}
/**
* @description List registered layout.
* @return {String[]}
*/
list() {
return [...this._.keys()];
}
/**
* @private
* @description Get simpified layout for file.
* @param {File} file
* @return {String}
*/
getFileLayout(file) {
if (file["layout"] == null) {
return null;
}
if (!this._.has(file["layout"])) {
return "page";
}
return file["layout"];
}
}
export default Decorator;