65.9K
CodeProject 正在变化。 阅读更多。
Home

用于 Visual Studio Code 的全能工具链, 用于文章撰写

2017年6月29日

MIT

20分钟阅读

viewsIcon

64270

downloadIcon

406

现在带自动编号功能!新的 Visual Studio Code 扩展“可扩展 Markdown 转换器”增强了内置的 Markdown 扩展,形成了一体化工具链,提供便捷的编辑器、渲染文档查看器、拼写检查器以及按 CodeProject 文章提交要求转换为 HTML 的功能。

Logo

 

Visuals Studio Code, Markdown support

引言

警惕建议——即使是这条。

卡尔·桑德堡

目录

引言

在我之前关于该主题的文章《文章撰写实用工具链》中,我表达了文章撰写的烦恼,并试图解释更好工具的重要性。我为了应对挑战而创建的工具链的一些变体大大提高了我的写作效率,并带来了一些心安,但远非完美。即使使用 Visual Studio 2015 作为写作工具也出现了一些明显的延迟。无论如何,它作为概念的证明起作用了:这项活动在第一篇文章写完后就得到了回报。

最近,我第一次尝试了Visual Studio Code,并立即注意到它对Markdown的支持。它是否可以用于文章撰写?我的结论是:“差不多了”。所有可用的支持和扩展都无法在没有大量额外努力(包括一些代码开发)的情况下,形成一个真正可用的工具链。一些至关重要的功能完全缺失。经过一番研究,我发现全面的解决方案需要另一个Visual Studio Code 扩展,以生成适合通过 CodeProject 文章提交向导立即提交的 HTML 文件,以及用于许多其他目的。请注意,我为 CodeProject 准备文章的想法是,能够一步将文章粘贴到文章提交向导中,无需任何修改。

在我创建这个扩展并将其发布到 Visual Studio Marketplace 后,我发现结果非常实用。我的工具链变得非常轻量级和方便。

本文完全使用基于 Visual Studio Code 的一体化工具链编写。

Visual Studio Code

我为什么要尝试 Visual Studio Code?恐怕要说,这个工具的好处被某种程度地低估了。它的营销方式给人的印象是,它是为了多平台特性而大幅削减了功能的 Visual Studio。这不是真的。例如,将Eclipse视为“集成开发环境”而将 Visual Studio Code 视为“源代码编辑器”是相当不公平的。

也许 Visual Studio Code 最令人印象深刻的特质是它的速度,尤其是与“真正的”Visual Studio相比。另一个令人愉悦的特点是:它对习惯使用 Visual Studio 的开发人员非常友好。

无论如何,我最初的兴趣始于寻找开发和调试.NET Core代码的最佳支持。但这完全是另一个故事了……

可用扩展

Visual Studio Marketplace上有许多开箱即用的 Markdown 相关 Visual Studio Code 扩展。据我所知,在撰写本文时,它们都存在相当大的缺陷或不足。如果它们是完美的,那么本文将完全多余。

VS Code Markdown,微软

基本的 Markdown 支持扩展嵌入在 Visual Studio Code 中。它的 ID 是 “Microsoft.vscode-markdown”。它提供基本的文档创作和预览。扩展“将 Markdown 转换为 HTML”基于此 Microsoft 扩展,并增加了最缺失的功能:将转换后的 Markdown 保存为 HTML 文件。

Markdown All in One

这是我尝试的第一个扩展。尽管它的名字带有“All”字样,但这似乎是一个巨大的夸大。也许唯一有用的功能是它显式的目录 (TOC) 生成和更新——参见描述。尽管如此,我还是使用这个扩展为Markdown 示例生成 TOC,只是为了避免依赖我在我的方法中所依赖的“markdown-it插件

该解决方案看起来太重了,因为它不依赖于 Microsoft 的“VS Code Markdown”扩展。相反,该解决方案基于单独安装的 node.js npm 和 npm 模块,例如 markdown-itmarkdown-it-named-headers 等。这些模块随扩展提供,重复了通过 Visual Code 安装的模块,这可能导致将来行为不匹配。

