Source: xxe/view/RemarkIndicator.js

/**
 * A <code>&lt;?xxe-remark?&gt;</code> indicator.
 */
class RemarkIndicator extends HTMLElement {
    constructor() {
        super();

        let shadow = this.attachShadow({mode: "open"});
        shadow.appendChild(RemarkIndicator.TEMPLATE.content.cloneNode(true));
        this._pole = shadow.lastElementChild;
        this._flag = this._pole.previousElementSibling;

        this.addEventListener("dblclick", this.editRemark.bind(this));
        
        this._docView = null;
        this._info = null;
    }

    editRemark(event) {
        event.preventDefault();
        event.stopPropagation();
        
        let view = NodeView.lookupView(this);
        if (view === null) {
            // Should not happen.
            return;
        }
        
        this._docView.selectNode(view)
            .then((selected) => {
                if (selected) {
                    this._docView.executeCommand(EXECUTE_NORMAL,
                                                 "remark", null);
                }
            });
    }
    
    // -----------------------------------------------------------------------
    // Custom element
    // -----------------------------------------------------------------------

    connectedCallback() {
        this._docView = DOMUtil.lookupAncestorByTag(this, "xxe-document-view");
        if (this._docView === null) {
            // Should not happen.
            return;
        }

        this._info = null;
        let infoValue = this.getAttribute("info");
        if (infoValue) {
            try {
                this._info = JSON.parse(infoValue);
            } catch (error) {
                console.error(`"${infoValue}", invalid "info" attribute`);
            }
        }
        if (this._info === null) {
            this._info = { "editor": "??", "bg": "#D3D3D3", "fg": "#808080",
                           "date": Date.now(), "text": "" };
        }

        if (this._info.text) {
            let tooltip = RemarkIndicator.remarkHeader(this._info.editor,
                                                       this._info.date);
            let text = this._info.text;
            if (text.length > 200) {
                text = text.substring(0, 199) + "\u2026";
            }
            tooltip += "\n" + text;
            if (Array.isArray(this._info.replies)) {
                const replyCount = this._info.replies.length;
                if (replyCount > 0) {
                    tooltip += "\n\n+ " +
                        ((replyCount === 1)?
                         `${replyCount} reply` : `${replyCount} replies`);
                }
            }
            
            this.setAttribute("title", tooltip);
        }

        this._flag.textContent = RemarkIndicator.getInitials(this._info.editor);
        this._flag.setAttribute("style",
        `border-color: ${this._info.fg}; background-color: ${this._info.bg};`);
        
        this._pole.setAttribute("style", `background-color: ${this._info.fg};`);
    }

    // -----------------------------------------------------------------------
    // Utilities
    // -----------------------------------------------------------------------

    static getInitials(editor) {
        if (!editor) {
            return "??";
        } else {
            return editor.substring(0, Math.min(2, editor.length));
        }
    }

    static remarkHeader(editor, date) {
        let ago = null;
        
        const delta = Date.now() - date;
        if (delta >= 0) {
            const s = Math.floor(delta / 1000);
            const m = Math.floor(s / 60);
            const h = Math.floor(m / 60);
            const d = Math.floor(h / 24);
            
            if (d >= 1) {
                if (d < 15) {
                    ago = `${editor}, ${d}d ago:`;
                }
            } else {
                if (h >= 1) {
                    ago = `${editor}, ${h}h ago:`;
                } else {
                    if (m >= 1) {
                        ago = `${editor}, ${m}m ago:`;
                    } else {
                        if (s >= 1) {
                            ago = `${editor}, ${s}s ago:`;
                        } else {
                            ago = `${editor}, now:`;
                        }
                    }
                }
            }
        }

        if (ago !== null) {
            return ago;
        } else {
            return `On ${(new Date(date)).toLocaleString()}, ${editor} wrote:`;
        }
    }
}

RemarkIndicator.TEMPLATE = document.createElement("template");
RemarkIndicator.TEMPLATE.innerHTML = `
<style>
:host {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    font-family: sans-serif;
    font-variant: normal;
    font-size: smaller;
    font-weight: bold;
    font-style: normal;
    text-decoration: none;
    color: var(--xui-fg, black);
    vertical-align: calc(0.2em + 2px + 0.8em + 0.25em); /*Extra 0.25em*/
}

.flag {
    flex: none;
    padding: 0.2em 0.4em;
    border: 2px solid black;
    border-radius: 0.4em;
}

.pole {
    flex: none;
    width: 2px;
    height: 0.8em;
    background-color: black;
}
</style><div class="flag"></div><div class="pole"></div>`;

window.customElements.define("xxe-remark-indicator", RemarkIndicator);