Generate manual overviews from github_docs

This commit is contained in:
Marc Froehlich
2026-07-06 03:02:49 +02:00
parent 248a7c0462
commit dfe5e28877
54 changed files with 2857 additions and 31 deletions
+53
View File
@@ -1,6 +1,59 @@
const fs = require("fs");
const path = require("path");
const markdownIt = require("markdown-it");
const markdownItAnchor = require("markdown-it-anchor");
module.exports = function (eleventyConfig) {
eleventyConfig.addPassthroughCopy({ "src/assets": "assets" });
const md = markdownIt({
html: true,
linkify: true,
typographer: true
}).use(markdownItAnchor, {
permalink: markdownItAnchor.permalink.headerLink()
});
eleventyConfig.addFilter("markdown", function (value) {
return md.render(value || "");
});
eleventyConfig.addCollection("manualPages", function () {
const docsDir = path.join(__dirname, "..", "github_docs");
if (!fs.existsSync(docsDir)) {
return [];
}
return fs.readdirSync(docsDir)
.filter(file => /^(en|de)-.+\.md$/.test(file))
.map(file => {
const fullPath = path.join(docsDir, file);
const raw = fs.readFileSync(fullPath, "utf8");
const lang = file.startsWith("de-") ? "de" : "en";
const slug = file
.replace(/^en-/, "")
.replace(/^de-/, "")
.replace(/\.md$/, "")
.toLowerCase();
const title = file
.replace(/^en-/, "")
.replace(/^de-/, "")
.replace(/\.md$/, "")
.replace(/-/g, " ");
return {
file,
lang,
slug,
title,
content: raw
};
});
});
return {
dir: {
input: "src",
+34
View File
@@ -113,4 +113,38 @@ nav a:hover {
.site-footer {
padding: 40px 7vw;
color: var(--muted);
}
.manual-content {
max-width: 980px;
}
.manual-content h1,
.manual-content h2,
.manual-content h3 {
line-height: 1.2;
}
.manual-content pre {
overflow-x: auto;
padding: 16px;
border-radius: 14px;
background: #020617;
}
.manual-content code {
color: #bae6fd;
}
.manual-content table {
width: 100%;
border-collapse: collapse;
overflow-x: auto;
}
.manual-content th,
.manual-content td {
border-bottom: 1px solid rgba(148, 163, 184, 0.22);
padding: 10px;
text-align: left;
}
+91 -4
View File
@@ -43,10 +43,97 @@
<section class="section">
<div class="grid">
<div class="card"><h3>Installation</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/de-Installation">Installationsanleitung öffnen</a></p></div>
<div class="card"><h3>Konfiguration</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/de-Konfiguration">Konfiguration öffnen</a></p></div>
<div class="card"><h3>Funktionen</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/de-Funktionen">Funktionsübersicht öffnen</a></p></div>
<div class="card"><h3>AirScout-Integration</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/de-AirScout-Integration">AirScout-Anleitung öffnen</a></p></div>
<div class="card">
<h3><a href="/manual/de/airscout-integration/">AirScout Integration</a></h3>
<p>Handbuchseite „AirScout Integration“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/benutzeroberflaeche/">Benutzeroberflaeche</a></h3>
<p>Handbuchseite „Benutzeroberflaeche“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/changelog/">Changelog</a></h3>
<p>Handbuchseite „Changelog“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/dx-cluster-server/">DX Cluster Server</a></h3>
<p>Handbuchseite „DX Cluster Server“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/funktionen/">Funktionen</a></h3>
<p>Handbuchseite „Funktionen“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/home/">Home</a></h3>
<p>Handbuchseite „Home“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/installation/">Installation</a></h3>
<p>Handbuchseite „Installation“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/konfiguration/">Konfiguration</a></h3>
<p>Handbuchseite „Konfiguration“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/log-synchronisation/">Log Synchronisation</a></h3>
<p>Handbuchseite „Log Synchronisation“ öffnen.</p>
</div>
<div class="card">
<h3><a href="/manual/de/makros-und-variablen/">Makros und Variablen</a></h3>
<p>Handbuchseite „Makros und Variablen“ öffnen.</p>
</div>
</div>
</section>
</main>
+91 -4
View File
@@ -43,10 +43,97 @@
<section class="section">
<div class="grid">
<div class="card"><h3>Installation</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/en-Installation">Open installation guide</a></p></div>
<div class="card"><h3>Configuration</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/en-Configuration">Open configuration guide</a></p></div>
<div class="card"><h3>Features</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/en-Features">Open feature documentation</a></p></div>
<div class="card"><h3>AirScout Integration</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/en-AirScout-Integration">Open AirScout guide</a></p></div>
<div class="card">
<h3><a href="/manual/en/airscout-integration/">AirScout Integration</a></h3>
<p>Open the AirScout Integration manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/changelog/">Changelog</a></h3>
<p>Open the Changelog manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/configuration/">Configuration</a></h3>
<p>Open the Configuration manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/dx-cluster-server/">DX Cluster Server</a></h3>
<p>Open the DX Cluster Server manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/features/">Features</a></h3>
<p>Open the Features manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/home/">Home</a></h3>
<p>Open the Home manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/installation/">Installation</a></h3>
<p>Open the Installation manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/log-sync/">Log Sync</a></h3>
<p>Open the Log Sync manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/macros-and-variables/">Macros and Variables</a></h3>
<p>Open the Macros and Variables manual page.</p>
</div>
<div class="card">
<h3><a href="/manual/en/user-interface/">User Interface</a></h3>
<p>Open the User Interface manual page.</p>
</div>
</div>
</section>
</main>
+3
View File
@@ -39,4 +39,7 @@
<url>
<loc>https://kst4contest.hamradioonline.de/manual/de/</loc>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/de/airscout-integration/</loc>
</url>
</urlset>
+35 -7
View File
@@ -222,6 +222,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT",
"peer": true
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT",
"peer": true
},
"node_modules/a-sync-waterfall": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
@@ -286,7 +311,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/asap": {
@@ -1029,7 +1053,6 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz",
"integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==",
"dev": true,
"funding": [
{
"type": "github",
@@ -1087,7 +1110,6 @@
"version": "14.3.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz",
"integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==",
"dev": true,
"funding": [
{
"type": "github",
@@ -1111,11 +1133,20 @@
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it-anchor": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-9.2.0.tgz",
"integrity": "sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==",
"license": "Unlicense",
"peerDependencies": {
"@types/markdown-it": "*",
"markdown-it": "*"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -1128,7 +1159,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true,
"license": "MIT"
},
"node_modules/mime": {
@@ -1400,7 +1430,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -1618,7 +1647,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"dev": true,
"license": "MIT"
},
"node_modules/unpipe": {
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
+15
View File
@@ -0,0 +1,15 @@
# Installation
> `npm install --save @types/linkify-it`
# Summary
This package contains type definitions for linkify-it (https://github.com/markdown-it/linkify-it).
# Details
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/linkify-it.
### Additional Details
* Last updated: Wed, 01 May 2024 18:07:45 GMT
* Dependencies: none
# Credits
These definitions were written by [Lindsey Smith](https://github.com/praxxis), [Robert Coie](https://github.com/rapropos/typed-linkify-it), [Alex Plumb](https://github.com/alexplumb), and [Rafa Gares](https://github.com/ragafus).
+174
View File
@@ -0,0 +1,174 @@
export type Validate = (text: string, pos: number, self: LinkifyIt) => number | boolean;
export interface FullRule {
validate: string | RegExp | Validate;
normalize?: ((match: Match) => void) | undefined;
}
export type Rule = string | FullRule;
/**
* An object, where each key/value describes protocol/rule:
*
* - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:`
* for example). `linkify-it` makes sure that prefix is not preceded with
* alphanumeric char and symbols. Only whitespaces and punctuation allowed.
* - __value__ - rule to check tail after link prefix
* - _String_ - just alias to existing rule
* - _Object_
* - _validate_ - validator function (should return matched length on success),
* or `RegExp`.
* - _normalize_ - optional function to normalize text & url of matched result
* (for example, for `@twitter` mentions).
*/
export interface SchemaRules {
[schema: string]: Rule;
}
export interface Options {
/**
* recognize URL-s without `http(s):` prefix. Default `true`.
*/
fuzzyLink?: boolean | undefined;
/**
* allow IPs in fuzzy links above. Can conflict with some texts
* like version numbers. Default `false`.
*/
fuzzyIP?: boolean | undefined;
/**
* recognize emails without `mailto:` prefix. Default `true`.
*/
fuzzyEmail?: boolean | undefined;
}
/**
* Match result. Single element of array, returned by {@link LinkifyIt#match}.
*/
declare class Match {
constructor(self: LinkifyIt, shift: number);
/**
* First position of matched string.
*/
index: number;
/**
* Next position after matched string.
*/
lastIndex: number;
/**
* Matched string.
*/
raw: string;
/**
* Prefix (protocol) for matched string.
*/
schema: string;
/**
* Normalized text of matched string.
*/
text: string;
/**
* Normalized url of matched string.
*/
url: string;
}
export type { Match };
declare class LinkifyIt {
/**
* new LinkifyIt(schemas, options)
* - schemas (Object): Optional. Additional schemas to validate (prefix/validator)
* - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false }
*
* Creates new linkifier instance with optional additional schemas.
* Can be called without `new` keyword for convenience.
*
* By default understands:
*
* - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links
* - "fuzzy" links and emails (example.com, foo@bar.com).
*/
constructor(schemas?: SchemaRules | Options, options?: Options);
// Use overloads to provide contextual typing to `FullRule.normalize`, which is ambiguous with string.normalize
/**
* Add new rule definition. See constructor description for details.
*
* @param schema rule name (fixed pattern prefix)
* @param definition schema definition
*/
add(schema: string, definition: string): this;
add(schema: string, definition: FullRule | null): this;
/**
* Set recognition options for links without schema.
*/
set(options: Options): this;
/**
* Searches linkifiable pattern and returns `true` on success or `false` on fail.
*/
test(text: string): boolean;
/**
* Very quick check, that can give false positives. Returns true if link MAY BE
* can exists. Can be used for speed optimization, when you need to check that
* link NOT exists.
*/
pretest(text: string): boolean;
/**
* Similar to {@link LinkifyIt#test} but checks only specific protocol tail exactly
* at given position. Returns length of found pattern (0 on fail).
*
* @param text text to scan
* @param schema rule (schema) name
* @param pos text offset to check from
*/
testSchemaAt(text: string, schema: string, pos: number): number;
/**
* Returns array of found link descriptions or `null` on fail. We strongly
* recommend to use {@link LinkifyIt#test} first, for best speed.
*/
match(text: string): Match[] | null;
/**
* Returns fully-formed (not fuzzy) link if it starts at the beginning
* of the string, and null otherwise.
*/
matchAtStart(text: string): Match | null;
/**
* Load (or merge) new tlds list. Those are user for fuzzy links (without prefix)
* to avoid false positives. By default this algorythm used:
*
* - hostname with any 2-letter root zones are ok.
* - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф
* are ok.
* - encoded (`xn--...`) root zones are ok.
*
* If list is replaced, then exact match for 2-chars root zones will be checked.
*
* @param list list of tlds
* @param keepOld merge with current list if `true` (`false` by default)
*/
tlds(list: string | string[], keepOld?: boolean): this;
/**
* Default normalizer (if schema does not define it's own).
*/
normalize(match: Match): void;
/**
* Override to modify basic RegExp-s.
*/
onCompile(): void;
re: {
[key: string]: RegExp;
};
}
export default LinkifyIt;
+3
View File
@@ -0,0 +1,3 @@
import LinkifyIt = require("./build/index.cjs.js");
export = LinkifyIt;
+50
View File
@@ -0,0 +1,50 @@
{
"name": "@types/linkify-it",
"version": "5.0.0",
"description": "TypeScript definitions for linkify-it",
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/linkify-it",
"license": "MIT",
"contributors": [
{
"name": "Lindsey Smith",
"githubUsername": "praxxis",
"url": "https://github.com/praxxis"
},
{
"name": "Robert Coie",
"url": "https://github.com/rapropos/typed-linkify-it"
},
{
"name": "Alex Plumb",
"githubUsername": "alexplumb",
"url": "https://github.com/alexplumb"
},
{
"name": "Rafa Gares",
"githubUsername": "ragafus",
"url": "https://github.com/ragafus"
}
],
"main": "",
"types": "index.d.ts",
"exports": {
".": {
"import": "./index.d.mts",
"require": "./build/index.cjs.d.ts"
},
"./*": {
"import": "./*",
"require": "./*"
},
"./package.json": "./package.json"
},
"repository": {
"type": "git",
"url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
"directory": "types/linkify-it"
},
"scripts": {},
"dependencies": {},
"typesPublisherContentHash": "884f8e9f827ff23c410016d9edc6e2410efbd2555c6e9e6c0ed734edd4145036",
"typeScriptVersion": "4.7"
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
+15
View File
@@ -0,0 +1,15 @@
# Installation
> `npm install --save @types/markdown-it`
# Summary
This package contains type definitions for markdown-it (https://github.com/markdown-it/markdown-it).
# Details
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/markdown-it.
### Additional Details
* Last updated: Thu, 25 Jul 2024 05:07:29 GMT
* Dependencies: [@types/linkify-it](https://npmjs.com/package/@types/linkify-it), [@types/mdurl](https://npmjs.com/package/@types/mdurl)
# Credits
These definitions were written by [York Yao](https://github.com/plantain-00), [Robert Coie](https://github.com/rapropos), [duduluu](https://github.com/duduluu), and [Piotr Błażejewicz](https://github.com/peterblazejewicz).
+1
View File
@@ -0,0 +1 @@
export { default, Options, PluginSimple, PluginWithOptions, PluginWithParams, PresetName } from "./lib/index.mjs";
+3
View File
@@ -0,0 +1,3 @@
import MarkdownIt = require("./dist/index.cjs.js");
export = MarkdownIt;
+7
View File
@@ -0,0 +1,7 @@
/**
* List of valid html blocks names, according to commonmark spec
* @see https://spec.commonmark.org/0.30/#html-blocks
*/
declare const htmlBlocks: string[];
export default htmlBlocks;
+2
View File
@@ -0,0 +1,2 @@
export const HTML_TAG_RE: RegExp;
export const HTML_OPEN_CLOSE_TAG_RE: RegExp;
+62
View File
@@ -0,0 +1,62 @@
import * as mdurl from "mdurl";
// import * as ucmicro from "uc.micro";
export const lib: {
mdurl: typeof mdurl;
ucmicro: any;
};
/**
* Merge objects
*/
export function assign(obj: any, ...from: any[]): any;
export function isString(obj: any): obj is string;
export function has(obj: any, key: keyof any): boolean;
export function unescapeMd(str: string): string;
export function unescapeAll(str: string): string;
export function isValidEntityCode(c: number): boolean;
export function fromCodePoint(c: number): string;
export function escapeHtml(str: string): string;
/**
* Remove element from array and put another array at those position.
* Useful for some operations with tokens
*/
export function arrayReplaceAt<T>(src: T[], pos: number, newElements: T[]): T[];
export function isSpace(code: number): boolean;
/**
* Zs (unicode class) || [\t\f\v\r\n]
*/
export function isWhiteSpace(code: number): boolean;
/**
* Markdown ASCII punctuation characters.
*
* !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~
*
* Don't confuse with unicode punctuation !!! It lacks some chars in ascii range.
*
* @see http://spec.commonmark.org/0.15/#ascii-punctuation-character
*/
export function isMdAsciiPunct(code: number): boolean;
/**
* Currently without astral characters support.
*/
export function isPunctChar(ch: string): boolean;
export function escapeRE(str: string): string;
/**
* Helper to unify [reference labels].
*/
export function normalizeReference(str: string): string;
+5
View File
@@ -0,0 +1,5 @@
import parseLinkDestination from "./parse_link_destination.mjs";
import parseLinkLabel from "./parse_link_label.mjs";
import parseLinkTitle from "./parse_link_title.mjs";
export { parseLinkDestination, parseLinkLabel, parseLinkTitle };
@@ -0,0 +1,7 @@
export interface ParseLinkDestinationResult {
ok: boolean;
pos: number;
str: string;
}
export default function parseLinkDestination(str: string, start: number, max: number): ParseLinkDestinationResult;
@@ -0,0 +1,3 @@
import StateInline from "../rules_inline/state_inline.mjs";
export default function parseLinkLabel(state: StateInline, start: number, disableNested?: boolean): number;
@@ -0,0 +1,29 @@
export interface ParseLinkTitleResult {
/**
* if `true`, this is a valid link title
*/
ok: boolean;
/**
* if `true`, this link can be continued on the next line
*/
can_continue: boolean;
/**
* if `ok`, it's the position of the first character after the closing marker
*/
pos: number;
/**
* if `ok`, it's the unescaped title
*/
str: string;
/**
* expected closing marker character code
*/
marker: number;
}
export default function parseLinkTitle(
str: string,
start: number,
max: number,
prev_state?: ParseLinkTitleResult,
): ParseLinkTitleResult;
+404
View File
@@ -0,0 +1,404 @@
import LinkifyIt from "linkify-it";
import * as utils from "./common/utils.mjs";
import * as helpers from "./helpers/index.mjs";
import ParserBlock from "./parser_block.mjs";
import ParserCore from "./parser_core.mjs";
import ParserInline from "./parser_inline.mjs";
import Renderer from "./renderer.mjs";
import Token from "./token.mjs";
/**
* MarkdownIt provides named presets as a convenience to quickly
* enable/disable active syntax rules and options for common use cases.
*
* - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) -
* configures parser to strict [CommonMark](http://commonmark.org/) mode.
* - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) -
* similar to GFM, used when no preset name given. Enables all available rules,
* but still without html, typographer & autolinker.
* - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) -
* all rules disabled. Useful to quickly setup your config via `.enable()`.
* For example, when you need only `bold` and `italic` markup and nothing else.
*/
export type PresetName = "default" | "zero" | "commonmark";
export interface Options {
/**
* Set `true` to enable HTML tags in source. Be careful!
* That's not safe! You may need external sanitizer to protect output from XSS.
* It's better to extend features via plugins, instead of enabling HTML.
* @default false
*/
html?: boolean | undefined;
/**
* Set `true` to add '/' when closing single tags
* (`<br />`). This is needed only for full CommonMark compatibility. In real
* world you will need HTML output.
* @default false
*/
xhtmlOut?: boolean | undefined;
/**
* Set `true` to convert `\n` in paragraphs into `<br>`.
* @default false
*/
breaks?: boolean | undefined;
/**
* CSS language class prefix for fenced blocks.
* Can be useful for external highlighters.
* @default 'language-'
*/
langPrefix?: string | undefined;
/**
* Set `true` to autoconvert URL-like text to links.
* @default false
*/
linkify?: boolean | undefined;
/**
* Set `true` to enable [some language-neutral replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) +
* quotes beautification (smartquotes).
* @default false
*/
typographer?: boolean | undefined;
/**
* Double + single quotes replacement
* pairs, when typographer enabled and smartquotes on. For example, you can
* use `'«»„“'` for Russian, `'„“‚‘'` for German, and
* `['«\xA0', '\xA0»', '\xA0', '\xA0']` for French (including nbsp).
* @default '“”‘’'
*/
quotes?: string | string[];
/**
* Highlighter function for fenced code blocks.
* Highlighter `function (str, lang, attrs)` should return escaped HTML. It can
* also return empty string if the source was not changed and should be escaped
* externally. If result starts with <pre... internal wrapper is skipped.
* @default null
*/
highlight?: ((str: string, lang: string, attrs: string) => string) | null | undefined;
}
export type PluginSimple = (md: MarkdownIt) => void;
export type PluginWithOptions<T = any> = (md: MarkdownIt, options?: T) => void;
export type PluginWithParams = (md: MarkdownIt, ...params: any[]) => void;
interface MarkdownItConstructor {
new(): MarkdownIt;
new(presetName: PresetName, options?: Options): MarkdownIt;
new(options: Options): MarkdownIt;
(): MarkdownIt;
(presetName: PresetName, options?: Options): MarkdownIt;
(options: Options): MarkdownIt;
}
interface MarkdownIt {
/**
* Instance of {@link ParserInline}. You may need it to add new rules when
* writing plugins. For simple rules control use {@link MarkdownIt.disable} and
* {@link MarkdownIt.enable}.
*/
readonly inline: ParserInline;
/**
* Instance of {@link ParserBlock}. You may need it to add new rules when
* writing plugins. For simple rules control use {@link MarkdownIt.disable} and
* {@link MarkdownIt.enable}.
*/
readonly block: ParserBlock;
/**
* Instance of {@link Core} chain executor. You may need it to add new rules when
* writing plugins. For simple rules control use {@link MarkdownIt.disable} and
* {@link MarkdownIt.enable}.
*/
readonly core: ParserCore;
/**
* Instance of {@link Renderer}. Use it to modify output look. Or to add rendering
* rules for new token types, generated by plugins.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')();
*
* function myToken(tokens, idx, options, env, self) {
* //...
* return result;
* };
*
* md.renderer.rules['my_token'] = myToken
* ```
*
* See {@link Renderer} docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js).
*/
readonly renderer: Renderer;
/**
* [linkify-it](https://github.com/markdown-it/linkify-it) instance.
* Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js)
* rule.
*/
readonly linkify: LinkifyIt;
/**
* Link validation function. CommonMark allows too much in links. By default
* we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas
* except some embedded image types.
*
* You can change this behaviour:
*
* ```javascript
* var md = require('markdown-it')();
* // enable everything
* md.validateLink = function () { return true; }
* ```
*/
validateLink(url: string): boolean;
/**
* Function used to encode link url to a machine-readable format,
* which includes url-encoding, punycode, etc.
*/
normalizeLink(url: string): string;
/**
* Function used to decode link url to a human-readable format`
*/
normalizeLinkText(url: string): string;
readonly utils: typeof utils;
readonly helpers: typeof helpers;
readonly options: Options;
/**
* *chainable*
*
* Set parser options (in the same format as in constructor). Probably, you
* will never need it, but you can change options after constructor call.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')()
* .set({ html: true, breaks: true })
* .set({ typographer: true });
* ```
*
* __Note:__ To achieve the best possible performance, don't modify a
* `markdown-it` instance options on the fly. If you need multiple configurations
* it's best to create multiple instances and initialize each with separate
* config.
*/
set(options: Options): this;
/**
* *chainable*, *internal*
*
* Batch load of all options and compenent settings. This is internal method,
* and you probably will not need it. But if you with - see available presets
* and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets)
*
* We strongly recommend to use presets instead of direct config loads. That
* will give better compatibility with next versions.
*/
configure(presets: PresetName): this;
/**
* *chainable*
*
* Enable list or rules. It will automatically find appropriate components,
* containing rules with given names. If rule not found, and `ignoreInvalid`
* not set - throws exception.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')()
* .enable(['sub', 'sup'])
* .disable('smartquotes');
* ```
*
* @param list rule name or list of rule names to enable
* @param ignoreInvalid set `true` to ignore errors when rule not found.
*/
enable(list: string | string[], ignoreInvalid?: boolean): this;
/**
* *chainable*
*
* The same as {@link MarkdownIt.enable}, but turn specified rules off.
*
* @param list rule name or list of rule names to disable.
* @param ignoreInvalid set `true` to ignore errors when rule not found.
*/
disable(list: string | string[], ignoreInvalid?: boolean): this;
/**
* *chainable*
*
* Load specified plugin with given params into current parser instance.
* It's just a sugar to call `plugin(md, params)` with curring.
*
* ##### Example
*
* ```javascript
* var iterator = require('markdown-it-for-inline');
* var md = require('markdown-it')()
* .use(iterator, 'foo_replace', 'text', function (tokens, idx) {
* tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar');
* });
* ```
*/
use(plugin: PluginSimple): this;
use<T = any>(plugin: PluginWithOptions<T>, options?: T): this;
use(plugin: PluginWithParams, ...params: any[]): this;
/**
* *internal*
*
* Parse input string and returns list of block tokens (special token type
* "inline" will contain list of inline tokens). You should not call this
* method directly, until you write custom renderer (for example, to produce
* AST).
*
* `env` is used to pass data between "distributed" rules and return additional
* metadata like reference info, needed for the renderer. It also can be used to
* inject data in specific cases. Usually, you will be ok to pass `{}`,
* and then pass updated object to renderer.
*
* @param src source string
* @param env environment sandbox
*/
parse(src: string, env: any): Token[];
/**
* Render markdown string into html. It does all magic for you :).
*
* `env` can be used to inject additional metadata (`{}` by default).
* But you will not need it with high probability. See also comment
* in {@link MarkdownIt.parse}.
*
* @param src source string
* @param env environment sandbox
*/
render(src: string, env?: any): string;
/**
* *internal*
*
* The same as {@link MarkdownIt.parse} but skip all block rules. It returns the
* block tokens list with the single `inline` element, containing parsed inline
* tokens in `children` property. Also updates `env` object.
*
* @param src source string
* @param env environment sandbox
*/
parseInline(src: string, env: any): Token[];
/**
* Similar to {@link MarkdownIt.render} but for single paragraph content. Result
* will NOT be wrapped into `<p>` tags.
*
* @param src source string
* @param env environment sandbox
*/
renderInline(src: string, env?: any): string;
}
/**
* Main parser/renderer class.
*
* ##### Usage
*
* ```javascript
* // node.js, "classic" way:
* var MarkdownIt = require('markdown-it'),
* md = new MarkdownIt();
* var result = md.render('# markdown-it rulezz!');
*
* // node.js, the same, but with sugar:
* var md = require('markdown-it')();
* var result = md.render('# markdown-it rulezz!');
*
* // browser without AMD, added to "window" on script load
* // Note, there are no dash.
* var md = window.markdownit();
* var result = md.render('# markdown-it rulezz!');
* ```
*
* Single line rendering, without paragraph wrap:
*
* ```javascript
* var md = require('markdown-it')();
* var result = md.renderInline('__markdown-it__ rulezz!');
* ```
*
* ##### Example
*
* ```javascript
* // commonmark mode
* var md = require('markdown-it')('commonmark');
*
* // default mode
* var md = require('markdown-it')();
*
* // enable everything
* var md = require('markdown-it')({
* html: true,
* linkify: true,
* typographer: true
* });
* ```
*
* ##### Syntax highlighting
*
* ```js
* var hljs = require('highlight.js') // https://highlightjs.org/
*
* var md = require('markdown-it')({
* highlight: function (str, lang) {
* if (lang && hljs.getLanguage(lang)) {
* try {
* return hljs.highlight(lang, str, true).value;
* } catch (__) {}
* }
*
* return ''; // use external default escaping
* }
* });
* ```
*
* Or with full wrapper override (if you need assign class to `<pre>`):
*
* ```javascript
* var hljs = require('highlight.js') // https://highlightjs.org/
*
* // Actual default values
* var md = require('markdown-it')({
* highlight: function (str, lang) {
* if (lang && hljs.getLanguage(lang)) {
* try {
* return '<pre class="hljs"><code>' +
* hljs.highlight(lang, str, true).value +
* '</code></pre>';
* } catch (__) {}
* }
*
* return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
* }
* });
* ```
*/
declare const MarkdownIt: MarkdownItConstructor;
export default MarkdownIt;
+25
View File
@@ -0,0 +1,25 @@
import MarkdownIt from "./index.mjs";
import Ruler from "./ruler.mjs";
import StateBlock from "./rules_block/state_block.mjs";
import Token from "./token.mjs";
export type RuleBlock = (state: StateBlock, startLine: number, endLine: number, silent: boolean) => boolean;
export default class ParserBlock {
/**
* {@link Ruler} instance. Keep configuration of block rules.
*/
ruler: Ruler<RuleBlock>;
/**
* Generate tokens for input range
*/
tokenize(state: StateBlock, startLine: number, endLine: number): void;
/**
* Process input string and push block tokens into `outTokens`
*/
parse(str: string, md: MarkdownIt, env: any, outTokens: Token[]): void;
State: typeof StateBlock;
}
+18
View File
@@ -0,0 +1,18 @@
import Ruler from "./ruler.mjs";
import StateCore from "./rules_core/state_core.mjs";
export type RuleCore = (state: StateCore) => void;
export default class Core {
/**
* {@link Ruler} instance. Keep configuration of core rules.
*/
ruler: Ruler<RuleCore>;
/**
* Executes core chain rules.
*/
process(state: StateCore): void;
State: typeof StateCore;
}
+38
View File
@@ -0,0 +1,38 @@
import MarkdownIt from "./index.mjs";
import Ruler from "./ruler.mjs";
import StateInline from "./rules_inline/state_inline.mjs";
import Token from "./token.mjs";
export type RuleInline = (state: StateInline, silent: boolean) => boolean;
export type RuleInline2 = (state: StateInline) => boolean;
export default class ParserInline {
/**
* {@link Ruler} instance. Keep configuration of inline rules.
*/
ruler: Ruler<RuleInline>;
/**
* {@link Ruler} instance. Second ruler used for post-processing
* (e.g. in emphasis-like rules).
*/
ruler2: Ruler<RuleInline2>;
/**
* Skip single token by running all rules in validation mode;
* returns `true` if any rule reported success
*/
skipToken(state: StateInline): void;
/**
* Generate tokens for input range
*/
tokenize(state: StateInline): void;
/**
* Process input string and push inline tokens into `outTokens`
*/
parse(str: string, md: MarkdownIt, env: any, outTokens: Token[]): void;
State: typeof StateInline;
}
+96
View File
@@ -0,0 +1,96 @@
import { Options } from "./index.mjs";
import Token from "./token.mjs";
export type RenderRule = (tokens: Token[], idx: number, options: Options, env: any, self: Renderer) => string;
export interface RenderRuleRecord {
[type: string]: RenderRule | undefined;
code_inline?: RenderRule | undefined;
code_block?: RenderRule | undefined;
fence?: RenderRule | undefined;
image?: RenderRule | undefined;
hardbreak?: RenderRule | undefined;
softbreak?: RenderRule | undefined;
text?: RenderRule | undefined;
html_block?: RenderRule | undefined;
html_inline?: RenderRule | undefined;
}
export default class Renderer {
/**
* Creates new {@link Renderer} instance and fill {@link Renderer#rules} with defaults.
*/
constructor();
/**
* Contains render rules for tokens. Can be updated and extended.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')();
*
* md.renderer.rules.strong_open = function () { return '<b>'; };
* md.renderer.rules.strong_close = function () { return '</b>'; };
*
* var result = md.renderInline(...);
* ```
*
* Each rule is called as independent static function with fixed signature:
*
* ```javascript
* function my_token_render(tokens, idx, options, env, renderer) {
* // ...
* return renderedHTML;
* }
* ```
*
* @see https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.mjs
*/
rules: RenderRuleRecord;
/**
* Render token attributes to string.
*/
renderAttrs(token: Token): string;
/**
* Default token renderer. Can be overriden by custom function
* in {@link Renderer#rules}.
*
* @param tokens list of tokens
* @param idx token index to render
* @param options params of parser instance
*/
renderToken(tokens: Token[], idx: number, options: Options): string;
/**
* The same as {@link Renderer.render}, but for single token of `inline` type.
*
* @param tokens list of block tokens to render
* @param options params of parser instance
* @param env additional data from parsed input (references, for example)
*/
renderInline(tokens: Token[], options: Options, env: any): string;
/**
* Special kludge for image `alt` attributes to conform CommonMark spec.
* Don't try to use it! Spec requires to show `alt` content with stripped markup,
* instead of simple escaping.
*
* @param tokens list of block tokens to render
* @param options params of parser instance
* @param env additional data from parsed input (references, for example)
*/
renderInlineAsText(tokens: Token[], options: Options, env: any): string;
/**
* Takes token stream and generates HTML. Probably, you will never need to call
* this method directly.
*
* @param tokens list of block tokens to render
* @param options params of parser instance
* @param env additional data from parsed input (references, for example)
*/
render(tokens: Token[], options: Options, env: any): string;
}
+158
View File
@@ -0,0 +1,158 @@
export interface RuleOptions {
/**
* array with names of "alternate" chains.
*/
alt: string[];
}
/**
* Helper class, used by {@link MarkdownIt#core}, {@link MarkdownIt#block} and
* {@link MarkdownIt#inline} to manage sequences of functions (rules):
*
* - keep rules in defined order
* - assign the name to each rule
* - enable/disable rules
* - add/replace rules
* - allow assign rules to additional named chains (in the same)
* - caching lists of active rules
*
* You will not need use this class directly until write plugins. For simple
* rules control use {@link MarkdownIt.disable}, {@link MarkdownIt.enable} and
* {@link MarkdownIt.use}.
*/
export default class Ruler<T> {
constructor();
/**
* Replace rule by name with new function & options. Throws error if name not
* found.
*
* ##### Example
*
* Replace existing typographer replacement rule with new one:
*
* ```javascript
* var md = require('markdown-it')();
*
* md.core.ruler.at('replacements', function replace(state) {
* //...
* });
* ```
*
* @param name rule name to replace.
* @param fn new rule function.
* @param options new rule options (not mandatory).
*/
at(name: string, fn: T, options?: RuleOptions): void;
/**
* Add new rule to chain before one with given name.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')();
*
* md.block.ruler.before('paragraph', 'my_rule', function replace(state) {
* //...
* });
* ```
*
* @see {@link Ruler.after}, {@link Ruler.push}
*
* @param beforeName new rule will be added before this one.
* @param ruleName name of added rule.
* @param fn rule function.
* @param options rule options (not mandatory).
*/
before(beforeName: string, ruleName: string, fn: T, options?: RuleOptions): void;
/**
* Add new rule to chain after one with given name.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')();
*
* md.inline.ruler.after('text', 'my_rule', function replace(state) {
* //...
* });
* ```
*
* @see {@link Ruler.before}, {@link Ruler.push}
*
* @param afterName new rule will be added after this one.
* @param ruleName name of added rule.
* @param fn rule function.
* @param options rule options (not mandatory).
*/
after(afterName: string, ruleName: string, fn: T, options?: RuleOptions): void;
/**
* Push new rule to the end of chain.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')();
*
* md.core.ruler.push('my_rule', function replace(state) {
* //...
* });
* ```
*
* @see {@link Ruler.before}, {@link Ruler.after}
*
* @param ruleName name of added rule.
* @param fn rule function.
* @param options rule options (not mandatory).
*/
push(ruleName: string, fn: T, options?: RuleOptions): void;
/**
* Enable rules with given names. If any rule name not found - throw Error.
* Errors can be disabled by second param.
*
* Returns list of found rule names (if no exception happened).
*
* @see {@link Ruler.disable}, {@link Ruler.enableOnly}
*
* @param list list of rule names to enable.
* @param ignoreInvalid set `true` to ignore errors when rule not found.
*/
enable(list: string | string[], ignoreInvalid?: boolean): string[];
/**
* Enable rules with given names, and disable everything else. If any rule name
* not found - throw Error. Errors can be disabled by second param.
*
* @see {@link Ruler.disable}, {@link Ruler.enable}
*
* @param list list of rule names to enable (whitelist).
* @param ignoreInvalid set `true` to ignore errors when rule not found.
*/
enableOnly(list: string | string[], ignoreInvalid?: boolean): void;
/**
* Disable rules with given names. If any rule name not found - throw Error.
* Errors can be disabled by second param.
*
* Returns list of found rule names (if no exception happened).
*
* @see {@link Ruler.enable}, {@link Ruler.enableOnly}
*
* @param list list of rule names to disable.
* @param ignoreInvalid set `true` to ignore errors when rule not found.
*/
disable(list: string | string[], ignoreInvalid?: boolean): string[];
/**
* Return array of active functions (rules) for given chain name. It analyzes
* rules configuration, compiles caches if not exists and returns result.
*
* Default chain name is `''` (empty string). It can't be skipped. That's
* done intentionally, to keep signature monomorphic for high speed.
*/
getRules(chainName: string): T[];
}
@@ -0,0 +1,124 @@
import MarkdownIt from "../index.mjs";
import Token, { Nesting } from "../token.mjs";
export type ParentType = "blockquote" | "list" | "root" | "paragraph" | "reference";
export default class StateBlock {
constructor(src: string, md: MarkdownIt, env: any, tokens: Token[]);
src: string;
/**
* link to parser instance
*/
md: MarkdownIt;
env: any;
//
// Internal state vartiables
//
tokens: Token[];
/**
* line begin offsets for fast jumps
*/
bMarks: number[];
/**
* line end offsets for fast jumps
*/
eMarks: number[];
/**
* offsets of the first non-space characters (tabs not expanded)
*/
tShift: number[];
/**
* indents for each line (tabs expanded)
*/
sCount: number[];
/**
* An amount of virtual spaces (tabs expanded) between beginning
* of each line (bMarks) and real beginning of that line.
*
* It exists only as a hack because blockquotes override bMarks
* losing information in the process.
*
* It's used only when expanding tabs, you can think about it as
* an initial tab length, e.g. bsCount=21 applied to string `\t123`
* means first tab should be expanded to 4-21%4 === 3 spaces.
*/
bsCount: number[];
// block parser variables
/**
* required block content indent (for example, if we are
* inside a list, it would be positioned after list marker)
*/
blkIndent: number;
/**
* line index in src
*/
line: number;
/**
* lines count
*/
lineMax: number;
/**
* loose/tight mode for lists
*/
tight: boolean;
/**
* indent of the current dd block (-1 if there isn't any)
*/
ddIndent: number;
/**
* indent of the current list block (-1 if there isn't any)
*/
listIndent: number;
/**
* used in lists to determine if they interrupt a paragraph
*/
parentType: ParentType;
level: number;
/**
* Push new token to "stream".
*/
push(type: string, tag: string, nesting: Nesting): Token;
isEmpty(line: number): boolean;
skipEmptyLines(from: number): number;
/**
* Skip spaces from given position.
*/
skipSpaces(pos: number): number;
/**
* Skip spaces from given position in reverse.
*/
skipSpacesBack(pos: number, min: number): number;
/**
* Skip char codes from given position
*/
skipChars(pos: number, code: number): number;
/**
* Skip char codes reverse from given position - 1
*/
skipCharsBack(pos: number, code: number, min: number): number;
/**
* cut lines range from source.
*/
getLines(begin: number, end: number, indent: number, keepLastLF: boolean): string;
Token: typeof Token;
}
@@ -0,0 +1,18 @@
import MarkdownIt from "../index.mjs";
import Token from "../token.mjs";
export default class StateCore {
constructor(src: string, md: MarkdownIt, env: any);
src: string;
env: any;
tokens: Token[];
inlineMode: boolean;
/**
* link to parser instance
*/
md: MarkdownIt;
Token: typeof Token;
}
@@ -0,0 +1,73 @@
import MarkdownIt from "../index.mjs";
import Token, { Nesting } from "../token.mjs";
export interface Scanned {
can_open: boolean;
can_close: boolean;
length: number;
}
export interface Delimiter {
marker: number;
length: number;
token: number;
end: number;
open: boolean;
close: boolean;
}
export interface TokenMeta {
delimiters: Delimiter[];
}
export default class StateInline {
constructor(src: string, md: MarkdownIt, env: any, outTokens: Token[]);
src: string;
env: any;
md: MarkdownIt;
tokens: Token[];
tokens_meta: Array<TokenMeta | null>;
pos: number;
posMax: number;
level: number;
pending: string;
pendingLevel: number;
/**
* Stores { start: end } pairs. Useful for backtrack
* optimization of pairs parse (emphasis, strikes).
*/
cache: any;
/**
* List of emphasis-like delimiters for current tag
*/
delimiters: Delimiter[];
// Stack of delimiter lists for upper level tags
// _prev_delimiters: StateInline.Delimiter[][];
/**
* Flush pending text
*/
pushPending(): Token;
/**
* Push new token to "stream".
* If pending text exists - flush it as text token
*/
push(type: string, tag: string, nesting: Nesting): Token;
/**
* Scan a sequence of emphasis-like markers, and determine whether
* it can start an emphasis sequence or end an emphasis sequence.
*
* @param start position to scan from (it should point at a valid marker)
* @param canSplitWord determine if these markers can be found inside a word
*/
scanDelims(start: number, canSplitWord: boolean): Scanned;
Token: typeof Token;
}
+108
View File
@@ -0,0 +1,108 @@
export type Nesting = 1 | 0 | -1;
export default class Token {
/**
* Create new token and fill passed properties.
*/
constructor(type: string, tag: string, nesting: Nesting);
/**
* Type of the token, e.g. "paragraph_open"
*/
type: string;
/**
* HTML tag name, e.g. "p"
*/
tag: string;
/**
* HTML attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]`
*/
attrs: Array<[string, string]> | null;
/**
* Source map info. Format: `[ line_begin, line_end ]`
*/
map: [number, number] | null;
/**
* Level change (number in {-1, 0, 1} set), where:
*
* - `1` means the tag is opening
* - `0` means the tag is self-closing
* - `-1` means the tag is closing
*/
nesting: Nesting;
/**
* Nesting level, the same as `state.level`
*/
level: number;
/**
* An array of child nodes (inline and img tokens)
*/
children: Token[] | null;
/**
* In a case of self-closing tag (code, html, fence, etc.),
* it has contents of this tag.
*/
content: string;
/**
* '*' or '_' for emphasis, fence string for fence, etc.
*/
markup: string;
/**
* - Info string for "fence" tokens
* - The value "auto" for autolink "link_open" and "link_close" tokens
* - The string value of the item marker for ordered-list "list_item_open" tokens
*/
info: string;
/**
* A place for plugins to store an arbitrary data
*/
meta: any;
/**
* True for block-level tokens, false for inline tokens.
* Used in renderer to calculate line breaks
*/
block: boolean;
/**
* If it's true, ignore this element when rendering. Used for tight lists
* to hide paragraphs.
*/
hidden: boolean;
/**
* Search attribute index by name.
*/
attrIndex(name: string): number;
/**
* Add `[ name, value ]` attribute to list. Init attrs if necessary
*/
attrPush(attrData: [string, string]): void;
/**
* Set `name` attribute to `value`. Override old value if exists.
*/
attrSet(name: string, value: string): void;
/**
* Get the value of attribute `name`, or null if it does not exist.
*/
attrGet(name: string): string | null;
/**
* Join value to existing attribute via space. Or create new attribute if not
* exists. Useful to operate with token classes.
*/
attrJoin(name: string, value: string): void;
}
+54
View File
@@ -0,0 +1,54 @@
{
"name": "@types/markdown-it",
"version": "14.1.2",
"description": "TypeScript definitions for markdown-it",
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/markdown-it",
"license": "MIT",
"contributors": [
{
"name": "York Yao",
"githubUsername": "plantain-00",
"url": "https://github.com/plantain-00"
},
{
"name": "Robert Coie",
"githubUsername": "rapropos",
"url": "https://github.com/rapropos"
},
{
"name": "duduluu",
"githubUsername": "duduluu",
"url": "https://github.com/duduluu"
},
{
"name": "Piotr Błażejewicz",
"githubUsername": "peterblazejewicz",
"url": "https://github.com/peterblazejewicz"
}
],
"main": "",
"types": "index.d.ts",
"exports": {
".": {
"import": "./index.d.mts",
"require": "./dist/index.cjs.d.ts"
},
"./*": {
"import": "./*",
"require": "./*"
},
"./package.json": "./package.json"
},
"repository": {
"type": "git",
"url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
"directory": "types/markdown-it"
},
"scripts": {},
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
},
"typesPublisherContentHash": "72b750af20eb4973f000b8b6a751de43787503125afcd200798f88f096b221e7",
"typeScriptVersion": "4.8"
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
+15
View File
@@ -0,0 +1,15 @@
# Installation
> `npm install --save @types/mdurl`
# Summary
This package contains type definitions for mdurl (https://github.com/markdown-it/mdurl#readme).
# Details
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mdurl.
### Additional Details
* Last updated: Wed, 01 May 2024 18:07:45 GMT
* Dependencies: none
# Credits
These definitions were written by [Junyoung Choi](https://github.com/rokt33r).
+6
View File
@@ -0,0 +1,6 @@
import decode from "./lib/decode.mjs";
import encode from "./lib/encode.mjs";
import format from "./lib/format.mjs";
import parse, { Url } from "./lib/parse.mjs";
export { decode, encode, format, parse, Url };
+3
View File
@@ -0,0 +1,3 @@
import mdurl = require("./build/index.cjs.js");
export = mdurl;
+10
View File
@@ -0,0 +1,10 @@
declare const decode: {
defaultChars: string;
componentChars: string;
/**
* Decode percent-encoded string.
*/
(str: string, exclude?: string): string;
};
export default decode;
+15
View File
@@ -0,0 +1,15 @@
declare const encode: {
defaultChars: string;
componentChars: string;
/**
* Encode unsafe characters with percent-encoding, skipping already
* encoded sequences.
*
* @param str string to encode
* @param exclude list of characters to ignore (in addition to a-zA-Z0-9)
* @param keepEscaped don't encode '%' in a correct escape sequence (default: true)
*/
(str: string, exclude?: string, keepEscaped?: boolean): string;
};
export default encode;
+3
View File
@@ -0,0 +1,3 @@
import { Url } from "./parse.mjs";
export default function format(url: Omit<Url, "parse" | "parseHost">): string;
+18
View File
@@ -0,0 +1,18 @@
declare class Url {
protocol: string;
slashes: string;
auth: string;
port: string;
hostname: string;
hash: string;
search: string;
pathname: string;
constructor();
parse(url: string, slashesDenoteHost?: boolean): this;
parseHost(host: string): void;
}
export default function parse(url: string | Url, slashesDenoteHost?: boolean): Url;
export type { Url };
+36
View File
@@ -0,0 +1,36 @@
{
"name": "@types/mdurl",
"version": "2.0.0",
"description": "TypeScript definitions for mdurl",
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mdurl",
"license": "MIT",
"contributors": [
{
"name": "Junyoung Choi",
"githubUsername": "rokt33r",
"url": "https://github.com/rokt33r"
}
],
"main": "",
"types": "index.d.ts",
"exports": {
".": {
"import": "./index.d.mts",
"require": "./build/index.cjs.d.ts"
},
"./*": {
"import": "./*",
"require": "./*"
},
"./package.json": "./package.json"
},
"repository": {
"type": "git",
"url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
"directory": "types/mdurl"
},
"scripts": {},
"dependencies": {},
"typesPublisherContentHash": "3dc3a8535d7207c6d737adfe2aac315c7f6546b2372bd343794994106ee251a5",
"typeScriptVersion": "4.7"
}
+600
View File
@@ -0,0 +1,600 @@
# markdown-it-anchor [![npm version](http://img.shields.io/npm/v/markdown-it-anchor.svg?style=flat-square)](https://www.npmjs.org/package/markdown-it-anchor)
> A markdown-it plugin that adds an `id` attribute to headings and
> optionally permalinks.
[markdown-it]: https://github.com/markdown-it/markdown-it
English | [中文 (v7.0.1)](./README-zh_CN.md)
## Overview
This plugin adds an `id` attribute to headings, e.g. `## Foo` becomes
`<h2 id="foo">Foo</h2>`.
Optionally it can also include [permalinks](#permalinks), e.g.
`<h2 id="foo"><a class="header-anchor" href="#foo">Foo</a></h2>`
and a bunch of other variants!
* [**Usage**](#usage)
* [User-friendly URLs](#user-friendly-urls)
* [Manually setting the `id` attribute](#manually-setting-the-id-attribute)
* [Compatible table of contents plugin](#compatible-table-of-contents-plugin)
* [Parsing headings from HTML blocks](#parsing-headings-from-html-blocks)
* [Browser example](#browser-example)
* [**Permalinks**](#permalinks)
* [Header link](#header-link)
* [Link after header](#link-after-header)
* [Link inside header](#link-inside-header)
* [ARIA hidden](#aria-hidden)
* [Custom permalink](#custom-permalink)
* [Debugging](#debugging)
* [Development](#development)
## Usage
```js
const md = require('markdown-it')()
.use(require('markdown-it-anchor'), opts)
```
See a [demo as JSFiddle](https://jsfiddle.net/9ukc8dy6/).
The `opts` object can contain:
| Name | Description | Default |
|------------------------|---------------------------------------------------------------------------|-----------------------------------------|
| `level` | Minimum level to apply anchors, or array of selected levels. | 1 |
| `permalink` | A function to render permalinks, see [permalinks] below. | `undefined` |
| `slugify` | A custom slugification function. | See [`index.js`][index-slugify] |
| `callback` | Called with token and info after rendering. | `undefined` |
| `getTokensText` | A custom function to get the text contents of the title from its tokens. | See [`index.js`][index-get-tokens-text] |
| `tabIndex` | Value of the `tabindex` attribute on headings, set to `false` to disable. | `-1` |
| `uniqueSlugStartIndex` | Index to start with when making duplicate slugs unique. | 1 |
[index-slugify]: https://github.com/valeriangalliat/markdown-it-anchor/blob/master/index.js#L3
[index-get-tokens-text]: https://github.com/valeriangalliat/markdown-it-anchor/blob/master/index.js#L5
[permalinks]: #permalinks
All headers greater than the minimum `level` will have an `id` attribute
with a slug of their content. For example, you can set `level` to 2 to
add anchors to all headers but `h1`. You can also pass an array of
header levels to apply the anchor, like `[2, 3]` to have an anchor on
only level 2 and 3 headers.
If a `permalink` renderer is given, it will be called for each matching header
to add a permalink. See [permalinks] below.
If a `slugify` function is given, you can decide how to transform a
heading text to a URL slug. See [user-friendly URLs](#user-friendly-urls).
The `callback` option is a function that will be called at the end of
rendering with the `token` and an `info` object. The `info` object has
`title` and `slug` properties with the token content and the slug used
for the identifier.
We set by default [`tabindex="-1"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex)
on headers. This marks the headers as focusable elements that are not
reachable by keyboard navigation. The effect is that screen readers will
read the title content when it's being jumped to. Outside of screen
readers, the experience is the same as not setting that attribute. You
can override this behavior with the `tabIndex` option. Set it to `false`
to remove the attribute altogether, otherwise the value will be used as
attribute value.
Finally, you can customize how the title text is extracted from the
markdown-it tokens (to later generate the slug). See [user-friendly URLs](#user-friendly-urls).
## User-friendly URLs
Starting from v5.0.0, markdown-it-anchor dropped the [`string`](https://github.com/jprichardson/string.js)
package to retain our core value of being an impartial and secure
library. Nevertheless, users looking for backward compatibility may want
the old `slugify` function:
```sh
npm install string
```
```js
const string = require('string')
const slugify = s => string(s).slugify().toString()
const md = require('markdown-it')()
.use(require('markdown-it-anchor'), { slugify })
```
Another popular library for this is [`@sindresorhus/slugify`](https://github.com/sindresorhus/slugify),
which have better Unicode support and other cool features:
```sh
npm install @sindresorhus/slugify
```
```js
const slugify = require('@sindresorhus/slugify')
const md = require('markdown-it')()
.use(require('markdown-it-anchor'), { slugify: s => slugify(s) })
```
### Custimizing the slugify input
Additionally, if you want to further customize the title that gets
passed to the `slugify` function, you can do so by customizing the
`getTokensText` function, that gets the plain text from a list of
markdown-it inline tokens:
```js
function getTokensText (tokens) {
return tokens
.filter(token => !['html_inline', 'image'].includes(token.type))
.map(t => t.content)
.join('')
}
const md = require('markdown-it')()
.use(require('markdown-it-anchor'), { getTokensText })
```
By default we include only `text` and `code_inline` tokens, which
appeared to be a sensible approach for the vast majority of use cases.
An alternative approach is to include every token's content except for
`html_inline` and `image` tokens, which yields the exact same results as
the previous approach with a stock markdown-it, but would also include
custom tokens added by any of your markdown-it plugins, which might or
might not be desirable for you. Now you have the option!
### Slugifying with state
If you need access to the markdown-it state from the slugify function,
e.g. to access `state.env`, you can use `slugifyWithState` instead.
```js
const md = require('markdown-it')()
.use(require('markdown-it-anchor'), {
slugifyWithState: (title, state) => `${state.env.id}-${slugify(title)}`
})
```
## Manually setting the `id` attribute
You might want to explicitly set the `id` attribute of your headings
from the Markdown document, for example to keep them consistent across
translations.
markdown-it-anchor is designed to reuse any existing `id`, making [markdown-it-attrs](https://www.npmjs.com/package/markdown-it-attrs)
a perfect fit for this use case. Make sure to load it before markdown-it-anchor!
Then you can do something like this:
```markdown
# Your title {#your-custom-id}
```
The anchor link will reuse the `id` that you explicitly defined.
## Compatible table of contents plugin
Looking for an automatic table of contents (TOC) generator? Take a look at
[markdown-it-toc-done-right](https://www.npmjs.com/package/markdown-it-toc-done-right)
it's made from the ground to be a great companion of this plugin.
## Parsing headings from HTML blocks
markdown-it-anchor doesn't parse HTML blocks, so headings defined in
HTML blocks will be ignored. If you need to add anchors to both HTML
headings and Markdown headings, the easiest way would be to do it on the
final HTML rather than during the Markdown parsing phase:
```js
const { parse } = require('node-html-parser')
const root = parse(html)
for (const h of root.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
const slug = h.getAttribute('id') || slugify(h.textContent)
h.setAttribute('id', slug)
h.innerHTML = `<a href="#${slug}">${h.innerHTML}</a>`
}
console.log(root.toString())
```
Or with a (not accessible) GitHub-style anchor, replace the
`h.innerHTML` part with:
```js
h.insertAdjacentHTML('afterbegin', `<a class="anchor" aria-hidden="true" href="#${slug}">🔗</a> `)
```
While this still needs extra work like handling duplicated slugs and
IDs, this should give you a solid base.
That said if you really want to use markdown-it-anchor for this even
though it's not designed to, you can do like npm does with their
[marky-markdown](https://github.com/npm/marky-markdown) parser, and
[transform the `html_block` tokens](https://github.com/npm/marky-markdown/blob/master/lib/plugin/html-heading.js)
into a sequence of `heading_open`, `inline`, and `heading_close` tokens
that can be handled by markdown-it-anchor:
```js
const md = require('markdown-it')()
.use(require('@npmcorp/marky-markdown/lib/plugin/html-heading'))
.use(require('markdown-it-anchor'), opts)
```
While they use regexes to parse the HTML and it won't gracefully handle
any arbitrary HTML, it should work okay for the happy path, which might
be good enough for you.
You might also want to check [this implementation](https://github.com/valeriangalliat/markdown-it-anchor/issues/105#issuecomment-907323858)
which uses [Cheerio](https://www.npmjs.com/package/cheerio) for a more
solid parsing, including support for HTML attributes.
The only edge cases I see it failing with are multiple headings defined
in the same HTML block with arbitrary content between them, or headings
where the opening and closing tag are defined in separate `html_block`
tokens, both which should very rarely happen.
If you need a bulletproof implementation, I would recommend the first
HTML parser approach I documented instead.
## Browser example
See [`example.html`](example.html).
## Permalinks
Version 8.0.0 completely reworked the way permalinks work in order to
offer more accessible options out of the box. You can also [make your own permalink](#custom-permalink).
Instead of a single default way of rendering permalinks (which used to
have a poor UX on screen readers), we now have multiple styles of
permalinks for you to chose from.
```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()
md.use(anchor, {
permalink: anchor.permalink[styleOfPermalink](permalinkOpts)
})
```
Here, `styleOfPermalink` is one of the available styles documented
below, and `permalinkOpts` is an options object.
<div id="common-options"></div>
All renderers share a common set of options:
| Name | Description | Default |
|---------------|---------------------------------------------------|------------------------------------|
| `class` | The class of the permalink anchor. | `header-anchor` |
| `symbol` | The symbol in the permalink anchor. | `#` |
| `renderHref` | A custom permalink `href` rendering function. | See [`permalink.js`](permalink.js) |
| `renderAttrs` | A custom permalink attributes rendering function. | See [`permalink.js`](permalink.js) |
For the `symbol`, you may want to use the [link symbol](http://graphemica.com/🔗),
or a symbol from your favorite web font.
### Header link
This style wraps the header itself in an anchor link. It doesn't use the
`symbol` option as there's no symbol needed in the markup (though you
could add it with CSS using `::before` if you like).
It's so simple it doesn't have any behaviour to custom, and it's also
accessible out of the box without any further configuration, hence it
doesn't have other options than the common ones described above.
You can find this style on the [MDN] as well as [HTTP Archive] and their
[Web Almanac], which to me is a good sign that this is a thoughtful way of
implementing permalinks. This is also the style that I chose for my own
[blog].
[MDN]: https://developer.mozilla.org/en-US/docs/Web
[HTTP Archive]: https://httparchive.org/reports/state-of-the-web
[Web Almanac]: https://almanac.httparchive.org/en/2020/table-of-contents
[blog]: https://www.codejam.info/
| Name | Description | Default |
|-------------------|-----------------------------------------------------------------------|---------------------------------------|
| `safariReaderFix` | Add a `span` inside the link so Safari shows headings in reader view. | `false` (for backwards compatibility) |
| | See [common options](#common-options). | |
```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()
md.use(anchor, {
permalink: anchor.permalink.headerLink()
})
```
```html
<h2 id="title"><a class="header-anchor" href="#title">Title</a></h2>
```
The main caveat of this approach is that you can't include links inside
headers. If you do, consider the other styles.
Also note that this pattern [breaks reader mode in Safari](https://www.leereamsnyder.com/blog/making-headings-with-links-show-up-in-safari-reader),
an issue you can also notice on the referenced websites above. This was
already [reported to Apple](https://bugs.webkit.org/show_bug.cgi?id=225609#c2)
but their bug tracker is not public. In the meantime, a fix mentioned in
the article above is to insert a `span` inside the link. You can use the
`safariReaderFix` option to enable it.
```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()
md.use(anchor, {
permalink: anchor.permalink.headerLink({ safariReaderFix: true })
})
```
```html
<h2 id="title"><a class="header-anchor" href="#title"><span>Title</span></a></h2>
```
### Link after header
If you want to customize further the screen reader experience of your
permalinks, this style gives you much more freedom than the [header link](#header-link).
It works by leaving the header itself alone, and adding the permalink
*after* it, giving you different methods of customizing the assistive
text. It makes the permalink symbol `aria-hidden` to not pollute the
experience, and leverages a `visuallyHiddenClass` to hide the assistive
text from the visual experience.
| Name | Description | Default |
|-----------------------|-----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------|
| `style` | The (sub) style of link, one of `visually-hidden`, `aria-label`, `aria-describedby` or `aria-labelledby`. | `visually-hidden` |
| `assistiveText` | A function that takes the title and returns the assistive text. | `undefined`, required for `visually-hidden` and `aria-label` styles |
| `visuallyHiddenClass` | The class you use to make an element visually hidden. | `undefined`, required for `visually-hidden` style |
| `space` | Add a space between the assistive text and the permalink symbol. | `true` |
| `placement` | Placement of the permalink symbol relative to the assistive text, can be `before` or `after` the header. | `after` |
| `wrapper` | Opening and closing wrapper string, e.g. `['<div class="wrapper">', '</div>']`. | `null` |
| | See [common options](#common-options). | |
```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()
md.use(anchor, {
permalink: anchor.permalink.linkAfterHeader({
style: 'visually-hidden',
assistiveText: title => `Permalink to “${title}`,
visuallyHiddenClass: 'visually-hidden',
wrapper: ['<div class="wrapper">', '</div>']
})
})
```
```html
<div class="wrapper">
<h2 id="title">Title</h2>
<a class="header-anchor" href="#title">
<span class="visually-hidden">Permalink to “Title”</span>
<span aria-hidden="true">#</span>
</a>
</div>
```
By using a visually hidden element for the assistive text, we make sure
that the assistive text can be picked up by translation services, as
most of the popular translation services (including Google Translate)
currently ignore `aria-label`.
If you prefer an alternative method for the assistive text, see other
styles:
<details>
<summary><code>aria-label</code> variant</summary>
This removes the need from a visually hidden `span`, but will likely
hurt the permalink experience when using a screen reader through a
translation service.
```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()
md.use(anchor, {
permalink: anchor.permalink.linkAfterHeader({
style: 'aria-label'
assistiveText: title => `Permalink to “${title}`
})
})
```
```html
<h2 id="title">Title</h2>
<a class="header-anchor" href="#title" aria-label="Permalink to “Title”">#</a>
```
</details>
<details>
<summary><code>aria-describedby</code> and <code>aria-labelledby</code> variants</summary>
This removes the need to customize the assistive text to your locale and
doesn't need a visually hidden `span` either, but since the anchor will
be described by just the text of the title without any context, it might
be confusing.
```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()
md.use(anchor, {
permalink: anchor.permalink.linkAfterHeader({
style: 'aria-describedby' // Or `aria-labelledby`
})
})
```
```html
<h2 id="title">Title</h2>
<a class="header-anchor" href="#title" aria-describedby="title">#</a>
```
</details>
### Link inside header
This is the equivalent of the default permalink in previous versions.
The reason it's not the first one in the list is because this method has
accessibility issues.
If you use a symbol like just `#` without adding any markup around,
screen readers will read it as part of every heading (in the case of
`#`, it could be read "pound", "number" or "number sign") meaning that
if you title is "my beautiful title", it will read "number sign my
beautiful title" for example. For other common symbols, `🔗` is usually
read as "link symbol" and `¶` as "pilcrow".
Additionally, screen readers users commonly request the list of all
links in the page, so they'll be flooded with "number sign, number sign,
number sign" for each of your headings.
I would highly recommend using one of the markups above which have a
better experience, but if you really want to use this markup, make sure
to pass accessible HTML as `symbol` to make things usable, like in the
example below, but even that has some flaws.
With that said, this permalink allows the following options:
| Name | Description | Default |
|--------------|--------------------------------------------------------------------------------------------------------------------------|---------|
| `space` | Add a space between the header text and the permalink symbol. Set it to a string to customize the space (e.g. `&nbsp;`). | `true` |
| `placement` | Placement of the permalink, can be `before` or `after` the header. This option used to be called `permalinkBefore`. | `after` |
| `ariaHidden` | Whether to add `aria-hidden="true"`, see [ARIA hidden](#aria-hidden). | `false` |
| | See [common options](#common-options). | |
```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()
md.use(anchor, {
permalink: anchor.permalink.linkInsideHeader({
symbol: `
<span class="visually-hidden">Jump to heading</span>
<span aria-hidden="true">#</span>
`,
placement: 'before'
})
})
```
```html
<h2 id="title">
<a class="header-anchor" href="#title">
<span class="visually-hidden">Jump to heading</span>
<span aria-hidden="true">#</span>
</a>
Title
</h2>
```
While this example allows more accessible anchors with the same markup
as previous versions of markdown-it-anchor, it's still not ideal. The
assistive text for permalinks will be read as part of the heading when
listing all the titles of the page, e.g. "jump to heading title 1, jump
to heading title 2" and so on. Also that assistive text is not very
useful when listing the links in the page (which will read "jump to
heading, jump to heading, jump to heading" for each of your permalinks).
### ARIA hidden
This is just an alias for [`linkInsideHeader`](#link-inside-header) with
`ariaHidden: true` by default, to mimic GitHub's way of rendering
permalinks.
Setting `aria-hidden="true"` makes the permalink explicitly inaccessible
instead of having the permalink and its symbol being read by screen
readers as part of every single headings (which was a pretty terrible
experience).
```js
const anchor = require('markdown-it-anchor')
const md = require('markdown-it')()
md.use(anchor, {
permalink: anchor.permalink.ariaHidden({
placement: 'before'
})
})
```
```html
<h2 id="title"><a class="header-anchor" href="#title" aria-hidden="true">#</a> Title</h2>
```
While no experience might be arguably better than a bad experience, I
would instead recommend using one of the above renderers to provide an
accessible experience. My favorite one is the [header link](#header-link),
which is also the simplest one.
### Custom permalink
If none of those options suit you, you can always make your own
renderer! Take inspiration from [the code behind all permalinks](permalink.js).
The signature of the function you pass in the `permalink` option is the
following:
```js
function renderPermalink (slug, opts, state, idx) {}
```
Where `opts` are the markdown-it-anchor options, `state` is a
markdown-it [`StateCore`](https://github.com/markdown-it/markdown-it)
instance, and `idx` is the index of the `heading_open` token in the
`state.tokens` array. That array contains [`Token`](https://markdown-it.github.io/markdown-it/#Token)
objects.
To make sense of the "token stream" and the way token objects are
organized, you will probably want to read the [markdown-it design principles](https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md)
page.
This function can freely modify the token stream (`state.tokens`),
usually around the given `idx`, to construct the anchor.
Because of the way the token stream works, a `heading_open` token is
usually followed by a `inline` token that contains the actual text (and
inline markup) of the heading, and finally a `heading_close` token. This
is why you'll see most built-in permalink renderers touch
`state.tokens[idx + 1]`, because they update the contents of the
`inline` token that follows a `heading_open`.
## Debugging
If you want to debug this library more easily, we support source maps.
Use the [source-map-support](https://www.npmjs.com/package/source-map-support)
module to enable it with Node.js.
```sh
node -r source-map-support/register your-script.js
```
## Development
```sh
# Build the library in the `dist/` directory.
npm run build
# Watch file changes to update `dist/`.
npm run dev
# Run tests, will use the build version so make sure to build after
# making changes.
npm test
```
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
+54
View File
@@ -0,0 +1,54 @@
{
"name": "markdown-it-anchor",
"version": "9.2.0",
"description": "Header anchors for markdown-it.",
"keywords": [
"markdown",
"markdown-it",
"markdown-it-plugin",
"header",
"anchor"
],
"license": "Unlicense",
"contributors": [
"Val (https://val.codejam.info/)",
"Nagao, Fabio Zendhi <nagaozen@gmail.com> (https://github.com/nagaozen/)"
],
"files": [
"README.md",
"UNLICENSE",
"dist/*",
"runkit.js",
"types/*"
],
"main": "dist/markdownItAnchor.js",
"module": "dist/markdownItAnchor.mjs",
"types": "./types/index.d.ts",
"repository": "valeriangalliat/markdown-it-anchor",
"scripts": {
"build": "microbundle --generateTypes false",
"dev": "microbundle --generateTypes false watch",
"lint": "standard index.js test.js",
"prepare": "npm run build && npm test",
"test": "npm run lint && ava test.js"
},
"devDependencies": {
"@sindresorhus/slugify": "^2.2.1",
"ava": "^5.1.0",
"markdown-it": "*",
"markdown-it-attrs": "^4.1.6",
"microbundle": "^0.15.1",
"source-map-support": "^0.5.19",
"standard": "^17.1.0"
},
"peerDependencies": {
"@types/markdown-it": "*",
"markdown-it": "*"
},
"source": "index.js",
"unpkg": "dist/markdownItAnchor.umd.js",
"mangle": {
"regex": "^_"
},
"runkitExampleFilename": "runkit.js"
}
+22
View File
@@ -0,0 +1,22 @@
const md = require('markdown-it')()
const anchor = require('markdown-it-anchor')
md.use(anchor, {
level: 1,
// slugify: string => string,
permalink: false,
// renderPermalink: (slug, opts, state, permalink) => {},
permalinkClass: 'header-anchor',
permalinkSymbol: '¶',
permalinkBefore: false
})
const src = `# First header
Lorem ipsum.
## Next section!
This is totaly awesome.`
console.log(md.render(src))
+74
View File
@@ -0,0 +1,74 @@
import MarkdownIt from 'markdown-it';
import { default as MarkdownItToken } from 'markdown-it/lib/token.mjs';
import { default as MarkdownItState} from 'markdown-it/lib/rules_core/state_core.mjs';
declare namespace anchor {
export type Token = MarkdownItToken
export type State = MarkdownItState
export type RenderHref = (slug: string, state: State) => string;
export type RenderAttrs = (slug: string, state: State) => Record<string, string | number>;
export interface PermalinkOptions {
class?: string,
symbol?: string,
renderHref?: RenderHref,
renderAttrs?: RenderAttrs
}
export interface HeaderLinkPermalinkOptions extends PermalinkOptions {
safariReaderFix?: boolean;
}
export interface LinkAfterHeaderPermalinkOptions extends PermalinkOptions {
style?: 'visually-hidden' | 'aria-label' | 'aria-describedby' | 'aria-labelledby',
assistiveText?: (title: string) => string,
visuallyHiddenClass?: string,
space?: boolean | string,
placement?: 'before' | 'after'
wrapper?: [string, string] | null
}
export interface LinkInsideHeaderPermalinkOptions extends PermalinkOptions {
space?: boolean | string,
placement?: 'before' | 'after',
ariaHidden?: boolean
}
export interface AriaHiddenPermalinkOptions extends PermalinkOptions {
space?: boolean | string,
placement?: 'before' | 'after'
}
export type PermalinkGenerator = (slug: string, opts: PermalinkOptions, state: State, index: number) => void;
export interface AnchorInfo {
slug: string;
title: string;
}
export interface AnchorOptions {
level?: number | number[];
slugify?(str: string): string;
slugifyWithState?(str: string, state: State): string;
getTokensText?(tokens: Token[]): string;
uniqueSlugStartIndex?: number;
permalink?: PermalinkGenerator;
callback?(token: Token, anchor_info: AnchorInfo): void;
tabIndex?: number | false;
}
export const permalink: {
headerLink: (opts?: HeaderLinkPermalinkOptions) => PermalinkGenerator
linkAfterHeader: (opts?: LinkAfterHeaderPermalinkOptions) => PermalinkGenerator
linkInsideHeader: (opts?: LinkInsideHeaderPermalinkOptions) => PermalinkGenerator
ariaHidden: (opts?: AriaHiddenPermalinkOptions) => PermalinkGenerator
};
}
declare function anchor(md: MarkdownIt, opts?: anchor.AnchorOptions): void;
export default anchor;
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
+39 -7
View File
@@ -7,6 +7,10 @@
"": {
"name": "kst4contest-website",
"version": "0.1.0",
"dependencies": {
"markdown-it": "^14.3.0",
"markdown-it-anchor": "^9.2.0"
},
"devDependencies": {
"@11ty/eleventy": "^3.0.0"
}
@@ -229,6 +233,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT",
"peer": true
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT",
"peer": true
},
"node_modules/a-sync-waterfall": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
@@ -293,7 +322,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/asap": {
@@ -1051,7 +1079,6 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz",
"integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==",
"dev": true,
"funding": [
{
"type": "github",
@@ -1109,7 +1136,6 @@
"version": "14.3.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz",
"integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==",
"dev": true,
"funding": [
{
"type": "github",
@@ -1133,11 +1159,20 @@
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it-anchor": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-9.2.0.tgz",
"integrity": "sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==",
"license": "Unlicense",
"peerDependencies": {
"@types/markdown-it": "*",
"markdown-it": "*"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -1150,7 +1185,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true,
"license": "MIT"
},
"node_modules/mime": {
@@ -1422,7 +1456,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -1640,7 +1673,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"dev": true,
"license": "MIT"
},
"node_modules/unpipe": {
+5 -1
View File
@@ -8,5 +8,9 @@
},
"devDependencies": {
"@11ty/eleventy": "^3.0.0"
},
"dependencies": {
"markdown-it": "^14.3.0",
"markdown-it-anchor": "^9.2.0"
}
}
}
+34
View File
@@ -113,4 +113,38 @@ nav a:hover {
.site-footer {
padding: 40px 7vw;
color: var(--muted);
}
.manual-content {
max-width: 980px;
}
.manual-content h1,
.manual-content h2,
.manual-content h3 {
line-height: 1.2;
}
.manual-content pre {
overflow-x: auto;
padding: 16px;
border-radius: 14px;
background: #020617;
}
.manual-content code {
color: #bae6fd;
}
.manual-content table {
width: 100%;
border-collapse: collapse;
overflow-x: auto;
}
.manual-content th,
.manual-content td {
border-bottom: 1px solid rgba(148, 163, 184, 0.22);
padding: 10px;
text-align: left;
}
+18
View File
@@ -0,0 +1,18 @@
---
pagination:
data: collections.manualPages
size: 1
alias: manual
permalink: "/manual/{{ manual.lang }}/{{ manual.slug }}/"
layout: base.njk
title: "{{ manual.title }} | KST4Contest Manual"
description: "KST4Contest manual page: {{ manual.title }}"
---
<section class="section">
<div class="card manual-content">
<p><a href="/manual/{{ manual.lang }}/">← Back to manual overview</a></p>
<h1>{{ manual.title }}</h1>
{{ manual.content | markdown | safe }}
</div>
</section>
+8 -4
View File
@@ -11,9 +11,13 @@ description: Deutsches Benutzerhandbuch für KST4Contest.
<section class="section">
<div class="grid">
<div class="card"><h3>Installation</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/de-Installation">Installationsanleitung öffnen</a></p></div>
<div class="card"><h3>Konfiguration</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/de-Konfiguration">Konfiguration öffnen</a></p></div>
<div class="card"><h3>Funktionen</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/de-Funktionen">Funktionsübersicht öffnen</a></p></div>
<div class="card"><h3>AirScout-Integration</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/de-AirScout-Integration">AirScout-Anleitung öffnen</a></p></div>
{% for manual in collections.manualPages %}
{% if manual.lang == "de" %}
<div class="card">
<h3><a href="/manual/de/{{ manual.slug }}/">{{ manual.title }}</a></h3>
<p>Handbuchseite „{{ manual.title }}“ öffnen.</p>
</div>
{% endif %}
{% endfor %}
</div>
</section>
+8 -4
View File
@@ -11,9 +11,13 @@ description: English user manual for KST4Contest.
<section class="section">
<div class="grid">
<div class="card"><h3>Installation</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/en-Installation">Open installation guide</a></p></div>
<div class="card"><h3>Configuration</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/en-Configuration">Open configuration guide</a></p></div>
<div class="card"><h3>Features</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/en-Features">Open feature documentation</a></p></div>
<div class="card"><h3>AirScout Integration</h3><p><a href="https://github.com/praktimarc/kst4contest/wiki/en-AirScout-Integration">Open AirScout guide</a></p></div>
{% for manual in collections.manualPages %}
{% if manual.lang == "en" %}
<div class="card">
<h3><a href="/manual/en/{{ manual.slug }}/">{{ manual.title }}</a></h3>
<p>Open the {{ manual.title }} manual page.</p>
</div>
{% endif %}
{% endfor %}
</div>
</section>