主頁 > .NET開發 > 要寫檔案了,emmm,先寫個檔案工具吧——DocMarkdown

要寫檔案了,emmm,先寫個檔案工具吧——DocMarkdown

2022-11-09 06:22:23 .NET開發

前言

之前想用Markdown來寫框架檔案,找來找去發現還是Jekyll的多,但又感覺不是很合我的需求
于是打算自己簡單弄一個展示Markdown檔案的網站工具,要支持多版本、多語言、導航、頁內導航等,并且支持Github Pages免費站點

組件選擇

我自己呢比較喜歡C#,恰好現在ASP.Net Core Blazor支持WebAssembly,絕大部分代碼都可以用C#完成
對于Markdown的分析,可以使用markdig組件(有個缺點,目前它把生成Html的代碼也放到了程式集里,增加了不少的程式集大小,增加了載入時間)
展示組件可以使用Blazorise,有挺多組件能用,還有幾個風格能選,使用比較方便

配置

為了能提供較好的通用性,我定義了以下配置

組態檔

站點目錄必須包含config.json組態檔,
組態檔宣告了DocMarkdown該從哪里讀取Markdown檔案并建立目錄關系,

config.json是一個JSON格式的組態檔,以下配置是一個完整的組態檔示例,

{
  "Title": "DocMarkdown",
  "Icon": "logo.png",
  "BaseUrl": "https://raw.githubusercontent.com/who/project",
  "Path": "docs",
  "Languages": [
    {
      "Name": "簡體中文",
      "Value": "zh-cn",
      "CatalogText": "本文內容"
    }
  ],
  "Versions": [
    {
      "Name": "DocMarkdown 1.0",
      "Value": "1.0",
      "Path": "main"
    }
  ]
}

標題

$.Title屬性值決定了顯示于左上角(默認主題)的檔案標題名稱,
該屬性必須填寫,

圖示

$.Icon屬性決定了顯示于檔案標題左側的圖示路徑,
該屬性可不存在或為空,

基礎地址

$.BaseUrl屬性決定了整個Markdown檔案的路徑,
該屬性必須填寫,可以為空字串,
當屬性為空字串或相對路徑時,將使用本域名內資源,

路徑地址

$.Path屬性將附加于每個Markdown檔案路徑之前,
該屬性可以不存在,

多語言

$.Languages屬性用于定義檔案的多語言支持,
該屬性可以不存在,
屬性內容必須為陣列,
第一個元素將作為默認語言,

語言名稱

$.Languages[0].Name屬性用于顯示語言名稱,
該屬性必須填寫,

語言值

$.Languages[0].Value屬性決定了該語言的檔案名稱,
該屬性必須填寫,
屬性內容將附加在Markdown檔案路徑擴展名之前,例如.zh-cn

目錄文本

$.Languages[0].CatalogText屬性決定了選擇該語言時,檔案頁右側的導航目錄標題,
該屬性必須填寫,

多版本

$.Versions屬性用于定義檔案的多版本支持,
該屬性可以不存在,
屬性內容必須為陣列,
第一個元素將作為默認版本,

版本名稱

$.Versions[0].Name屬性用于顯示版本名稱,
該屬性必須填寫,

版本值

$.Languages[0].Value屬性決定了該版本在Url上的值,
該屬性必須填寫,

版本路徑

$.Languages[0].Path屬性決定了該版本在Url上的值,
該屬性必須填寫,

導航配置

檔案根路徑必須存在nav.json,如果存在多語言,每個語言都需要一份導航配置,
以檔案路徑規則里的示例為例,則必須存在https://raw.githubusercontent.com/who/project/main/docs/nav.zh-cn.json導航組態檔,

nav.json是一個JSON格式的組態檔,以下配置是一個完整的組態檔示例,

{
  "簡介": {
    "Path": "index"
  },
  "快速使用": {
    "Path": "quick"
  },
  "高級": {
    "Children": {
      "內容A": {
        "Path": "advanced/content1"
      },
      "內容B": {
        "Path": "advanced/content2"
      }
    }
  }
}

導航檔案的內容將被決議生成樹形結構展示于頁面,

節點名稱

$.{name}屬性名稱將作為導航目錄的樹形節點名,
屬性值為物件,不能為空,
可以存在多個節點,

節點路徑

$.{name}.Path屬性作為該節點對應的檔案路徑,路徑為相對路徑,
屬性可以不存在,不存在或為空時,只作為可折疊節點,點擊不會導航至其它頁面,

節點子項

$.{name}.Children屬性作為該節點的子項容器,里面包含了該節點下的所有子節點內容,
屬性可以不存在,

可以組合多層樹形導航目錄,

