@nent/core
Version:
291 lines (290 loc) • 8.8 kB
JavaScript
/*!
* NENT 2022
*/
import { Component, Element, forceUpdate, h, Host, Prop, State, } from '@stencil/core';
import { eventBus } from '../../services/actions';
import { commonState, onCommonStateChange, } from '../../services/common';
import { debugIf, error } from '../../services/common/logging';
import { replaceHtmlInElement } from '../../services/content/elements';
import { resolveRemoteContent, resolveSrc, } from '../../services/content/remote';
import { resolveChildElementXAttributes } from '../../services/data/elements';
import { evaluatePredicate } from '../../services/data/expressions';
import { DATA_EVENTS } from '../../services/data/interfaces';
import { filterData } from '../../services/data/jsonata.worker';
import { resolveTokens } from '../../services/data/tokens';
import { dedent } from '../n-content/services/utils';
import { routingState } from '../n-views/services/state';
import { renderMarkdown } from './services/remarkable.worker';
/**
* This element converts markdown text to HTML. It can render
* from an inline-template or from a remote source.
*
* @system content
* @extension data
* @extension elements
*/
export class ContentMarkdown {
constructor() {
this.contentClass = 'rendered-content';
this.renderCache = {};
this.location = routingState.location;
this.contentElement = null;
/**
* Cross Origin Mode
*/
this.mode = 'cors';
/**
* Before rendering HTML, replace any data-tokens with their
* resolved values. This also commands this element to
* re-render it's HTML for data-changes. This can affect
* performance.
*/
this.resolveTokens = false;
/**
* If set, disables auto-rendering of this instance.
* To fetch the contents change to false or remove
* attribute.
*/
this.deferLoad = false;
/**
* Force render with data & route changes.
*/
this.noCache = false;
}
async getContentKey() {
return this.src
? await resolveSrc(this.src)
: this.childScript
? 'script'
: null;
}
get canCache() {
return this.noCache === false && this.resolveTokens === false;
}
get childScript() {
return this.el.querySelector('script');
}
async componentWillLoad() {
if (this.resolveTokens || this.when != undefined) {
if (commonState.dataEnabled) {
this.subscribeToDataEvents();
}
else {
this.dataSubscription = onCommonStateChange('dataEnabled', enabled => {
if (enabled) {
this.subscribeToDataEvents();
this.dataSubscription();
}
});
}
}
}
subscribeToDataEvents() {
this.dataSubscription = eventBus.on(DATA_EVENTS.DataChanged, () => {
forceUpdate(this.el);
});
}
async componentWillRender() {
let shouldRender = !this.deferLoad;
if (shouldRender && this.when)
shouldRender = await evaluatePredicate(this.when);
if (shouldRender) {
if (this.contentElement && this.canCache)
return;
this.contentElement = await this.resolveContentElement();
}
else {
this.contentElement = null;
}
}
async resolveContentElement() {
var _a;
const key = await this.getContentKey();
if (key && this.renderCache[key])
return this.renderCache[key];
const content = this.src
? await this.getContentFromSrc()
: await this.getContentFromScript();
if (content == null)
return null;
const div = document.createElement('div');
div.innerHTML = (await renderMarkdown(content)) || '';
div.className = this.contentClass;
if (commonState.elementsEnabled)
resolveChildElementXAttributes(div);
(_a = routingState.router) === null || _a === void 0 ? void 0 : _a.captureInnerLinks(div);
this.highlight(div);
if (key && this.canCache)
this.renderCache[key] = div;
return div;
}
async getContentFromSrc() {
try {
let content = await resolveRemoteContent(window, this.src, this.mode, this.resolveTokens);
if (content && this.json) {
debugIf(commonState.debug, `n-content-markdown: filtering: ${this.json}`);
const data = JSON.parse(content);
content = await filterData(this.json, data);
}
return content;
}
catch (err) {
error(`n-content-markdown: unable to retrieve content from ${this.src}. ${err}`);
return null;
}
}
async getContentFromScript() {
const element = this.childScript;
if (!(element === null || element === void 0 ? void 0 : element.textContent))
return null;
let content = dedent(element.textContent);
if (this.resolveTokens)
content = await resolveTokens(content);
return content;
}
highlight(container) {
const win = window;
const prism = win.Prism;
if (prism === null || prism === void 0 ? void 0 : prism.highlightAllUnder) {
prism.highlightAllUnder(container);
}
}
render() {
replaceHtmlInElement(this.el, `.${this.contentClass}`, this.contentElement);
return h(Host, { hidden: this.contentElement == null });
}
disconnectedCallback() {
var _a;
(_a = this.dataSubscription) === null || _a === void 0 ? void 0 : _a.call(this);
}
static get is() { return "n-content-markdown"; }
static get properties() { return {
"src": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Remote Template URL"
},
"attribute": "src",
"reflect": false
},
"mode": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'cors' | 'navigate' | 'no-cors' | 'same-origin'",
"resolved": "\"cors\" | \"navigate\" | \"no-cors\" | \"same-origin\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Cross Origin Mode"
},
"attribute": "mode",
"reflect": false,
"defaultValue": "'cors'"
},
"resolveTokens": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Before rendering HTML, replace any data-tokens with their\nresolved values. This also commands this element to\nre-render it's HTML for data-changes. This can affect\nperformance."
},
"attribute": "resolve-tokens",
"reflect": false,
"defaultValue": "false"
},
"deferLoad": {
"type": "boolean",
"mutable": true,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "If set, disables auto-rendering of this instance.\nTo fetch the contents change to false or remove\nattribute."
},
"attribute": "defer-load",
"reflect": false,
"defaultValue": "false"
},
"when": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "A data-token predicate to advise this element when\nto render (useful if used in a dynamic route or if\ntokens are used in the 'src' attribute)"
},
"attribute": "when",
"reflect": false
},
"noCache": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Force render with data & route changes."
},
"attribute": "no-cache",
"reflect": false,
"defaultValue": "false"
},
"json": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The JSONata expression to select the markdown from a json response.\nsee <https://try.jsonata.org> for more info."
},
"attribute": "json",
"reflect": false
}
}; }
static get states() { return {
"location": {}
}; }
static get elementRef() { return "el"; }
}