<template>
    <link rel="stylesheet" href="/static/editor.md/css/editormd.css">
    <!-- rewrite Markdown CSS(Cmd Markdown Style) from https://github.com/wakaryry/editorMd -->
    <link rel="stylesheet" href="/static/CmdMarkdown/custom.css">

    <div :id="editorID"></div>
</template>

<script>
import scriptjs from 'scriptjs'

const defaultEditorValue = null;
let editorClient = defaultEditorValue;

export default {
    name: 'MarkdownEditor',
    props: {
        editorID: {
            type: String,
            required: false,
            default: 'markdown-editor'
        },
        // 加载该组件时，是否自动执行 initEditor
        // 如果为 false，则将初始化的时机和主动权交给父组件
        autoInit: {
            type: Boolean,
            required: false,
            default: true
        },
        // 父组件可设置编辑器初始 markdown 代码
        markdownCode: {
            type: String,
            required: false,
            default: ''
        },
        // 设置宽度，默认 100%
        width: {
            type: String,
            required: false,
            default: '100%'
        },
        // 设置高度，默认固定值
        height: {
            type: String,
            required: false,
            default: '720px'
        },
        // 是否支持图片本地上传
        imageUpload: {
            type: Boolean,
            required: false,
            default: false
        },
        // 提供图片上传的后端接口
        imageUploadURL: {
            type: String,
            required: false,
            default: ''
        },
        // 图片上传时携带的 token
        imageUploadToken: {
            type: String,
            required: false,
            default: ''
        },
        // 当 image-dialog 插件更新 imageUploadURL 时触发
        imageUploadURLChange: {
            type: Function,
            required: false,
            default: (newURL) => {
                console.log("new imageUploadURL: " + newURL)
            }
        },
        // 新增或覆盖原有工具栏。相关属性如下:
        // - name: 必须定义. 如果是 | 则仅做分隔符；如果和原有 toolbar name 冲突则覆盖原有 toolbar
        // - icon: font-awesome icon. 需确保在 editormd.css 中已定义
        // - title: 定义悬停该 toolbar 时浮现的文本
        // - handler: 定义点击该 toolbar 后触发的操作
        // - shortcut: 定义该 toolbar 绑定的快捷键
        // - nofoucs: 默认为 false；如果为 true 则点击该 toolbar 后不会触发 cm.focus() 事件
        appendToolbar: {
            type: Array,
            required: false,
            default: [],
        },
        // 编辑器内绑定快捷键事件，会覆盖原有快捷键事件
        shortcutFunction: {
            type: Object,
            required: false,
            default: {},
        },
        // 编辑器加载完成后，执行该函数
        onload: {
            type: Function,
            required: false,
            default: () => { }
        },
        // 全屏编辑器时，执行该函数
        onFullScreen: {
            type: Function,
            required: false,
            default: () => { }
        },
        // 退出全屏时，执行该函数
        onFullScreenExit: {
            type: Function,
            required: false,
            default: () => { }
        }
    },
    data() {
        return {
            loadingCompleted: false,
            editormdPreview: null,
        }
    },
    mounted() {
        if (this.autoInit) {
            this.initEditor()
            // 设置延迟初始化 markdown 编辑器。因为只会初始化一次，需要等待数据加载完成之后再初始化
            // setTimeout(() => {
            //     this.initEditor()
            // }, 300)
        }
    },
    methods: {
        fetchScript(url) {
            return new Promise((resolve) => {
                scriptjs(url, () => {
                    resolve()
                })
            })
        },
        initEditor() {
            (async () => {
                await this.fetchScript('/static/jquery/jquery.min.js')
                await this.fetchScript('/static/editor.md/editormd.js')
                // await this.fetchScript('/static/editor.md/editorrmd.amd.js')
                // 加载完成以上 JS 资源之后，再加载编辑器
                this.$nextTick(() => {
                    const vm = this
                    const editor = window.editormd(vm.editorID, {
                        // 初始化内容
                        markdown: vm.markdownCode,
                        // 定义宽度和高度
                        width: vm.width,
                        height: vm.height,

                        path: '/static/editor.md/lib/',
                        emoji: true,
                        codeFold: true,
                        searchReplace: true,
                        saveHTMLToTextarea: true, // 保存 HTML 到 Textarea
                        htmlDecode: 'style,script,iframe|on*', // 开启 HTML 标签解析，为了安全性，默认不开启
                        tocm: true, // Using [TOCM]
                        tex: true, // 开启科学公式TeX语言支持，默认关闭
                        plantuml: true, // 开启 plantuml 解析，默认关闭
                        flowChart: true, // 开启流程图支持，默认关闭
                        sequenceDiagram: true, // 开启时序/序列图支持，默认关闭
                        taskList: true,

                        // 定义图片本地上传的功能
                        imageUpload: vm.imageUpload, // 是否支持图片本地上传。若不提供 imageUploadURL 则需要用户自行设定
                        imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "ico"],
                        imageUploadURL: vm.imageUploadURL, // 可用于上传图片的服务端接口
                        imageUploadToken: vm.imageUploadToken,
                        imageUploadURLChange: vm.imageUploadURLChange,

                        // 可分区域定制样式主题
                        // theme: (localStorage.theme) ? localStorage.theme : "dark",
                        // editorTheme: (localStorage.editorTheme) ? localStorage.editorThheme : "3024-night",
                        // previewTheme: (localStorage.previewTheme) ? localStorage.previewTheme : "default",

                        // 给工具栏补充工具事件
                        appendToolbar: vm.appendToolbar,

                        // 绑定快捷键。如果冲突，会覆盖其他相同快捷键事件
                        shortcutFunction: vm.shortcutFunction,

                        // 全屏
                        onfullscreen: function () {
                            vm.onFullScreen()
                        },

                        // 退出全屏
                        onfullscreenExit: function () {
                            vm.onFullScreenExit()
                        },

                        // 监听更新，更新父组件值
                        onchange: function () {
                            console.log("For some reason, the onchange event doesn't work here.")
                        },

                        onload: function () {
                            // 将 editor 变量赋予 editorClient，便于后续调用 API
                            editorClient = this
                            vm.loadingCompleted = true
                            vm.editormdPreview = $('#' + vm.editorID + ' .editormd-preview-container')[0]
                            // 执行父组件传递的 callback 方法
                            vm.onload()

                            // 监听，改变父组件的值
                            editor.on('change', function () {
                                vm.$emit('update:markdownCode', this.getMarkdown())
                            })

                            // 加载ctrl+v粘贴图片插件
                            // window.editormd.loadPlugin('/static/editor.md/plugins/image-handle-paste/image-handle-paste', function() {
                            //     editor.imagePaste()
                            // })
                        }
                    })
                })
            })()
        },
        isEditorLoadingCompleted() {
            return editorClient != defaultEditorValue && this.loadingCompleted
        },

        // 以下是 editor.md 对外暴露的 API
        resetHeight(height) {
            if (!this.isEditorLoadingCompleted()) {
                return false
            }
            editorClient.height(height)
            return true
        },
        resetWidth(width) {
            if (!this.isEditorLoadingCompleted()) {
                return false
            }
            editorClient.width(width)
            return true
        },
        setMarkdownCode(newMarkdownCode) {
            if (!this.isEditorLoadingCompleted()) {
                return false
            }
            if (newMarkdownCode.length == 0) {
                editorClient.clear()
            } else {
                editorClient.setMarkdown(newMarkdownCode)
            }
            return true
        },
        getMarkdownCode() {
            if (!this.isEditorLoadingCompleted()) {
                return ""
            }
            return editorClient.getMarkdown()
        },
        getPreviewedHTML() {
            if (!this.isEditorLoadingCompleted()) {
                return ""
            }
            return editorClient.getPreviewedHTML()
        },
        getTableOfContents() {
            return generateTableOfContents(this.editormdPreview);
        },
    }
}