{
  "一級目錄1": {
    "Path": "c1"
  },
  "一級目錄2": {
    "Path": "c2"
  },
  "一級目錄3": {
    "Children": {
      "二級目錄1": {
        "Path": "c3/c1"
      },
      "二級目錄2": {
        "Children": {
          "三級目錄1": {
            "Path": "c3/c2/c1"
          },
          "三級目錄2": {
            "Path": "c3/c2/c2"              
          }
        }
      }
    }
  }
}

檔案路徑規則

基于配置,DocMarkdown會將網站的路徑映射至目標檔案,
例如/grpc/
當以/結尾或為空值時,自動添加index
然后得到路徑/grpc/index

如果存在多語言,則于路徑末尾添加.{lang}{lang}為當前語言值
最后于末尾添加.md擴展名,
得到路徑/grpc/index.zh-cn.md

如果存在路徑地址,則于路徑前添加/{path}路徑地址
得到路徑/docs/grpc/index.zh-cn.md

如果存在多版本,則于路徑前添加/{version}{version}版本路徑
得到路徑/main/docs/grpc/index.zh-cn.md

最后于路徑前添加{baseUrl}基礎地址
得到路徑https://raw.githubusercontent.com/who/project/main/docs/grpc/index.zh-cn.md

DocMarkdown將請求該地址以獲取Markdown檔案內容并決議生成Html內容展現出來,

決議與渲染

markdig能決議Markdown內容并回傳一系列不同型別的物件,根據這些物件的型別,我們可以生成想要的內容對應的Razor組件

定義一個MarkdownRenderer用于決議對應型別的物件

public abstract class MarkdownRenderer
{
    public abstract bool CanRender(MarkdownObject markdown);

    public abstract object Render(IMarkdownRenderContext context, MarkdownObject markdown);
}

public abstract class MarkdownRenderer<T> : MarkdownRenderer
    where T : MarkdownObject
{
    public override bool CanRender(MarkdownObject markdown)
    {
        return markdown is T;
    }

    public override object Render(IMarkdownRenderContext context, MarkdownObject markdown)
    {
        return Render(context, (T)markdown);
    }

    protected abstract object Render(IMarkdownRenderContext context, T markdown);
}

為什么回傳object型別?這是由于Markdown里支持HTML內容,而markdig回傳行內HTML內容時,會將一個元素拆成兩個IarkdownRender
一個是開頭,例如<span>,一個是結尾,例如</span>

渲染Block和Inline