不幸的是,在markdown-it-named-headers中发现了一个严重bug:相同的标题名称会破坏id属性值的唯一性,这会导致生成的HTML无效。此外,为id属性和目录生成单独的插件几乎没有意义。相反,这两个功能部分密切相关,应该在统一的插件中支持,以确保一致性。

代码拼写检查器扩展

此扩展在此处描述。它提供了相当方便的拼写检查和快速类型修复工作流程。

拼写问题直接显示在文本中,因此可以通过 IntelliSense 进行修复。特别是,可以将正确的单词添加到全局(按用户)或本地(按工作区)词典。Markdown 格式不会破坏拼写问题的检测。非常重要的是,有一个问题摘要视图,“拼写检查器信息”,可用于确保没有遗漏任何问题,以及打开或关闭该功能。下面,我将演示如何将此命令和其他命令添加到键盘绑定中。

那么,问题出在哪里?

最重要的一部分尚未完成:生成 HTML 文件。到目前为止,我们还没有获得可接受的开箱即用 Visual Studio Code 扩展。有些将文档输出为 HTML,但方式我们无法轻松使用,例如,将内容放入系统剪贴板中。

Visual Studio Code 文档页面上提供了一些想法。主要思想是:转换为 HTML 应该像项目构建一样工作,使用熟悉的 Ctrl+Shift+B 快捷方式,在输出窗口中显示结果,等等。为此,应该在 “.vscode/tasks.json” 中定义适当的构建任务。

它会奏效吗?实际上,不会。存在许多问题,使得整个过程几乎无法使用。一些问题是次要的,很容易修复。但最严重的问题是:生成的文件缺少标题中的id属性。这引发了一个问题:如果它无论如何都无法与生成的 HTML 一起使用,我们为什么要用扩展代码自动生成 TOC 呢?值得注意的是,扩展预览可以正确地使用 TOC 进行导航。这是通过另一个node.js模块“markdown-it-named-headers”实现的,它是“markdown-it”的一个插件。它无法通过命令行“markdown-it”接口使用,因为命名标题是可选的,并且未通过命令行参数公开。此外,它们可能被认为是危险的。在解释我的解决方案之前,我需要给读者一个警告

警告
不幸的是,在自动生成id和目录时,无法保证id值的唯一性,就像某些处理器(例如Pandoc)所做的那样。严格来说,可能的id冲突将使HTML无效。至少,CodeProject文章页面上基于锚点的导航可能会中断。也就是说,如果创建两个或多个相同的标题或在HTML中明确输入非唯一的id属性,就会发生这种情况。

尽管存在这个问题,但在相同标题的情况下,id值的唯一性并未被破坏;此问题已在可扩展 Markdown 转换器 4.0.0 版本中解决。

话虽如此,让我们考虑一个真正可行的解决方案,它基于创建独立的 Visual Studio Code 扩展。

解决方案:可扩展 Markdown 转换器

扩展开发技术在Visual Studio Code 文档中有所描述。

Markdown-it 初始化

解决方案的主要思想是:我们不需要打包完整的 Markdown 支持 npm 模块,而是需要重用依赖扩展“VS Code Markdown”提供的资源

// extension host makes it always accessible to extensions:
var vscode = require('vscode');  

const idToc = require("./id.toc.js");

//...

if (!lazy.markdownIt)
    lazy.markdownIt = (function () { // modify, depending in settings
    const extension = vscode.extensions.getExtension("Microsoft.vscode-markdown");
    if (!extension) return;
    const extensionPath = path.join(extension.extensionPath, "node_modules");
    let md = require(path.join(extensionPath, "markdown-it"))().set(optionSet);
    md.use(idToc, {
        enableHeadingId: lazy.settings.headingId, 
        idPrefix: lazy.settings.headingIdPrefix,
        stringModule: require(path.join(extensionPath, "string")),
        markerPattern: new RegExp(lazy.settings.tocRegex, "m"),
        includeLevel: lazy.settings.tocIncludeLevels,
        tocContainerClass: lazy.settings.tocContainerClass,
        tocListType: lazy.settings.tocListType
    });
    for (let pluginData in additionalPlugins) {
        let plugin;
        try {
            plugin = require(additionalPlugins[pluginData].name);
        } catch (requireException) {
            continue;
        } //exception
        md = md.use(plugin, additionalPlugins[pluginData].options);
    } // using additionalPlugins
    return md;
})();

