UNPKG

@teipublisher/pb-components

Version:
139 lines (121 loc) 3.99 kB
import { LitElement, html, css } from 'lit-element'; import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; import { pbMixin, waitOnce } from './pb-mixin.js'; import * as marked from 'marked/lib/marked.js'; import './pb-code-highlight.js'; const renderer = new window.marked.Renderer(); renderer.code = function code(code, infostring, escaped) { return `<pb-code-highlight language="${infostring}" line-numbers> <template>${code}</template> </pb-code-highlight>`; }; window.marked.setOptions({ renderer }); function removeIndent(input) { const indents = input.match(/^[^\S]*(?=\S)/gm); if (!indents || !indents[0].length) return input; indents.sort((a, b) => a.length - b.length); if (!indents[0].length) return input; return input.replace(RegExp('^' + indents[0], 'gm'), ''); } /** * A component to render markdown. Content to render may either * * 1. be specified via the `content` property * 2. included in the body of the element * 3. loaded from an external URL * * Using option 2, if the markdown includes embedded HTML, make sure to wrap * the content into an `template` HTML element to prevent the browser from interpreting * it. * * Using option 3, you can either specify an absolute or relative URL. Relative URLs * will be interpreted relative to the endpoint set by `pb-page`. */ export class PbMarkdown extends pbMixin(LitElement) { static get properties() { return { /** * The markdown content to be rendered. If undefined, * markdown will be taken from the content of the element * or loaded from the specified URL. */ content: { type: String, }, /** * An absolute or relative URL to load the markdown from. */ url: { type: String, }, ...super.properties, }; } constructor() { super(); this._url = null; } set url(value) { this._url = value; waitOnce("pb-page-ready", (options) => { this._load(options.endpoint); }); } connectedCallback() { super.connectedCallback(); this.style.display = "block"; if (!this.content) { const content = document.createElement("div"); for (let i = 0; i < this.childNodes.length; i++) { const node = this.childNodes[i]; content.appendChild( document.importNode(node.content || node, true) ); } this.content = removeIndent(content.innerHTML); } this.subscribeTo("pb-zoom", (ev) => { this.zoom(ev.detail.direction); }); } _load(server) { const url = this.toAbsoluteURL(this._url, server); fetch(url, { credentials: "same-origin" }) .then((response) => response.text()) .catch(() => { console.error( "<pb-markdown> failed to load content from %s", url.toString() ); return Promise.resolve(this.content); }) .then((text) => { this.content = text; }); } createRenderRoot() { return this; } render() { if (!this.content) { return null; } return html`<div>${unsafeHTML(window.marked(this.content))}</div>`; } zoom(direction) { const fontSize = window .getComputedStyle(this) .getPropertyValue("font-size"); const size = parseInt(fontSize.replace(/^(\d+)px/, "$1")); if (direction === "in") { this.style.fontSize = size + 1 + "px"; } else { this.style.fontSize = size - 1 + "px"; } } } customElements.define('pb-markdown', PbMarkdown);