public RenderFragment RenderBlock(ContainerBlock containerBlock)
{
    return new RenderFragment(builder =>
    {
        int i = 0;
        foreach (var block in containerBlock)
        {
            var obj = Render(block);
            if (obj is RenderFragment fragment)
                builder.AddContent(i, fragment);
            else if (obj is MarkupString markup)
                builder.AddContent(i, markup);
            else if (obj is HtmlElement html)
            {
                if (html.IsEnd)
                    builder.CloseComponent();
                else
                {
                    builder.OpenElement(i, html.Tag);
                    i++;
                    if (html.Attributes != null)
                    {
                        foreach (var attr in html.Attributes)
                        {
                            if (attr.Value =https://www.cnblogs.com/Kation/archive/2022/11/08/= null)
                                builder.AddAttribute(i, attr.Key);
                            else
                                builder.AddAttribute(i, attr.Key, attr.Value);
                            i++;
                        }
                    }
                    if (html.IsSelfClose)
                        builder.CloseElement();
                }
            }
            else
                builder.AddContent(i, obj);
            i++;
        }
    });
}
public RenderFragment RenderInline(ContainerInline containerInline)
{
    return new RenderFragment(content =>
    {
        var inline = containerInline.FirstChild;
        int i = 0;
        while (inline != null)
        {
            var obj = Render(inline);
            if (obj is RenderFragment fragment)
                content.AddContent(i, fragment);
            else if (obj is MarkupString markup)
                content.AddContent(i, markup);
            else if (obj is HtmlElement html)
            {
                if (html.IsEnd)
                    content.CloseComponent();
                else
                {
                    content.OpenElement(i, html.Tag);
                    i++;
                    if (html.Attributes != null)
                    {
                        foreach (var attr in html.Attributes)
                        {
                            if (attr.Value == null)
                                content.AddAttribute(i, attr.Key);
                            else
                                content.AddAttribute(i, attr.Key, attr.Value);
                            i++;
                        }
                    }
                    if (html.IsSelfClose)
                        content.CloseElement();
                }
            }
            else
                content.AddContent(i, obj);
            inline = inline.NextSibling;
            i++;
        }
    });
}

渲染整個Markdown檔案

private void RenderMarkdown(RenderHandle renderHandle, MarkdownDocument document)
{
    var content = RenderBlock(document);
    renderHandle.Render(builder =>
    {
        builder.OpenComponent<LayoutView>(0);
        builder.AddAttribute(1, nameof(LayoutView.Layout), typeof(MainLayout));
        builder.AddAttribute(2, nameof(LayoutView.ChildContent), (RenderFragment)(child =>
        {
            child.OpenComponent<Index>(0);
            child.AddAttribute(1, "Content", content);
            child.CloseComponent();
        }));
        builder.CloseComponent();
    });
}

加載

為了加快加載速度,按照官方檔案,改為加載Brotli壓縮后的檔案
并增加加載進度影片

<div id="app">
    <div  style="bottom: 0; top: 0; left: 0; right: 0;">
        <div >
            <div style="width: 64px; height: 64px;">
                <svg viewBox="0 0 21 24">
                    <path fill="transparent" d="M4.5,19.5A1.5,1.5,0,0,0,6,21H18.08V18H6A1.51,1.51,0,0,0,4.5,19.5Z" transform="translate(-1.5)" />
                    <path fill="#1296db" d="M21.39,18a1.12,1.12,0,0,0,1.12-1.12V1.13A1.13,1.13,0,0,0,21.38,0H6A4.5,4.5,0,0,0,1.5,4.5v15A4.5,4.5,0,0,0,6,24H21.38a1.13,1.13,0,0,0,1.13-1.13v-.76A1.12,1.12,0,0,0,21.39,21h-.3V18Zm-4.14-4.54-2.93-4h1.79V5h2.3V9.42h1.79ZM13.29,5v8.52H11V8.91L8.94,11.54,6.89,8.91v4.64H4.59V5H6.95l2,3.22,2-3.22Zm4.79,16H6a1.5,1.5,0,0,1,0-3H18.08Z" transform="translate(-1.5)" />
                </svg>
            </div>
            <div >
                <div  style="margin-top: 32px;">
                    <div id="progressBar"  role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%</div>
                </div>
            </div>
        </div>
    </div>
</div>
var total = 0;
var receivedLength = 0;
Blazor.start({ // start manually with loadBootResource
    loadBootResource: function (type, name, defaultUri, integrity) {
        if (type == "dotnetjs")
            return defaultUri;

        if (location.hostname !== 'localhost')
            defaultUri = defaultUri + '.br';

        const fetchResources = fetch(defaultUri, { cache: 'no-cache' });
        return fetchResources.then(async (r) => {
            const reader = r.body.getReader();
            let length = +r.headers.get('Content-Length');
            total += length;
            var progressbar = document.getElementById('progressBar');
            let dataLength = 0;
            let dataArray = [];
            while (true) {
                const { done, value } = await reader.read();
                if (done) {
                    break;
                }
                dataArray.push(value);
                dataLength += value.length;
                receivedLength += value.length;
                const percent = Math.round(receivedLength / total * 100)
                var pct = percent + '%';
                progressbar.style.width = pct;
                progressbar.innerText = pct + ' ' + calcSize(receivedLength) + '/' + calcSize(total);
                console.log('Received: ' + name + ',' + calcSize(dataLength) + '/' + calcSize(length));
            }
            let data = https://www.cnblogs.com/Kation/archive/2022/11/08/new Uint8Array(dataLength);
            let position = 0;
            for (let array of dataArray) {
                data.set(array, position);
                position += array.length;
            }
            const contentType = type ==='dotnetwasm' ? 'application/wasm' : 'application/octet-stream';
            if (location.hostname !== 'localhost') {
                const decompressedResponseArray = BrotliDecode(data);
                return new Response(decompressedResponseArray,
                    { headers: { 'content-type': contentType } });
            }
            else
                return new Response(data,
                    { headers: { 'content-type': contentType } });
        });
        return fetchResources;
    }
});

function calcSize(bytes) {
    if (bytes > 1024 * 1024) {
        return Math.round(bytes / 1024 / 1024 * 100) / 100 + 'MB';
    }
    else if (bytes > 1024) {
        return Math.round(bytes / 1024 * 100) / 100 + 'KB';
    }
    else {
        return bytes + 'B';
    }
}


這樣加載內容就能縮小至2.5MB

效果

鏈接

最終效果,點擊訪問
Github原始碼地址,點擊訪問

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/529770.html

標籤:.NET技术

上一篇:C#教程之C#教程之Dev GridControl GridView 屬性大全[中文解釋]

下一篇:給依賴屬性設定單向系結時,直接給依賴屬性賦值,導致單向系結永久失效

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more