解決自媒體一鍵多平臺發佈,從零開發Markdown編輯器(一)

来源:https://www.cnblogs.com/hxsfx/archive/2022/09/12/16686305.html
-Advertisement-
Play Games

前言 在這個人人都是自媒體的時代,為了擴大個人影響力同時預防文章被盜版至其他平臺,多平臺發佈文章就成了創作者們的一大痛點,為瞭解決這一痛點就需要將文章的編輯到發佈無縫集成。 現在要實現這一功能,開發一個完全可控的Markdown編輯器就是第一步。 本文源碼已上傳Github:Github hxsfx ...


前言

在這個人人都是自媒體的時代,為了擴大個人影響力同時預防文章被盜版至其他平臺,多平臺發佈文章就成了創作者們的一大痛點,為瞭解決這一痛點就需要將文章的編輯到發佈無縫集成。
現在要實現這一功能,開發一個完全可控的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個平臺的一鍵發佈功能。

博客園-本文作者(好先生FX http://www.cnblogs.com/hxsfx)

著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Django框架(九) cookie與session簡介 網址的發展史: 1、起初網站都沒有保存用戶功能的需求,所有用戶訪問返回的結果都是一樣的。 比如:新聞網頁,博客網頁,小說... (這些網頁是不需要登錄後才能訪問的,每個人訪問的結果都一樣) 2、後來出現了一些需要保存用戶信息的網站 比如:支付 ...
  • 由於本文需要有一定的Stream基礎,所以如果不懂什麼是Stream的同學請移步:Java Stream入門 操作分類 graph LR 操作分類 中間操作 終端操作 操作分類 中間操作 有狀態 中間操作 無狀態 短路 終端操作 非短路 終端操作 中間操作只進行操作的記錄,而實際的操作是由終端操作來 ...
  • 這兩天在對一些ORM進行性能測試(涉及SqlSugar、FreeSql、Fast.Framework、Dapper.LiteSql),測試用的是Winform程式,別人第一眼看到我的程式,說,你這測試沒意義! 可能我的測試程式的某些地方寫的比較變態吧,但我認為有現實意義,並且網上有相關網站崩潰問題的 ...
  • 參考鏈接:https://www.systutorials.com/docs/linux/man/7-netlink/ #1. 監聽Netlink消息類型示例 Netlink是用戶程式與內核通信的socket方法,通過Netlink可以獲得修改內核的配置,常見的有獲得介面的IP地址列表、更改路由表或 ...
  • 0. 前言 可以臨時設置,也可以修改配置文件 1. 修改配置文件 # 打開 配置IP的文件 路徑如下 sudo vi /etc/netplan/01-network-manager-all.yaml 1.1 輸入(修改)以下內容 # This is the network config writte ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 1.結論先行 無論ibp(innodb_buffer_pool_size)是否充足,MySQL的性能都遠不如GreatSQL。 MySQL的性能平均約為 ...
  • 一.數據的存儲方式 特定的文件 / 記憶體 / 第三方雲伺服器 / 資料庫伺服器 二.什麼是資料庫 資料庫按照一定的形式來組織存儲數據,目的是為了便於操作數據 —— 增刪改查 三.資料庫發展歷史 網狀資料庫 -> 層次型資料庫 -> 關係型資料庫 -> 非關係型資料庫(NoSQL) 關係型資料庫邏輯結 ...
  • 本文介紹對vue-plugin-hiprint部分重要代碼的解析,這是一個很好的開源插件,能夠自己自定義列印模板,通過後端傳來的數據進行渲染列印,官方也提供了許多的api供開發者使用。界面採用了antdesign。實現了免預覽的直接列印。 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...