ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

解决自媒体一键多平台发布,从零开发Markdown编辑器(一)

2022-09-12 15:32:38  阅读:266  来源: 互联网

标签:Markdown textContent 一键 else 编辑器 html prevAnalysisResult analysisResult var


前言

在这个人人都是自媒体的时代,为了扩大个人影响力同时预防文章被盗版至其他平台,多平台发布文章就成了创作者们的一大痛点,为了解决这一痛点就需要将文章的编辑到发布无缝集成。
现在要实现这一功能,开发一个完全可控的Markdown编辑器就是第一步。
本文源码已上传Github:Github hxsfx MarkdownEditor

界面草图

image

技术选型

考虑到编辑器解析渲染放在前端更合适,采用了HTML+JS+CSS实现Markdown编辑器模块。

功能演示及代码分享

各位小伙伴可以访问在线演示地址:https://md.hxsfx.com/

1、标题语法

  • 功能演示
    image
  • 代码分享
var h4_start = "#### ";
var h3_start = "### ";
var h2_start = "## ";
var h1_start = "# ";
if (textContent.startsWith(h4_start)) {
    html = textContent.substring(h4_start.length, textContent.length);
    tagName = "h4";
}//四级标题
else if (textContent.startsWith(h3_start)) {
    html = textContent.substring(h3_start.length, textContent.length);
    tagName = "h3";
}//三级标题
else if (textContent.startsWith(h2_start)) {
    html = textContent.substring(h2_start.length, textContent.length);
    tagName = "h2";
}//二级标题
else if (textContent.startsWith(h1_start)) {
    html = textContent.substring(h1_start.length, textContent.length);
    tagName = "h1";
}//一级标题

2、强调语法

  • 功能演示
    image
  • 代码分享
//提取强调语法
function ExtractEmphasisGrammar(html) {
    //粗斜体
    var html = html.replace(/\*\*\*.*?\*\*\*/g, function (strongAndem_val) {
        var _strongAndem_val = strongAndem_val.substring(3, strongAndem_val.length - 3)
        return CreatePreviewSectionHTML("strong,em", _strongAndem_val);
    });
    //粗体
    var html = html.replace(/\*\*.*?\*\*/g, function (strong_val) {
        var _strong_val = strong_val.substring(2, strong_val.length - 2);
        return CreatePreviewSectionHTML("strong", _strong_val);
    });
    //斜体
    var html = html.replace(/\*.*?\*/g, function (em_val) {
        var _em_val = em_val.substring(1, em_val.length - 1);
        return CreatePreviewSectionHTML("em", _em_val);
    });
    return html;
}
//根据标签和内部内容生成预览区域内行块html
function CreatePreviewSectionHTML(tagName, innerHTML) {
    var html = innerHTML;
    if (tagName == "code") {
        html = html.replace(/(\s)/g, " ");//.replace("/ /g"," ");
    }//将空格替换为转义字符防止多个空格在html显示为一个

    if (tagName == "" || tagName == null || tagName == undefined) {
    } else if (tagName == "hr") {
        html = "<hr>";
    } else {
        var start_tagName = "";
        var end_tagName = "";
        var tagNameSplit = tagName.split(",");
        for (var i = 0; i < tagNameSplit.length; i++) {
            start_tagName += "<" + tagNameSplit[i] + ">";
            end_tagName = end_tagName + "</" + tagNameSplit[i] + ">";
        }
        html = start_tagName + html + end_tagName;
    }
    return html;
}

3、引用语法

  • 功能演示
    image
  • 代码分享
var blockquote_start = "&gt;";
if (textContent.startsWith(blockquote_start)) {
    isBlockquote = true;
    html = textContent.substring(blockquote_start.length, textContent.length);
    tagName = "blockquote";
}//引用

4、列表语法

  • 功能演示
    image

  • 代码分享

var olli_pattern = /^ {0,3}[1-9]*\. /; //有序列表正则表达式
var ulli_pattern = /^[ ]{0,3}(\* |- |\+ )/; //有序列表正则表达式

