Home Reference Source

js/template/MarkdownTemplate.js

import MarkdownViewController from '~/controllers/MarkdownViewController';
import Template from '~/template/Template';

import { fromEvent } from 'rxjs';
import { map, share, startWith } from 'rxjs/operators';

const minHeight = 16 * 8; // line-height * lines
const reflowTargets = new Set();

window.addEventListener("resize", () => {
    for (const target of reflowTargets) {
        target.reflow();
    }
})

/**
 * Markdown editor
 * @implements {InputInterface}
 */
export default class MarkdownTemplate extends Template {
    /**
     * Markdown editor. Pass opts as an object
     * @param {?string} placeholder The placeholder
     * @param {?(MarkdownControl[])} controls - If provided use these controls
     * @param {boolean} [shadow=true] - If to show shadow
     * @param {boolean} [autoresize=false] - If to autoresize
     */
    constructor({ placeholder = "", controls, hasShadow = true, autoResize = false } = {}) {
        const textarea = <textarea placeholder={placeholder} class="markdown text-base"></textarea>;

        super(
            <div class="markdown-wrapper">{textarea}</div>
        );

        this._observeValue = fromEvent(textarea, 'input')
            .pipe(
                map(event => event.target.value),
                startWith(""),
                share());

        this.defineLinkedInput('value', textarea);
        this.defineLinkedClass('hasShadow', '!markdown-wrapper--shadow-none')
        /** @type {Boolean} */
        this.hasShadow = hasShadow;

        /** @type {MarkdownViewController} */
        this.controller = new MarkdownViewController(textarea, controls);

        this._autoresize = false;
        this._textarea = textarea;
        this._onInput = null;

        this.autoresize = autoResize;
    }

    // MARK: - InputInterface
    /** @override */
    observeValue() {
        return this._observeValue;
    }

    /** @override */
    get userInput() { return this._textarea }

    /** @type {boolean} */
    set autoresize(shouldAutoresize) {
        if (shouldAutoresize) {
            this._autoresize = true;
            this._onInput = () => { this.reflow() };
            this._textarea.addEventListener("input", this._onInput);
        } else {
            this._autoresize = false;
            if (this._onInput) {
                this._textarea.removeEventListener("input", this._onInput);
                this._onInput = null;
            }
        }
    }

    /** @override */
    didLoad() {
        this.reflow();
    }

    /**
     * Re-determines dimensions etc.
     */
    reflow() {
        if (this._autoresize) {
            this._textarea.style.height = "";
            this._textarea.style.height = `${Math.max(this._textarea.scrollHeight, minHeight)}px`;
        }
    }
}