// now we can use markdownIt to render HTML

//...

这里加载了两个node.js模块:“markdown-it”和“string”。它们随嵌入式扩展“Microsoft.vscode-markdown”一起提供,并可以通过其id找到。“string”扩展由嵌入在“Extensible Markdown Converter”扩展中的“idToc”模块使用。它实现标题id属性和目录的生成,并充当markdown-it插件的角色。

请注意,设置 markdownIt 的函数还会加载附加插件。这些插件可以由用户安装在任意目录中,并在下文解释的“settings.json”中进行配置。

使用惰性评估优化性能

另一个有趣的观点是使用惰性求值。扩展本身是惰性加载的,只有在真正需要时才加载。在扩展激活期间,而不是在每个命令执行期间,执行相对较长的操作,例如markdown-it设置和设置处理,是很诱人的。然而,它不会正确工作,因为在使用之间,用户可以进行一些更改,使得markdown-it的当前状态或设置数据无效。特别是,用户可以编辑设置或仅仅是一个或另一个 CSS 文件。惰性求值与某种失效机制相结合解决了这个问题。这就是它的工作原理

const lazy = { markdownIt: undefined, settings: undefined };

// ...

if (!lazy.settings)
    lazy.settings = getSettings();

// ...

if (!lazy.markdownIt) // already shown in first code sample
    lazy.markdownIt = (function () {
        // ...
    };    

// ...

vscode.workspace.onDidChangeTextDocument(function (e) {
    if (e.document === vscode.window.activeTextEditor.document)
        provider.update(previewUri);
    if (e.document.languageId == "css") // any change in CSS is detected
        lazy.settings = undefined;
}); //vscode.workspace.onDidChangeTextDocument
vscode.workspace.onDidChangeConfiguration(function (e) {
    lazy.settings = undefined;
    lazy.markdownIt = undefined;
}); //vscode.workspace.onDidChangeConfiguration

自动编号

版本 5.0.0 引入了可选的用户配置自动编号,具有丰富的功能集。它作为嵌入式插件的一部分实现。此插件也作为独立产品发布在 npm 上。

首先,所有选项分为两个级别:适用于整个文档的通用选项(命名为default*)和按标题级别描述的选项(在pattern属性中)。例外是选项standAlong,它仅出现在pattern中,并且仅针对单个标题级别定义。

默认情况下,标题编号显示为多组件字符串,包括上级标题的编号,例如“2.11.3”。选项standAlong用于禁用上级部分,在此示例中仅显示“3”。

属性名称 默认值 描述
前缀
defaultPrefix
"" 数字前出现的字符串。常用包括“Chapter”、“Part”
后缀
defaultSuffix
". " 数字后出现的字符串。用于分隔数字和标题标题
start
defaultStart
1 每个编号部分的起始编号。它可以是任何整数、任何可解析为整数的字符串或任何字符。
分隔符
defaultSeparator
1 每个编号部分的起始编号。它可以是任何整数、任何可解析为整数的字符串或任何字符。
独立 未定义 “独立”标志,定义对于某些独立的标题级别,不显示从上级标题继承的数字组件。

文档中作为第一段定义的自动编号选项示例

@numbering {
    enable: true
}

此示例定义了默认自动编号的使用。

版本 5.8.0 引入了用于文档内自动编号选项规范的新简化格式。

以前使用的格式是 JSON;它仍然可以使用,但它不太适合人工输入,并且不容错。目前,它优先。如果 JSON 解析失败,新解析器将尝试使用新语法解析[](= ... =)标签的内容。

  • 每个属性都放在单独的一行上
  • 前导和尾随的空格以及语法元素之间的空格将被忽略
  • 行语法:<property>: <value>
  • 文档属性
    • 已启用: true
    • defaultStart: <value>
    • defaultSeparator: <value>
    • defaultPrefix: <value>
    • defaultSuffix: <value>
  • 标题级别属性
    • h<level>.standAlone: true
    • h<level>.start: <value>
    • h<level>.separator: <value>
    • h<level>.prefix: <value>
    • h<level>.suffix: <value>
      这里,<level> 值 1、2、... 对应于 Markdown 标题 ###、... 或 HTML 元素 h1h2、...
  • .start, defaultStart的有效值
    • 整数
    • 字符串(在这种情况下,只使用第一个字符)
    • 字符串数组

如果一行解析失败,则会忽略它。这可用于注释。

文档内自动编号选项规范示例

@numbering {
    enable: true
    defaultSeparator: "."
    defaultSuffix: " "
    h1.prefix: "Chapter "
    h1.suffix: ": "
    h1.separator: "doesn't matter, nothing to separate on top level"
    h1.start: ["One", "Two", "Three", "Four"]
    h2.prefix: "Chapter "
    h2.separator: ", §"
    h2.suffix: ": "
    h3.standAlong: true
    h4.start: "a"
    h4.suffix: ") "
}

自动编号的“start”选项可以是任何整数或表示该整数的字符串,任何字符,或者从版本 5.5.5 开始,可以是表示标题编号的字符串数组,例如["One", "Two", "Three"]。这些不同的类别以自然的不同方式进行迭代。特别是,基于字符的名称通过递增其 Unicode 代码点进行迭代,例如“A”、“B”、“C”……

在实现中,这里有一个有趣的亮点是涵盖所有这些情况的通用Iterator

function Iterator(first) {
    if (first.constructor == Array) this.array = first;
    this.counter = this.array ? 0 : first;
    Iterator.prototype.toString = function () {
        return this.array ?
            this.array[this.counter].toString() : this.counter.toString()
    }; // toString
    Iterator.prototype.next = function () {
        if (this.array)
            if (!this.array[this.counter + 1]) {
                this.counter = this.array[this.array.length - 1];
                delete this.array;
            } else
                this.counter++;
        if (!this.array) { // again, because it could have changed by delete this.array 
            let tryNumeric = parseInt(this.counter);
            if (isNaN(tryNumeric)) {
                let codePoint = this.counter.codePointAt();
                this.counter = String.fromCodePoint(++codePoint);
            } else
                this.counter = (++tryNumeric).toString();
        } //if
        return this;
    } //next
} //Iterator

我们来看看它是如何运作的。

假设我们有以下 Markdown 片段,包含在其他 Markdown 文件中,自动编号选项不同。

## Contents{no-toc}

@toc

## Introduction

## Something Else 

### Section

### Another Section

#### Sub-Section

要启用默认自动编号,请按此方式包含它

@numbering {
    enable: true
}

@include(body.md)

这将给出以下 TOC

现在,让我们使用一些高级自动编号选项

@numbering {
    enable: true
    defaultSuffix: " "
    h2.prefix: "Chapter "
    h2.suffix: ": "
    h2.start: ["One", "Two", "Three"]
    h4.start: "a"
    h4.suffix": ") "
    h4.standAlong: true
}