if (olli_pattern.test(textContent)) {
    //有序列表项
    if (textContent.startsWith(" ")) {
        isOL2 = true;
    } else {
        isOL = true;
    }
    html = textContent.replace(olli_pattern, "");
    tagName = "ol";
}
else if (ulli_pattern.test(textContent)) {
    //无序列表项
    if (textContent.startsWith(" ")) {
        isUL2 = true;
    } else {
        isUL = true;
    }
    html = textContent.replace(ulli_pattern, "");
    tagName = "ul";
}
//提取列表语法
function ExtractList(analysisResult, prevAnalysisResult) {
    var isExtractTable = true;
    if (prevAnalysisResult == null || prevAnalysisResult.ListInfo == null) {
        isExtractTable = CreateListInfo(analysisResult, isExtractTable);
    }//没有上一行 或者 上一行不是列表
    else {
        var liHTML = analysisResult.AnalysisHTML;
        if ((prevAnalysisResult.IsOL && analysisResult.IsOL) ||
            (prevAnalysisResult.IsUL && analysisResult.IsUL)) {
            //接着上一行继续
            analysisResult.ListInfo = prevAnalysisResult.ListInfo;
            analysisResult.ListInfo.LiInfoArray.push({ LiHtml: liHTML, LiInfoArray: [] });
            analysisResult.ListInfo.IsMergePrevHTML = true;
        }//同为一级标题且标签相同
        else if ((prevAnalysisResult.IsOL && analysisResult.IsUL) ||
            (prevAnalysisResult.IsUL && analysisResult.IsOL)) {
            isExtractTable = CreateListInfo(analysisResult, isExtractTable);
        }
        else if ((prevAnalysisResult.IsOL && (analysisResult.IsOL2 || analysisResult.IsUL2)) ||
            (prevAnalysisResult.IsUL && (analysisResult.IsOL2 || analysisResult.IsUL2))) {
            var currentFisrtLevelLiInfoArray = prevAnalysisResult.ListInfo.LiInfoArray.slice(-1)[0];
            var secondLevelLiInfoArray = currentFisrtLevelLiInfoArray.LiInfoArray;
            var isFindPeer = false;
            for (var i = 0; i < secondLevelLiInfoArray.length; i++) {
                var _secondLevelLiInfo = secondLevelLiInfoArray[i];
                if (_secondLevelLiInfo.TagName == analysisResult.TagName) {
                    isFindPeer = true;
                    _secondLevelLiInfo.LiHtmlList.push(liHTML);
                }
            }
            if (!isFindPeer) {
                secondLevelLiInfoArray.push({ TagName: analysisResult.TagName, LiHtmlList: [liHTML] });
            }
            analysisResult.ListInfo = prevAnalysisResult.ListInfo;
            analysisResult.ListInfo.IsMergePrevHTML = true;
        }
        else if ((prevAnalysisResult.IsOL2 && (analysisResult.IsOL || analysisResult.IsUL)) ||
            (prevAnalysisResult.IsUL2 && (analysisResult.IsOL || analysisResult.IsUL))) {
            if (prevAnalysisResult.ListInfo.TagName == analysisResult.TagName) {
                prevAnalysisResult.ListInfo.LiInfoArray.push({ LiHtml: liHTML, LiInfoArray: [] });
                analysisResult.ListInfo = prevAnalysisResult.ListInfo;
                analysisResult.ListInfo.IsMergePrevHTML = true;
            }//此二级有序项对应一级项的列表项跟当前一致且为一级
            else {
                isExtractTable = CreateListInfo(analysisResult, isExtractTable);
            }//当前一级与上一个一级标签不同无法合并,再生成一个新的
        }
        else if ((prevAnalysisResult.IsOL2 && analysisResult.IsOL2) ||
            (prevAnalysisResult.IsUL2 && analysisResult.IsUL2)) {
            var currentFisrtLevelLiInfoArray = prevAnalysisResult.ListInfo.LiInfoArray.slice(-1)[0];
            var secondLevelLiInfoArray = currentFisrtLevelLiInfoArray.LiInfoArray.slice(-1)[0];
            secondLevelLiInfoArray.LiHtmlList.push(liHTML);
            analysisResult.ListInfo = prevAnalysisResult.ListInfo;
            analysisResult.ListInfo.IsMergePrevHTML = true;
        }//同为二级同标签,直接追加
        else if ((prevAnalysisResult.IsOL2 && analysisResult.IsUL2) ||
            (prevAnalysisResult.IsUL2 && analysisResult.IsOL2)) {
            var currentFisrtLevelLiInfoArray = prevAnalysisResult.ListInfo.LiInfoArray.slice(-1)[0];
            //这儿是要新添加不同的二级标签跟上面不一样所以不要属性
            currentFisrtLevelLiInfoArray.LiInfoArray.push({ TagName: analysisResult.TagName, LiHtmlList: [liHTML] });
            analysisResult.ListInfo = prevAnalysisResult.ListInfo;
            analysisResult.ListInfo.IsMergePrevHTML = true;
        }//虽然同时二级,但标签不同,需生成新的二级
    }
    return isExtractTable;
}
function CreateListInfo(analysisResult, isExtractTable) {
    if (analysisResult.IsUL || analysisResult.IsOL) {
        analysisResult.ListInfo = {
            IsMergePrevHTML: false,
            TagName: analysisResult.TagName,
            LiInfoArray: [
                {
                    LiHtml: analysisResult.AnalysisHTML,
                    LiInfoArray: []
                    //LiInfoArray: [{
                    // TagName: "",
                    // LiHtmlList: []
                    //}],
                },
            ]
        };
    } //识别为一级无序列表项 或者 识别为一级有序列表项
    else {
        isExtractTable = false;
    } //第一行识别为二级列表项,做普通处理
    return isExtractTable;
}