/**
 * 获取一篇文档的章节标题的目录
 * 
 * 入参示例：
 * <div>
 *     <h1>1</h1>
 *     <h2>1.1</h2>
 *     <h1>2</h1>
 *     <h2>2.1</h2>
 * </div>
 * 
 * 输出示例：
 * [{
 *     "name": "1",
 *     "nodes": [{
 *         "name": "1.1",
 *         "nodes": []
 *     }]
 * },
 * {
 *     "name": "2",
 *     "nodes": [{
 *         "name": "2.1",
 *         "nodes": []
 *     }]
 * }]
 * 
 * @param   {Element} element   文档的 document 对象
 * @returns {Array}   tocObject 目录列表（Table of Contents）
 */
function generateTableOfContents(element) {
    // 该变量用于存储文章目录节点
    let tableOfContents = {
        name: "",
        parent: null,
        nodes: [],
    };

    // 遍历得到所有直接子元素中的 <h> 标签
    const hTags = element.querySelectorAll(":scope > h1, :scope > h2, :scope > h3, :scope > h4, :scope > h5, :scope > h6");

    let lastLevel = 0;
    let lastNode = tableOfContents;
    // 遍历文档内所有 H 标签
    hTags.forEach(hTag => {
        // currentLevel 表示 H 标签后的数字，比如 H1 标签，那么 currentLevel 为 1
        let currentLevel = hTag.tagName[1];
        // headTitle 则是 H 标签的文本，将用于在目录中显示
        let headTitle = hTag.innerText;
        lastNode = generateNodeOfTOC(lastNode, lastLevel, currentLevel, headTitle);
        lastLevel = currentLevel;
    });

    // 删除 node 节点中的 parent 字段，避免循环嵌套（circular structure）问题
    deleteParentAssociation(tableOfContents);
    return tableOfContents.nodes;
}
function generateNodeOfTOC(lastNode, lastLevel, currentLevel, headTitle) {
    // 需要找到刚好比当前节点大一级的 lastNode
    if (currentLevel > lastLevel) {
        // 如果当前 H 标题的级别小于上一个 H 标题（数字越小，级别越大）
        let diff = currentLevel - lastLevel;
        for (let i = 0; i < diff - 1; i++) {
            let node = {
                name: "",
                nodes: [],
                parent: lastNode,
            }
            lastNode.nodes.push(node);
            lastNode = node;
        }
    } else {
        // 如果当前 H 标题的级别大于等于上一个 H 标题（数字越小，级别越大）
        let diff = lastLevel - currentLevel;
        for (let i = 0; i <= diff; i++) {
            lastNode = lastNode.parent;
        }
    }

    let currentNode = {
        name: headTitle,
        nodes: [],
        parent: lastNode,
    }
    lastNode.nodes.push(currentNode);
    return currentNode;
}
function deleteParentAssociation(node) {
    delete node.parent;
    node.nodes.forEach(n => {
        deleteParentAssociation(n);
    })
}
</script>

<style scoped></style>