@include(body.md)

## Out of Chapter Names

No more names in "start": ["One", "Two", "Three", "Four"]

## Using Letters Instead of Names

它会给我们一个更花哨的目录

请注意,["One", "Two", "Three", "Four"]数组的迭代元素已用完。然后,下一个迭代以下列方式执行:最后一个元素被解析为字符并按其代码点递增。这给了我们“U”和“V”。此功能旨在优雅地处理这种情况,以便作者可以轻松检测需要提供更多数组元素的位置,而不会破坏 TOC 结构。

另请注意标题编号 1、2 和“a)”。此处使用了“standAlone”选项,并且“a”使用了专门的 "suffix": ") "

设置附加插件

additionalPlugins对象以保守的方式从设置数据构建:如果出现问题,插件数据将不会被添加。

const additionalPlugins = (function () {
    let result = [];
    if (!settings.additionalPlugins) return result;
    if (!settings.additionalPlugins.plugins) return result;
    if (!settings.additionalPlugins.plugins.length) return result;
    if (settings.additionalPlugins.plugins.length < 1) return result;
    let effectiveParentPath = settings.additionalPlugins.absolutePath;
    if (!effectiveParentPath) {
        let relativePath = settings.additionalPlugins.relativePath;
        if (!relativePath) return result;
        relativePath = relativePath.toString();
        effectiveParentPath =
            path.join(vscode.workspace.rootPath, relativePath);
    } //if 
    if (!effectiveParentPath) return result;
    if (!fs.existsSync(effectiveParentPath.toString())) return result;
    for (let pluginDataProperty in settings.additionalPlugins.plugins) {
        const pluginData =
            settings.additionalPlugins.plugins[pluginDataProperty];
        if (!pluginData.name) continue;
        const effectivePath =
            path.join(
                effectiveParentPath.toString(),
                pluginData.name.toString());
        if (!fs.existsSync(effectivePath)) continue;
        if (!pluginData.enable) continue;
        result.push({name: effectivePath, options: pluginData.options});
    } // loop settings.additionalPlugins.plugins
    return result;
}()); //additionalPlugins