5、代码块语法

  • 功能演示
    image
  • 代码分享
var isCodeBlock = false;
if (analysisResult.IsCodeBlock) {
    isCodeBlock = analysisResult.IsCodeBlock;
    //当前是代码块结束或开始
    if (prevAnalysisResult == null) {
        //代码块开始
        analysisResult.TextContent = "";
        previewHTMLArray.push(CreatePreviewSectionHTML("pre", analysisResult.TextContent));
    }
    else {
        if (prevAnalysisResult.IsCodeBlock) {
            //结束
            analysisResult.IsCodeBlock = false;
            analysisResult.TextContent = prevAnalysisResult.TextContent;
            previewHTMLArray[previewHTMLArray.length - 1] = CreatePreviewSectionHTML("pre", analysisResult.TextContent);
        }
        else {
            //开始
            analysisResult.TextContent = "";
            previewHTMLArray.push(CreatePreviewSectionHTML("pre", analysisResult.TextContent));
        }
    }
}

if (prevAnalysisResult.IsCodeBlock) {
    //当前在代码块内
    isCodeBlock = true;
    analysisResult.IsCodeBlock = true;
    var _textContent = "";
    if (analysisResult.TextContent != "" && analysisResult.TextContent != null) {
        _textContent = CreatePreviewSectionHTML("code", analysisResult.TextContent);
    }
    analysisResult.TextContent = prevAnalysisResult.TextContent + _textContent;
    previewHTMLArray[previewHTMLArray.length - 1] = CreatePreviewSectionHTML("pre", analysisResult.TextContent);
}

6、分隔线语法

  • 功能演示
    image
  • 代码分享
var separator_pattern = /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/;//分隔线正则表达式
if (separator_pattern.test(textContent)) {
    tagName = "hr";
}//分隔线

7、链接语法

  • 功能演示
    image
  • 代码分享
