mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-20 14:56:17 -04:00
Merge 8371d208b1
into 07eea0f484
This commit is contained in:
commit
021c3cd51e
9 changed files with 6933 additions and 8286 deletions
15
components.d.ts
vendored
15
components.d.ts
vendored
|
@ -121,7 +121,7 @@ declare module '@vue/runtime-core' {
|
||||||
LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default']
|
LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default']
|
||||||
MacAddressGenerator: typeof import('./src/tools/mac-address-generator/mac-address-generator.vue')['default']
|
MacAddressGenerator: typeof import('./src/tools/mac-address-generator/mac-address-generator.vue')['default']
|
||||||
MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default']
|
MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default']
|
||||||
MarkdownToHtml: typeof import('./src/tools/markdown-to-html/markdown-to-html.vue')['default']
|
MarkdownTocGenerator: typeof import('./src/tools/markdown-toc-generator/markdown-toc-generator.vue')['default']
|
||||||
MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default']
|
MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default']
|
||||||
MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default']
|
MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default']
|
||||||
MenuBarItem: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue')['default']
|
MenuBarItem: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue')['default']
|
||||||
|
@ -131,18 +131,29 @@ declare module '@vue/runtime-core' {
|
||||||
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
||||||
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
|
NCode: typeof import('naive-ui')['NCode']
|
||||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||||
|
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDivider: typeof import('naive-ui')['NDivider']
|
NDivider: typeof import('naive-ui')['NDivider']
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
|
NGi: typeof import('naive-ui')['NGi']
|
||||||
|
NGrid: typeof import('naive-ui')['NGrid']
|
||||||
NH1: typeof import('naive-ui')['NH1']
|
NH1: typeof import('naive-ui')['NH1']
|
||||||
NH3: typeof import('naive-ui')['NH3']
|
NH3: typeof import('naive-ui')['NH3']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
|
NImage: typeof import('naive-ui')['NImage']
|
||||||
|
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||||
|
NLabel: typeof import('naive-ui')['NLabel']
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
NLayout: typeof import('naive-ui')['NLayout']
|
||||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||||
NMenu: typeof import('naive-ui')['NMenu']
|
NMenu: typeof import('naive-ui')['NMenu']
|
||||||
|
NP: typeof import('naive-ui')['NP']
|
||||||
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
NTable: typeof import('naive-ui')['NTable']
|
NSpin: typeof import('naive-ui')['NSpin']
|
||||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
||||||
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
||||||
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
||||||
|
|
|
@ -88,6 +88,7 @@
|
||||||
"pinia": "^2.0.34",
|
"pinia": "^2.0.34",
|
||||||
"plausible-tracker": "^0.3.8",
|
"plausible-tracker": "^0.3.8",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
|
"sanitize-html": "^2.13.1",
|
||||||
"randexp": "^0.5.3",
|
"randexp": "^0.5.3",
|
||||||
"sql-formatter": "^13.0.0",
|
"sql-formatter": "^13.0.0",
|
||||||
"ua-parser-js": "^1.0.35",
|
"ua-parser-js": "^1.0.35",
|
||||||
|
@ -122,6 +123,7 @@
|
||||||
"@types/node": "^18.15.11",
|
"@types/node": "^18.15.11",
|
||||||
"@types/node-forge": "^1.3.2",
|
"@types/node-forge": "^1.3.2",
|
||||||
"@types/qrcode": "^1.5.0",
|
"@types/qrcode": "^1.5.0",
|
||||||
|
"@types/sanitize-html": "^2.13.0",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^9.0.0",
|
||||||
"@unocss/eslint-config": "^0.57.0",
|
"@unocss/eslint-config": "^0.57.0",
|
||||||
|
|
14492
pnpm-lock.yaml
generated
14492
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
||||||
import { tool as base64FileConverter } from './base64-file-converter';
|
import { tool as base64FileConverter } from './base64-file-converter';
|
||||||
import { tool as base64StringConverter } from './base64-string-converter';
|
import { tool as base64StringConverter } from './base64-string-converter';
|
||||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||||
|
import { tool as markdownTocGenerator } from './markdown-toc-generator';
|
||||||
import { tool as emailNormalizer } from './email-normalizer';
|
import { tool as emailNormalizer } from './email-normalizer';
|
||||||
|
|
||||||
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
||||||
|
@ -113,6 +114,7 @@ export const toolsByCategory: ToolCategory[] = [
|
||||||
listConverter,
|
listConverter,
|
||||||
tomlToJson,
|
tomlToJson,
|
||||||
tomlToYaml,
|
tomlToYaml,
|
||||||
|
markdownTocGenerator,
|
||||||
xmlToJson,
|
xmlToJson,
|
||||||
jsonToXml,
|
jsonToXml,
|
||||||
markdownToHtml,
|
markdownToHtml,
|
||||||
|
|
12
src/tools/markdown-toc-generator/index.ts
Normal file
12
src/tools/markdown-toc-generator/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Table } from '@vicons/tabler';
|
||||||
|
import { defineTool } from '../tool';
|
||||||
|
|
||||||
|
export const tool = defineTool({
|
||||||
|
name: 'Markdown toc generator',
|
||||||
|
path: '/markdown-toc-generator',
|
||||||
|
description: 'Generate a TOC from a markdown file/content',
|
||||||
|
keywords: ['markdown', 'md', 'toc', 'generator'],
|
||||||
|
component: () => import('./markdown-toc-generator.vue'),
|
||||||
|
icon: Table,
|
||||||
|
createdAt: new Date('2024-05-11'),
|
||||||
|
});
|
6
src/tools/markdown-toc-generator/markdown-contents.d.ts
vendored
Normal file
6
src/tools/markdown-toc-generator/markdown-contents.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
declare module 'markdown-contents'{
|
||||||
|
declare class MarkdownContents {
|
||||||
|
markdown(): string;
|
||||||
|
}
|
||||||
|
export default function Create(markdown: string):MarkdownContents;
|
||||||
|
}
|
|
@ -0,0 +1,380 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import {
|
||||||
|
getTocMarkdown,
|
||||||
|
} from './markdown-toc-generator.service';
|
||||||
|
|
||||||
|
describe('markdown-toc-generator', () => {
|
||||||
|
it('Generate TOC correctly', async () => {
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: '',
|
||||||
|
})).to.equal('');
|
||||||
|
|
||||||
|
const sourceMarkdown = `# Some main title
|
||||||
|
|
||||||
|
[TOC]
|
||||||
|
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
## Last Title`;
|
||||||
|
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: sourceMarkdown,
|
||||||
|
anchorPrefix: 'h-',
|
||||||
|
})).to.equal(`# Some main title
|
||||||
|
|
||||||
|
<!-- TOC START -->
|
||||||
|
- [First Title](#h-first-title)
|
||||||
|
- [Second Spaced Title](#h-second-spaced-title)
|
||||||
|
* [Title with Link TOC](#h-title-with-link-toc)
|
||||||
|
* [Title with code \`var\`](#h-title-with-code-var)
|
||||||
|
- [Last Title](#h-last-title)
|
||||||
|
<!-- TOC END -->
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="first-title"></a>
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="second-spaced-title"></a>
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="title-with-link-toc"></a>
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="title-with-code-var"></a>
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="last-title"></a>
|
||||||
|
## Last Title`);
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: sourceMarkdown,
|
||||||
|
maxLevel: 2,
|
||||||
|
})).to.equal(`# Some main title
|
||||||
|
|
||||||
|
<!-- TOC START -->
|
||||||
|
- [First Title](#first-title)
|
||||||
|
- [Second Spaced Title](#second-spaced-title)
|
||||||
|
- [Last Title](#last-title)
|
||||||
|
<!-- TOC END -->
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="first-title"></a>
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="second-spaced-title"></a>
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="last-title"></a>
|
||||||
|
## Last Title`);
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: sourceMarkdown,
|
||||||
|
commentStyle: 'liquid',
|
||||||
|
})).to.equal(`# Some main title
|
||||||
|
|
||||||
|
{%- # TOC START -%}
|
||||||
|
- [First Title](#first-title)
|
||||||
|
- [Second Spaced Title](#second-spaced-title)
|
||||||
|
* [Title with Link TOC](#title-with-link-toc)
|
||||||
|
* [Title with code \`var\`](#title-with-code-var)
|
||||||
|
- [Last Title](#last-title)
|
||||||
|
{%- # TOC END -%}
|
||||||
|
|
||||||
|
{%- # TOC ANCHOR -%}<a name="first-title"></a>
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
{%- # TOC ANCHOR -%}<a name="second-spaced-title"></a>
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
{%- # TOC ANCHOR -%}<a name="title-with-link-toc"></a>
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
{%- # TOC ANCHOR -%}<a name="title-with-code-var"></a>
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
{%- # TOC ANCHOR -%}<a name="last-title"></a>
|
||||||
|
## Last Title`);
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: sourceMarkdown,
|
||||||
|
generateAnchors: false,
|
||||||
|
})).to.equal(`# Some main title
|
||||||
|
|
||||||
|
<!-- TOC START -->
|
||||||
|
- [First Title](#first-title)
|
||||||
|
- [Second Spaced Title](#second-spaced-title)
|
||||||
|
* [Title with Link TOC](#title-with-link-toc)
|
||||||
|
* [Title with code \`var\`](#title-with-code-var)
|
||||||
|
- [Last Title](#last-title)
|
||||||
|
<!-- TOC END -->
|
||||||
|
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
## Last Title`);
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: sourceMarkdown,
|
||||||
|
indentSpaces: 4,
|
||||||
|
indentChars: '-',
|
||||||
|
concatSpaces: false,
|
||||||
|
})).to.equal(`# Some main title
|
||||||
|
|
||||||
|
<!-- TOC START -->
|
||||||
|
- [First Title](#first-title)
|
||||||
|
- [Second Spaced Title](#second--spaced--title)
|
||||||
|
- [Title with Link TOC](#title-with-link-toc)
|
||||||
|
- [Title with code \`var\`](#title-with-code-var)
|
||||||
|
- [Last Title](#last-title)
|
||||||
|
<!-- TOC END -->
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="first-title"></a>
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="second--spaced--title"></a>
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="title-with-link-toc"></a>
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="title-with-code-var"></a>
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="last-title"></a>
|
||||||
|
## Last Title`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Regenerate TOC correctly', async () => {
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: `# Some main title
|
||||||
|
|
||||||
|
<!-- TOC START -->
|
||||||
|
- [First Title](#first-title)
|
||||||
|
- [Second Spaced Title](#second--spaced--title)
|
||||||
|
- [Title with Link TOC](#title-with-link-toc)
|
||||||
|
- [Title with code \`var\`](#title-with-code-var)
|
||||||
|
- [Last Title](#last-title)
|
||||||
|
<!-- TOC END -->
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="first-title"></a>
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="second--spaced--title"></a>
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="title-with-link-toc"></a>
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="title-with-code-var"></a>
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="last-title"></a>
|
||||||
|
## Last Title`,
|
||||||
|
anchorPrefix: 'h-',
|
||||||
|
})).to.equal(`# Some main title
|
||||||
|
|
||||||
|
<!-- TOC START -->
|
||||||
|
- [First Title](#h-first-title)
|
||||||
|
- [Second Spaced Title](#h-second-spaced-title)
|
||||||
|
* [Title with Link TOC](#h-title-with-link-toc)
|
||||||
|
* [Title with code \`var\`](#h-title-with-code-var)
|
||||||
|
- [Last Title](#h-last-title)
|
||||||
|
<!-- TOC END -->
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="first-title"></a>
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="second-spaced-title"></a>
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="title-with-link-toc"></a>
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="title-with-code-var"></a>
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="last-title"></a>
|
||||||
|
## Last Title`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Generate distinct TOC ids', async () => {
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: `# Some main title
|
||||||
|
|
||||||
|
[TOC]
|
||||||
|
|
||||||
|
## Same Title 1
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
## Same Title 1
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
### Same title 1
|
||||||
|
|
||||||
|
Some text`,
|
||||||
|
anchorPrefix: 'h-',
|
||||||
|
})).to.equal(`# Some main title
|
||||||
|
|
||||||
|
<!-- TOC START -->
|
||||||
|
- [Same Title 1](#h-same-title-1)
|
||||||
|
- [Same Title 1](#h-same-title-1-1)
|
||||||
|
* [Same title 1](#h-same-title-1-2)
|
||||||
|
<!-- TOC END -->
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="same-title-1"></a>
|
||||||
|
## Same Title 1
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="same-title-1-1"></a>
|
||||||
|
## Same Title 1
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="same-title-1-2"></a>
|
||||||
|
### Same title 1
|
||||||
|
|
||||||
|
Some text`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Generate ids for non latin', async () => {
|
||||||
|
expect(getTocMarkdown({
|
||||||
|
markdown: `# Some main title
|
||||||
|
|
||||||
|
[TOC]
|
||||||
|
|
||||||
|
## Привет non-latin 你好
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
## 😄 emoji
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
### Other title 1
|
||||||
|
|
||||||
|
Some text`,
|
||||||
|
anchorPrefix: 'h-',
|
||||||
|
})).to.equal(`# Some main title
|
||||||
|
|
||||||
|
<!-- TOC START -->
|
||||||
|
- [Привет non-latin 你好](#h--non-latin-)
|
||||||
|
- [😄 emoji](#h--emoji)
|
||||||
|
* [Other title 1](#h-other-title-1)
|
||||||
|
<!-- TOC END -->
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="-non-latin-"></a>
|
||||||
|
## Привет non-latin 你好
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="-emoji"></a>
|
||||||
|
## 😄 emoji
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- TOC ANCHOR --><a name="other-title-1"></a>
|
||||||
|
### Other title 1
|
||||||
|
|
||||||
|
Some text`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,183 @@
|
||||||
|
import sanitizeHtml from 'sanitize-html';
|
||||||
|
|
||||||
|
function stripNonLatinCharacters(text: string) {
|
||||||
|
return text.replace(/[^A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u02BB\u02EE\uA78C\d\s_-]/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
function transformInlineCode(text: string, transform: (s: string) => string) {
|
||||||
|
return text.replace(/`(.*?)`/g, (_, p) => {
|
||||||
|
return `\`${transform(p)}\``;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function spacesToDash(text: string) {
|
||||||
|
return text.replace(/\s/g, '-');
|
||||||
|
};
|
||||||
|
|
||||||
|
function stripHtmlTags(text: string) {
|
||||||
|
return sanitizeHtml(text, {
|
||||||
|
allowedTags: [],
|
||||||
|
allowedAttributes: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripMarkdownLinks(text: string, replacement: string = '$1') {
|
||||||
|
return text.replace(/\[([^\]]*)\]\([^\)]*\)/g, replacement); // NOSONAR
|
||||||
|
};
|
||||||
|
|
||||||
|
function concatDashes(text: string) {
|
||||||
|
return text.replace(/--+/g, '-');
|
||||||
|
};
|
||||||
|
|
||||||
|
function removeUnderscoreBoldAndItalics(text: string) {
|
||||||
|
const underscoreBoldAndItalicsRegexes = ['__', '_'].map((it) => {
|
||||||
|
return new RegExp(`\\b${it}([^_\\s]|[^_\\s].*?[^_\\s])${it}\\b`, 'g');
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = text;
|
||||||
|
|
||||||
|
underscoreBoldAndItalicsRegexes.forEach((regex) => {
|
||||||
|
result = result.replace(regex, '$1');
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function genericAnchorGenerator(text: string, concatSpaces: boolean) {
|
||||||
|
let result = text;
|
||||||
|
result = result.toLowerCase();
|
||||||
|
result = transformInlineCode(result, (s: string) => {
|
||||||
|
return stripNonLatinCharacters(s);
|
||||||
|
});
|
||||||
|
result = removeUnderscoreBoldAndItalics(result);
|
||||||
|
result = stripHtmlTags(result);
|
||||||
|
result = stripMarkdownLinks(result);
|
||||||
|
result = result.trim();
|
||||||
|
result = stripNonLatinCharacters(result);
|
||||||
|
result = spacesToDash(result);
|
||||||
|
if (concatSpaces) {
|
||||||
|
result = concatDashes(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function escapeRegExp(string: string) {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Title {
|
||||||
|
level: number
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
md: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTitles(markdown: string, idGenerator: (titleMarkdownContent: string) => string) {
|
||||||
|
const titles: Title[] = [];
|
||||||
|
|
||||||
|
markdown = markdown.replace(/^```[\s\S]*?\n```/mg, () => {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
markdown = markdown.replace(/^~~~[\s\S]*?\n~~~/mg, () => {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
[...markdown.matchAll(/^(#+)(.*$)/mg)].forEach( // NOSONAR
|
||||||
|
([match, levelString, titleContent]) => {
|
||||||
|
const level = levelString.length;
|
||||||
|
|
||||||
|
titles.push({
|
||||||
|
md: match,
|
||||||
|
level,
|
||||||
|
id: idGenerator(titleContent),
|
||||||
|
name: titleContent.trim(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return titles;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getTocMarkdown({
|
||||||
|
markdown,
|
||||||
|
generateAnchors = true,
|
||||||
|
indentChars = '-*+',
|
||||||
|
indentSpaces = 2,
|
||||||
|
maxLevel = -1,
|
||||||
|
anchorPrefix = '',
|
||||||
|
concatSpaces = true,
|
||||||
|
commentStyle = 'html',
|
||||||
|
}: {
|
||||||
|
markdown: string
|
||||||
|
generateAnchors?: boolean
|
||||||
|
indentChars?: string
|
||||||
|
indentSpaces?: number
|
||||||
|
maxLevel?: number
|
||||||
|
anchorPrefix?: string
|
||||||
|
concatSpaces?: boolean
|
||||||
|
commentStyle?: 'html' | 'liquid'
|
||||||
|
}) {
|
||||||
|
const allIds: { [id: string]: number } = {};
|
||||||
|
const getFinalId = (id: string) => {
|
||||||
|
if (typeof allIds[id] === 'undefined') {
|
||||||
|
allIds[id] = 0;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
allIds[id] += 1;
|
||||||
|
return `${id}-${allIds[id]}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const titles = getTitles(markdown, titleContent => getFinalId(genericAnchorGenerator(titleContent, concatSpaces)));
|
||||||
|
|
||||||
|
const createLink = (linkText: string, url: string) => {
|
||||||
|
return `[${linkText.replace(/\[/g, '\\[').replace(/\]/g, '\\]')}](${url.replace(/\(/g, '%28').replace(/\(/g, '%29')})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
let markdownTOC = '';
|
||||||
|
let resultMarkdown = markdown;
|
||||||
|
const commentOpen = commentStyle === 'html' ? '<!--' : '{%- #';
|
||||||
|
const commentClose = commentStyle === 'html' ? '-->' : '-%}';
|
||||||
|
|
||||||
|
resultMarkdown = resultMarkdown.replace(
|
||||||
|
new RegExp(`\n${escapeRegExp(commentOpen)} TOC START.*?TOC END ${escapeRegExp(commentClose)}\n`, 'smg'),
|
||||||
|
'\n[TOC]\n',
|
||||||
|
);
|
||||||
|
resultMarkdown = resultMarkdown.replace(
|
||||||
|
new RegExp(`^${escapeRegExp(commentOpen)} TOC ANCHOR.*?\n`, 'mg'),
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
|
titles.forEach((title) => {
|
||||||
|
if (title.level === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxLevel > 0 && title.level > maxLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const level = title.level - 2;
|
||||||
|
let offset = '';
|
||||||
|
if (level) {
|
||||||
|
offset = `${Array.from({ length: level * indentSpaces }).join(' ')} `;
|
||||||
|
}
|
||||||
|
const bulletChar = indentChars[level] ?? indentChars.slice(-1)[0];
|
||||||
|
|
||||||
|
const anchorName = `${anchorPrefix}${title.id}`;
|
||||||
|
|
||||||
|
markdownTOC += `${offset}${bulletChar} ${createLink(stripMarkdownLinks(title.name), `#${anchorName}`)}\n`;
|
||||||
|
|
||||||
|
if (generateAnchors) {
|
||||||
|
resultMarkdown = resultMarkdown.replace(
|
||||||
|
new RegExp(`(?<!^${commentOpen} TOC ANCHOR.*\n)^${escapeRegExp(title.md)}`, 'm'),
|
||||||
|
`${commentOpen} TOC ANCHOR ${commentClose}<a name="${title.id}"></a>\n${title.md}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resultMarkdown = resultMarkdown.replace(
|
||||||
|
/^\[TOC\]\n/mg,
|
||||||
|
`${commentOpen} TOC START ${commentClose}\n${markdownTOC}${commentOpen} TOC END ${commentClose}\n`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return resultMarkdown;
|
||||||
|
}
|
127
src/tools/markdown-toc-generator/markdown-toc-generator.vue
Normal file
127
src/tools/markdown-toc-generator/markdown-toc-generator.vue
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { withDefaultOnError } from '../../utils/defaults';
|
||||||
|
import {
|
||||||
|
getTocMarkdown,
|
||||||
|
} from './markdown-toc-generator.service';
|
||||||
|
import { useQueryParamOrStorage } from '@/composable/queryParams';
|
||||||
|
|
||||||
|
const markdown = ref(`# Some main title
|
||||||
|
|
||||||
|
[TOC]
|
||||||
|
|
||||||
|
## First Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
## Second Spaced Title
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
### Title with Link [TOC](http://it-tools.tech)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
## some bash code
|
||||||
|
echo 'test';
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Title with code \`var\`
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
## Last Title`);
|
||||||
|
const generateAnchors = useQueryParamOrStorage({ name: 'anchors', storageName: 'md-toc-gen:anchors', defaultValue: true });
|
||||||
|
const indentChars = useQueryParamOrStorage({ name: 'bullets', storageName: 'md-toc-gen:bullets', defaultValue: '-*+' });
|
||||||
|
const indentSpaces = ref(3);
|
||||||
|
const maxLevel = useQueryParamOrStorage({ name: 'max', storageName: 'md-toc-gen:max', defaultValue: -1 });
|
||||||
|
const anchorPrefix = useQueryParamOrStorage({ name: 'prefix', storageName: 'md-toc-gen:prefix', defaultValue: '' });
|
||||||
|
const concatSpaces = useQueryParamOrStorage({ name: 'concat', storageName: 'md-toc-gen:concat', defaultValue: false });
|
||||||
|
const commentStyle = useQueryParamOrStorage({ name: 'comment', storageName: 'md-toc-gen:comment', defaultValue: 'html' });
|
||||||
|
|
||||||
|
const markdownWithTOC = computed(() => withDefaultOnError(() => {
|
||||||
|
return getTocMarkdown({
|
||||||
|
markdown: markdown.value,
|
||||||
|
anchorPrefix: anchorPrefix.value,
|
||||||
|
commentStyle: commentStyle.value as ('html' | 'liquid'),
|
||||||
|
concatSpaces: concatSpaces.value,
|
||||||
|
generateAnchors: generateAnchors.value,
|
||||||
|
indentChars: indentChars.value,
|
||||||
|
indentSpaces: indentSpaces.value,
|
||||||
|
maxLevel: maxLevel.value,
|
||||||
|
});
|
||||||
|
}, ''));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<c-card title="Options" mb-2>
|
||||||
|
<n-space>
|
||||||
|
<n-form-item label-placement="left">
|
||||||
|
<n-checkbox v-model:checked="generateAnchors">
|
||||||
|
Generate Anchors
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="Max Heading Level:" label-placement="left">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="maxLevel"
|
||||||
|
placeholder="Max Heading Level..."
|
||||||
|
:max="6" :min="-1"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-space>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Advanced</summary>
|
||||||
|
<n-space>
|
||||||
|
<n-form-item label-placement="left">
|
||||||
|
<n-checkbox v-model:checked="concatSpaces">
|
||||||
|
Concat Spaces
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="indentChars"
|
||||||
|
label="Bullet Chars"
|
||||||
|
label-position="left"
|
||||||
|
placeholder="Bullet Chars"
|
||||||
|
/>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="anchorPrefix"
|
||||||
|
label="Anchors Prefix"
|
||||||
|
label-position="left"
|
||||||
|
placeholder="Anchors Prefix"
|
||||||
|
/>
|
||||||
|
<n-form-item label="Indents: " label-placement="left">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="indentSpaces"
|
||||||
|
placeholder="Indents..."
|
||||||
|
:max="10" :min="1"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<c-select
|
||||||
|
v-model:value="commentStyle"
|
||||||
|
label="Comment Styles"
|
||||||
|
label-position="left"
|
||||||
|
:options="['html', 'liquid']"
|
||||||
|
placeholder="Comment Styles"
|
||||||
|
/>
|
||||||
|
</n-space>
|
||||||
|
</details>
|
||||||
|
</c-card>
|
||||||
|
|
||||||
|
<c-card title="Input markdown" mb-2>
|
||||||
|
<n-p>You can paste a document with existing TOC (generated by this tool) or add a <code>[TOC]</code> marker in your document (on a single line)</n-p>
|
||||||
|
<c-input-text
|
||||||
|
v-model:value="markdown"
|
||||||
|
placeholder="Put your markdown here..."
|
||||||
|
multiline
|
||||||
|
rows="8"
|
||||||
|
/>
|
||||||
|
</c-card>
|
||||||
|
|
||||||
|
<c-card title="Output markdown with TOC" mb-2>
|
||||||
|
<textarea-copyable
|
||||||
|
language="markdown"
|
||||||
|
:value="markdownWithTOC"
|
||||||
|
/>
|
||||||
|
</c-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
Loading…
Add table
Add a link
Reference in a new issue