当然,“package.json”应该在其依赖项中声明扩展“VS Code Markdown”。

{
    "name": "convert-markdown-to-html",
    "displayName": "Convert Markdown to HTML",
    "description": "Converts Markdown files and saves them as HTML",

    // ...
    
    "dependencies": {
        "vscode": "^1.13.1"
    },
    "extensionDependencies": [
        "Microsoft.vscode-markdown"
    ],
    
    // ...

}

函数getMarkdownPlugin解决了现有扩展中未解决的一些问题。首先,它启用了标题中id属性的生成,这些属性用于 TOC 和其他地方。另一个重要的改进是使用了markdown-it排版器

扩展预览的一个明显烦恼是未使用 node.js 模块“markdown-it”的排版器功能。我在上一篇文章中写过类似的功能;请查看“–smart”命令行选项的描述。就“markdown-it”模块而言,这是排版器选项,应在渲染之前设置。这在转换脚本“build.js”中完成,因此像长破折号("‐‐‐")、撇号或不成对引号这样的输入标记将以排版正确的方式显示为“—”、“”、“'”等。这样,扩展预览和最终输出将看起来不同。另一个不同之处是预览的“花式格式”,它并非完全由 CSS 控制。

排版器行为在“可扩展 Markdown 转换器”文档中有详细解释。

预览与生成的 HTML 文件不匹配的问题已在 2.2.0 版本中解决。现在,预览与文件精确匹配,因为它是由相同源生成的。有关更多详细信息,请参阅预览文档

另一个要实现的功能是向生成的正文添加 HTML 头部和尾部。模块markdown-it本身不执行此操作。有一个特殊的原因使其足够重要:需要在head元素中指定 UTF-8 编码,以使渲染正确。CodeProject 无论如何都会这样做,但在将文档粘贴到 CodeProject 文章提交向导之前检查文档很重要。

有关其他实现细节,请参阅原始源代码

自定义工作区设置

所有设置都可以全局(按用户)或按工作区进行自定义。我们可以使用它来自定义 Markdown 预览和行为(首先是 IntelliSense),使其适合用于文章撰写的工作区。要按工作区进行操作,我们需要创建一个文件“.vscode/settings.json”。