var def_pattern = /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/;
if (def_pattern.test(textContent)) {
    html = "textContent";
    tagName = "a";
}//链接
//提取链接和图片语法
function ExtractLink(html) {
    var link_pattern = /!?\[(?:\[[^\[\]]*\]|\\.|`[^`]*`|[^\[\]\\`])*?\]\(\s*(?:<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*)(?:\s+(?:"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/g;
    if (link_pattern.test(html)) {
        var link_pattern2 = /^!?\[((?:\[[^\[\]]*\]|\\.|`[^`]*`|[^\[\]\\`])*?)\]\(\s*(<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*)(?:\s+("(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/;
        html = html.replace(link_pattern, function (val) {
            var getVals = link_pattern2.exec(val);
            var text = getVals[1];
            var href = getVals[2];
            href = href.trim().replace(/^<([\s\S]*)>$/, '$1');
            href = href.replace(/<strong><em>/, "***");
            href = href.replace(/<\/strong><\/em>/, "***");
            href = href.replace(/<strong>/, "**");
            href = href.replace(/<\/strong>/, "**");
            href = href.replace(/<em>/, "*");
            href = href.replace(/<\/em>/, "*");
            var title = getVals[3];
            title = getVals[3] ? getVals[3].slice(1, -1) : '';
            title = title.replace(/<strong><em>/, "***");
            title = title.replace(/<\/strong><\/em>/, "***");
            title = title.replace(/<strong>/, "**");
            title = title.replace(/<\/strong>/, "**");
            title = title.replace(/<em>/, "*");
            title = title.replace(/<\/em>/, "*");
            if (getVals[0].startsWith("!")) {
                return "<img src=\"" + href + "\" title=\"" + title + "\" alt=\"" + text + "\"/>";
            } else {
                var text = ExtractLink(text);
                return "<a href=\"" + href + "\" title=\"" + title + "\">" + text + "</a>";
            }
        });
    }
    return html;
}

8、图片语法

  • 功能演示
    image
  • 代码分享
    参考第7点的链接语法

9、表格语法

  • 功能演示
    image
  • 代码分享
var table_tag_pattern = /^[ ]{0,3}((\|[ ]*?(?:[:]{0,1}- *){3,}[:]{0,1}[ ]*)+\|){1,}[ | ]{0,}$/;//表格出现的标识
if (table_tag_pattern.test(textContent)) {
    html = textContent;
}//表格
//提取表格语法
function ExtractTable(analysisResult, prevAnalysisResult) {
    var isExtractTable = true;
    if (prevAnalysisResult == null) {
        isExtractTable = false;
    }//但因为是第一行,所以不能转为表格
    else {
        //把当前内容根据|分隔进行拆分
        var currentSplitArray = analysisResult.AnalysisHTML.split('|');
        if (analysisResult.IsTable && prevAnalysisResult.TableInfo == null) {
            if (/^[ ]{0,3}\|/.test(prevAnalysisResult.AnalysisHTML)) {
                var headerTHTextArray = prevAnalysisResult.AnalysisHTML.split('|');
                analysisResult.TableInfo = {};
                analysisResult.TableInfo.TextAlignArray = new Array();
                var columnCount = currentSplitArray.length - 2;
                if (columnCount > headerTHTextArray.length - 2) {
                    columnCount = headerTHTextArray.length - 2;
                }
                var thHTML = "";
                for (var i = 0; i < columnCount; i++) {
                    var _tagText = currentSplitArray[i + 1].trim();
                    var textAlign = "";
                    if (_tagText.startsWith("-") && _tagText.endsWith("-")) {
                        textAlign = ""
                    } else if (_tagText.startsWith(":") && _tagText.endsWith(":")) {
                        textAlign = "center";
                    } else if (_tagText.startsWith(":")) {
                        textAlign = "left";
                    } else if (_tagText.endsWith(":")) {
                        textAlign = "right";
                    }
                    var textAlignStyle = ""
                    if (textAlign != "") {
                        textAlignStyle = "style=\"text-align:" + textAlign + "\";";
                    }
                    analysisResult.TableInfo.TextAlignArray.push(textAlignStyle);
                    thHTML += "<th " + textAlignStyle + ">" + headerTHTextArray[i + 1] + "</th>"
                }
                analysisResult.TableInfo.THeadHTML = "<thead><tr>" + thHTML + "</tr></thead>";
                analysisResult.TableInfo.TBodyHTMLArray = new Array();
            }//检查上一行是不是符合做表头内容文本的格式条件
            else {
                isExtractTable = false;
            }//此行虽然是表格标识出现,但因为上一行格式不对,不满足生成表格的条件
        }//当表头还未生成的时候先生成表头
        else if (prevAnalysisResult.TableInfo != null) {
            analysisResult.TableInfo = prevAnalysisResult.TableInfo;
            var tdHtml = "";
            for (var i = 0; i < analysisResult.TableInfo.TextAlignArray.length; i++) {
                var text = currentSplitArray[i + 1];
                if (text === undefined) {
                    text = "";
                }
                tdHtml += "<td " + analysisResult.TableInfo.TextAlignArray[i] + ">" + text + "</td>"
            }
            analysisResult.TableInfo.TBodyHTMLArray.push("<tr>" + tdHtml + "</tr>");
        }//当表头生成后生成表体
        else {
            isExtractTable = false;
        }
    }
    if (isExtractTable == false) {
        analysisResult.TableInfo = null;
    }
    return isExtractTable;
}

10、其他功能

  • 功能演示
    image
  • 代码分享
//通过使用localStorage实现本地缓存
//缓存输入至localStorage
function LocalStorageInputMD(mdInputHTML){
    localStorage.setItem('ls_mdInput',mdInputHTML);
}
//实现输入内容导出
function exportRaw(name, data) {
    var urlObject = window.URL || window.webkitURL || window;
    var export_blob = new Blob([data]);
    var save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
    save_link.href = urlObject.createObjectURL(export_blob);
    save_link.download = name;
    var ev = document.createEvent("MouseEvents");
    ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    save_link.dispatchEvent(ev);
}
//通过将输入缓存至EditorElementRecordHistoryArray中,实现撤销和重做功能
//点击撤销按钮
UndoButtonElement.onclick = function () {
    //document.execCommand("Undo");
    if (EditorElementRecordHistoryArray === undefined ||
        EditorElementRecordHistoryArray == null) {
        EditorElementRecordHistoryArray = new Array();
    }
    else {
        if (EditorElementRecordHistoryArray.length >= 2) {
            if (EditorElementRecordHistoryArray.length <= 2) {
                UndoButtonElement.className += " disable";
            }//当记录元素小于等于1个,就可以撤销了
            if (EditorElementRecordHistoryArray_undo === undefined ||
                EditorElementRecordHistoryArray_undo == null) {
                EditorElementRecordHistoryArray_undo = new Array();
            }//先判断重做队列是否为null
            RedoButtonElement.className = RedoButtonElement.className.replace("disable", "");//将重做按钮点亮
            EditorElementRecordHistoryArray_undo.push(EditorElementRecordHistoryArray.pop());//将保存的历史记录最后一条取出来(这是当前条,取出来要放到重做队列)
            editDivElement.innerHTML = EditorElementRecordHistoryArray.pop();//将当前条的前一条取出来
            Preview();//渲染
        }
    }
}
//点击重做按钮
RedoButtonElement.onclick = function () {
    //document.execCommand("Redo");
    if (EditorElementRecordHistoryArray_undo !== undefined &&
        EditorElementRecordHistoryArray_undo != null &&
        EditorElementRecordHistoryArray_undo.length > 0) {
        editDivElement.innerHTML = EditorElementRecordHistoryArray_undo.pop();
        Preview();//渲染
    }
    if (EditorElementRecordHistoryArray_undo === undefined ||
        EditorElementRecordHistoryArray_undo == null ||
        EditorElementRecordHistoryArray_undo.length <= 0) {
        if (RedoButtonElement.className.indexOf("disable") < 0) {
            RedoButtonElement.className += " disable";
        }
    }
}
//只要有输入动作就清空重做记录
function ClearUndo() {
    EditorElementRecordHistoryArray_undo = new Array();
    if (RedoButtonElement.className.indexOf("disable") < 0) {
        RedoButtonElement.className += " disable";
    }
}

写在最后

本次开发代码质量只能说有手就行哈哈。接下来除了完成新功能的添加,也会预留一部分的时间来重构代码。如果各位小伙伴有什么建议的可以通过评论或者私信的方式告诉我,让我们一起学习吧。

预告一下

后期将对接各平台发布功能(初步预计5个平台,包括博客园、知乎、今日头条、CSDN、简书),预期1个月左右完成一个平台对接,争取春节前完成5个平台的一键发布功能。

标签:Markdown,textContent,一键,else,编辑器,html,prevAnalysisResult,analysisResult,var
来源: https://www.cnblogs.com/hxsfx/p/16686305.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有