{
    "markdown.extension.convertToHtml.reportSuccess": true, // default
    "markdown.extension.convertToHtml.showHtmlInBrowser": false, // default
    "markdown.extension.convertToHtml.embedCss": false, // default
    "markdown.extension.convertToHtml.outputPath": "", // default
    "markdown.extension.convertToHtml.titleLocatorRegex":
        "^(.*?)\\[\\]\\(title\\)", // default
    // markdown-it options, all defaults:
    "markdown.extension.convertToHtml.options.allowHTML": true,
    // "markdown-it-named-headers" plug-in,
    // adds id attributes to h1 .. h6 elements:
    "markdown.extension.convertToHtml.options.headingId": true,
    // converts "link-like" text: for ex., "http://my.com"
    // to HTML anchors:
    "markdown.extension.convertToHtml.options.linkify": false,
    // replaces new line marker with br HTML element:
    "markdown.extension.convertToHtml.options.br": true,
    // typographer, see the documentation:
    // https://sakryukov.github.io/vscode-markdown-to-html/#typographer
    // :
    "markdown.extension.convertToHtml.options.typographer": true,
    // applicable if typographer is true:
    // 4 characters, replacement for "" and '':
    "markdown.extension.convertToHtml.options.smartQuotes": "“”‘’",    
    "markdown.extension.convertToHtml.options.additionalPlugins": {
        "absolutePath": null, // just a placeholder
        // relative to workspace:
        "relativePath": "../additional_plugins/node_modules", 
        "plugins": [
            {
                "name": "markdown-it-sub",
                "enable": true,
                "syntacticDecorators": [
                    {
                        "enable": true,
                        "regexString": "\\~(.*?)\\~",
                        "tooltipFormat": "Subscript: %s",
                        "style": { "backgroundColor": "yellow" }
                    }
                ]
            },
            {
                "name": "markdown-it-sup",
                "enable": true,
                "syntacticDecorators": [
                    {
                        "enable": true,
                        "regexString": "\\^(.*?)\\^",
                        "tooltipFormat": "Superscript: %s",
                        "style": { "backgroundColor": "azure" }
                    }
                ]
            }
        ]
    "markdown.styles": [
        // same styles used for preview are used in converted HTML files:
        "style.css", 
        "someMoreStyles.css"
    ],
 
    // ...

    // Very important: remove entered words from suggestions,
    // leave only markdown syntax:
    "editor.wordBasedSuggestions": false,
    "[markdown]": { // for Markdown language id only:
        "editor.codeLens": true,
        "editor.lineNumbers": "off",
        "editor.rulers": [
            79 //CodeProject requirements for source code samples
        ]
    },
    "git.enabled": false, // git is used, but not with VS Code integration 
    "cSpell.enabled": true // default
}

上述所有选项“markdown.styles”均由扩展“可扩展 Markdown 转换器”引入。选项“markdown.styles”由该扩展及其依赖扩展“VS Code Markdown”共享。如果定义了一个或多个 CSS 文件,它们将用作生成的 HTML 文件中的外部嵌入式样式表,具体取决于选项“markdown.extension.convertToHtml.embedCss”。用户负责提供 CSS 文件本身。

也许最有趣的“将 Markdown 转换为 HTML”选项是“markdown.extension.convertToHtml.options.additionalPlugins”。它们的行为在扩展文档中进行了解释。

注意一个特定的插件包,“markdown-it-table-of-contents”。它有自己的选项。这些选项透明地从“settings.json”传递到扩展代码。上面显示的代码片段解释了如何实现。

设置修改过程在此处解释。在 Visual Studio Code 中打开原始文件并以文本格式编辑,以保持完全控制并利用 IntelliSense 的优势,这在某种程度上是有意义的。

一些设置被复制到本地文件“.vscode/settings.json”中,但未修改,而是为了方便实验而添加;其中大部分已从此代码示例中省略。请注意两个关键项:“editor.wordBasedSuggestions”和“editor.rulers”。第一个是解决显示所有先前输入的单词作为 IntelliSense 建议的方法。第二个在第 79 列绘制了一条垂直线,这对于代码示例很重要:CodeProject 要求所有代码示例行都在 80 列范围内。

整合所有:烹饪食谱

  1. 安装Visual Studio Code;它也将安装 Microsoft 的内置扩展“VS Code Markdown”;
  2. 安装本文中描述的 Visual Studio Code 扩展“将 Markdown 转换为 HTML”;
  3. 安装 Visual Studio Code 扩展“代码拼写检查器”;
  4. 使用 Visual Studio Code 打开带有 Markdown 文档的文件夹;在本文提供的代码示例中,这是“.vscode”所在的根目录。

另外,我还建议安装 Visual Studio Code 扩展“编号书签”;它们对于编辑非常有用。

一切准备就绪。现在你可以测试它了。创建并打开一些 .md 文件。在编辑器中,你将获得以下命令:

  1. “Ctrl+空格”:查看 Markdown 语法中的建议列表并选择要输入的片段;
  2. “Ctrl+.”:如果“代码拼写检查器”扩展已激活,问题词将带有下划线,并且光标位于该词内,获取快速修复建议列表并选择更正的词;
  3. “Ctrl+Shift+V”:打开预览;
  4. “Ctrl+K V”:并排打开预览;
  5. “Ctrl+/”:注释掉或取消注释当前行或选定内容;
  6. 编辑器上下文菜单:“Markdown: 转换为 HTML”,转换当前激活编辑器中加载的文件。
  7. 编辑器上下文菜单,资源管理器上下文菜单:“Markdown: 将工作区中所有 .md 文件转换为 HTML”。

要获取完整的 Markdown 相关命令列表,请按 F1 或 Ctrl+Shift+P 并输入“Markdown”;根据需要查找其他命令。

可以将基于 Markdown 的文章调整以符合 CodeProject 的要求。

我建议

使用@toc自动构建目录,并避免自动编号,因为HTML导航已经足够。使用从“##”级别开始的常规Markdown标题,即“##”、“###”等。不要使用#,而应使用带有扩展标记{title}…的常规段落。

对于“## Contents”部分本身,请使用扩展标记{no-toc}。这样,此标题将不会出现在目录中。

标题的自动生成的 id 值将自动用于目录中。如果您需要在其他地方引用标题元素,请至少生成一次 HTML,然后查看它在目录中的引用方式,然后复制/粘贴,例如:as it is explained [in this section](#heading-usage)…

使用围栏代码块来显示源代码。添加 langid 属性,这可以通过新的 Markdown 扩展“属性”语法实现。例如:

~~~ {lang=C#}{id=code-csharp-usage-sample}
class MyClass { /* ... */ }
~~~

属性lang将用于正确的语法高亮显示,id可用于引用源代码示例。例如:在[上面所示的示例](#code-csharp-usage-sample)中…

添加常规 HTML 注释:在“目录”之前,添加<!-- copy to CodeProject from this point -->,在末尾添加<!-- copy to CodeProject to this point -->。生成 HTML 文件后,找到这两个标记,复制它们之间的文本,然后粘贴到 CodeProject 文章提交向导的源元素中。一切都将正确,无需手动编辑。

版本

初始版本

2017年6月29日

版本 1.0.3

2017年7月4日

代码已从头重写,并作为 Visual Studio Code 扩展“convert-markdown-to-html”发布到 Visual Studio Marketplace。

文章几乎完全重写。

版本 1.1.0

2017年7月5日

增加了对多个 CSS 文件的支持以及在 HTML 中嵌入 CSS 代码的选项。

版本 1.1.1

2017年7月5日

修复了 CSS 文件相对路径中的一个 bug。

版本 2.1.0

2017年7月8日

Visual Studio Code 扩展标题修改为“可扩展 Markdown 转换器”。

自 v. 2.0.0 起,用户可以通过安装npm 包注册表大量可用的任何“markdown-it”插件来扩展 Markdown 功能。所有包都可以从一个来源配置:“settings.json”,在用户或工作区级别

也就是说,不再需要不同的 Markdown 扩展。只需将内置扩展与可扩展 Markdown 转换器结合使用就足够了。所有所需功能都可以通过可用的插件和统一的配置设计来组装。

此版本发布后,由于这些彻底的改进,文章进行了大量更新。

版本 2.2.0

2017年7月9日

  • 实现与生成的 HTML 相同的预览
  • 提高性能(markdown-it 模块设置和设置的惰性评估)
  • 在设置中,实现了“outputPath”选项

Markdown 代码示例已更新。现在它们演示了附加插件和新开发的技术,例如标题检测

版本 3.0.0

2017年7月13日

  • 实现了可扩展的 Markdown 语法着色,适用于每个插件语法,用户可配置。
  • 实现了用户可配置的输入 Markdown 文档标题语法着色。
  • 实现了文件包含,具有用户可配置的语法着色。
  • 添加了按键绑定,以覆盖“VS Code Markdown”预览按键绑定。

相对于版本 2.3.0 的变化:包含文件的位置现在相对于 Markdown 文档的位置。

版本 4.3.0

自 3.0.0 版以来的累计变更

  • 在外部“markdown-it-named-headers”和“markdown-it-table-of-contents”中发现了严重错误:生成的标题 ID 值不是唯一的。作为临时解决方案,这些外部模块已被删除,并替换为扩展中嵌入的模块。

主要版本号递增,因为目录的默认 Markdown 模式已更改为 ^\[\]\(toc\),这意味着行首的“[](toc)”。这可能会破坏与使用“[[toc]]”的现有 Markdown 文档的向后兼容性,但很容易修复。

  • 新增设置选项:“markdown.extension.convertToHtml.tocIncludeLevels”,默认值 [1, 2, 3, 4, 5, 6]
  • 新增设置选项:“markdown.extension.convertToHtml.tocContainerClass”,默认值“toc”
  • 新增设置选项:“markdown.extension.convertToHtml.tocListType”,默认值“ul”
  • 目录和标题id属性的生成已统一到单个嵌入模块中。
  • 新增设置选项:“markdown.extension.convertToHtml.options.headingIdPrefix”,默认值“heading.”
  • 修复了标题id属性生成中的一个 bug;现在,每次渲染时都会重置所使用的id值集合。
  • 添加了徽标

版本 5.8.1

自 4.3.0 版本以来的累计变更

  • 添加了基于嵌入式 markdown-it 插件的高级 Markdown 扩展功能。
    • 语法扩展:用于标记要从 TOC 中排除的标题的标签。
    • 扩展了选项,可以在全局或按 TOC 级别选择 TOC 中的 ulol 元素。
    • 扩展了选项,可以在全局或按 TOC 级别向 TOC 列表元素添加 HTML 属性集。
    • 可选的用户可配置自动编号,功能丰富。
  • 为转换和预览命令以及编辑器标题的菜单项添加了图像。
  • 改进的图片
  • 在嵌入式 markdown-it 插件的旧代码中发现了严重 bug,因此该模块已完全重写。
  • 改进了文档内自动编号规范中无效 JSON 的处理。
  • 引入了文档内自动编号选项规范的新简化格式。
  • 改进了 ID 生成。

新的当前功能集提供了对标题和目录的全面处理。使用了两个相关的用户可配置语法扩展:“TOC”(默认情况下,段落开头的 [](toc))和“从 TOC 中排除”(默认情况下,标题中任意位置的 {no-toc})。id 值及其从 TOC 的 <a href...> 锚点引用的创建源自单个来源,并保证了引用完整性和唯一性。除非禁用该功能,否则标记本身将从输出中删除。

版本 7.0.0

VSCode 扩展 API 至少两次破坏了向后兼容性。此版本恢复了与最新 VSCode 版本(撰写本文时为 1.54.3)的兼容性。

版本 7.2.0

改进了高级自动编号功能。

版本 8.0.0

恢复了附加插件功能,重新测试了所有功能,添加了少量修复或改进,完善了测试/演示文档。

结论

在进行了几次解决方案原型设计并创建了功能完善的扩展之后,我觉得结果令人满意。

尽管 Visual Studio Code 看起来仍需一些稳定时间,但它的代码和 API 看起来相当健康且易于维护。它已经为我们提供了一个出色的开发工具,现在也可以愉快且安心地用于文章撰写。

写作愉快!

© . All rights reserved.