From e32d38c057da3d3baed71b8dd32c129c155bc7f1 Mon Sep 17 00:00:00 2001 From: sharevb Date: Sat, 18 May 2024 15:20:38 +0200 Subject: [PATCH 1/6] feat(new tool): Markdown TOC Generator Fix #573 --- components.d.ts | 4 + pnpm-lock.yaml | 19 +- src/composable/queryParams.ts | 33 +- src/tools/index.ts | 2 + src/tools/markdown-toc-generator/index.ts | 12 + .../markdown-contents.d.ts | 6 + .../markdown-toc-generator.service.test.ts | 380 ++++++++++++++++++ .../markdown-toc-generator.service.ts | 178 ++++++++ .../markdown-toc-generator.vue | 91 +++++ 9 files changed, 715 insertions(+), 10 deletions(-) create mode 100644 src/tools/markdown-toc-generator/index.ts create mode 100644 src/tools/markdown-toc-generator/markdown-contents.d.ts create mode 100644 src/tools/markdown-toc-generator/markdown-toc-generator.service.test.ts create mode 100644 src/tools/markdown-toc-generator/markdown-toc-generator.service.ts create mode 100644 src/tools/markdown-toc-generator/markdown-toc-generator.vue diff --git a/components.d.ts b/components.d.ts index f2c3146f..2aa235c0 100644 --- a/components.d.ts +++ b/components.d.ts @@ -119,6 +119,7 @@ declare module '@vue/runtime-core' { 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'] MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.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'] 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'] @@ -129,15 +130,18 @@ declare module '@vue/runtime-core' { NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] + NColorPicker: typeof import('naive-ui')['NColorPicker'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NDivider: typeof import('naive-ui')['NDivider'] 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'] NH3: typeof import('naive-ui')['NH3'] 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'] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd6c38c9..17569742 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3351,7 +3351,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.7.2(vue@3.3.4) + '@vueuse/shared': 10.11.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3984,19 +3984,19 @@ packages: - vue dev: false - /@vueuse/shared@10.3.0(vue@3.3.4): - resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} + /@vueuse/shared@10.11.0(vue@3.3.4): + resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==} dependencies: - vue-demi: 0.14.5(vue@3.3.4) + vue-demi: 0.14.8(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue dev: false - /@vueuse/shared@10.7.2(vue@3.3.4): - resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==} + /@vueuse/shared@10.3.0(vue@3.3.4): + resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} dependencies: - vue-demi: 0.14.6(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -9151,8 +9151,8 @@ packages: vue: 3.3.4 dev: false - /vue-demi@0.14.6(vue@3.3.4): - resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} + /vue-demi@0.14.8(vue@3.3.4): + resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==} engines: {node: '>=12'} hasBin: true requiresBuild: true @@ -9442,6 +9442,7 @@ packages: /workbox-google-analytics@7.0.0: resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained dependencies: workbox-background-sync: 7.0.0 workbox-core: 7.0.0 diff --git a/src/composable/queryParams.ts b/src/composable/queryParams.ts index 9699abbc..7cc8cc0d 100644 --- a/src/composable/queryParams.ts +++ b/src/composable/queryParams.ts @@ -1,7 +1,8 @@ import { useRouteQuery } from '@vueuse/router'; import { computed } from 'vue'; +import { useStorage } from '@vueuse/core'; -export { useQueryParam }; +export { useQueryParam, useQueryParamOrStorage }; const transformers = { number: { @@ -16,6 +17,12 @@ const transformers = { fromQuery: (value: string) => value.toLowerCase() === 'true', toQuery: (value: boolean) => (value ? 'true' : 'false'), }, + object: { + fromQuery: (value: string) => { + return JSON.parse(value); + }, + toQuery: (value: object) => JSON.stringify(value), + }, }; function useQueryParam({ name, defaultValue }: { name: string; defaultValue: T }) { @@ -33,3 +40,27 @@ function useQueryParam({ name, defaultValue }: { name: string; defaultValue: }, }); } + +function useQueryParamOrStorage({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) { + const type = typeof defaultValue; + const transformer = transformers[type as keyof typeof transformers] ?? transformers.string; + + const storageRef = useStorage(storageName, defaultValue); + const proxyDefaultValue = transformer.toQuery(defaultValue as never); + const proxy = useRouteQuery(name, proxyDefaultValue); + + const r = ref(defaultValue); + + watch(r, + (value) => { + proxy.value = transformer.toQuery(value as never); + storageRef.value = value as never; + }, + { deep: true }); + + r.value = (proxy.value && proxy.value !== proxyDefaultValue + ? transformer.fromQuery(proxy.value) as unknown as T + : storageRef.value as T) as never; + + return r; +} diff --git a/src/tools/index.ts b/src/tools/index.ts index aa861c93..da1fe145 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as markdownTocGenerator } from './markdown-toc-generator'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -107,6 +108,7 @@ export const toolsByCategory: ToolCategory[] = [ listConverter, tomlToJson, tomlToYaml, + markdownTocGenerator, ], }, { diff --git a/src/tools/markdown-toc-generator/index.ts b/src/tools/markdown-toc-generator/index.ts new file mode 100644 index 00000000..37f540d3 --- /dev/null +++ b/src/tools/markdown-toc-generator/index.ts @@ -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', 'toc', 'generator'], + component: () => import('./markdown-toc-generator.vue'), + icon: Table, + createdAt: new Date('2024-05-11'), +}); diff --git a/src/tools/markdown-toc-generator/markdown-contents.d.ts b/src/tools/markdown-toc-generator/markdown-contents.d.ts new file mode 100644 index 00000000..156e4b7c --- /dev/null +++ b/src/tools/markdown-toc-generator/markdown-contents.d.ts @@ -0,0 +1,6 @@ +declare module 'markdown-contents'{ + declare class MarkdownContents { + markdown(): string; + } + export default function Create(markdown: string):MarkdownContents; +} \ No newline at end of file diff --git a/src/tools/markdown-toc-generator/markdown-toc-generator.service.test.ts b/src/tools/markdown-toc-generator/markdown-toc-generator.service.test.ts new file mode 100644 index 00000000..d1a4d778 --- /dev/null +++ b/src/tools/markdown-toc-generator/markdown-toc-generator.service.test.ts @@ -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 + + +- [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) + + + +## 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, + maxLevel: 2, + })).to.equal(`# Some main title + + +- [First Title](#first-title) +- [Second Spaced Title](#second-spaced-title) +- [Last Title](#last-title) + + + +## 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, + 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 -%} +## First Title + +Some text + +{%- # TOC ANCHOR -%} +## Second Spaced Title + +Some text + +{%- # TOC ANCHOR -%} +### Title with Link [TOC](http://it-tools.tech) + +\`\`\` +## some bash code +echo 'test'; +\`\`\` + +{%- # TOC ANCHOR -%} +### Title with code \`var\` + +Some text + +{%- # TOC ANCHOR -%} +## Last Title`); + expect(getTocMarkdown({ + markdown: sourceMarkdown, + generateAnchors: false, + })).to.equal(`# Some main title + + +- [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) + + +## 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 + + +- [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) + + + +## 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`); + }); + + it('Regenerate TOC correctly', async () => { + expect(getTocMarkdown({ + markdown: `# Some main title + + +- [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) + + + +## 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`, + anchorPrefix: 'h-', + })).to.equal(`# Some main title + + +- [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) + + + +## 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`); + }); + + 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 + + +- [Same Title 1](#h-same-title-1) +- [Same Title 1](#h-same-title-1-1) + * [Same title 1](#h-same-title-1-2) + + + +## Same Title 1 + +Some text + + +## Same Title 1 + +Some text + + +### 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 + + +- [Привет non-latin 你好](#h--non-latin-) +- [😄 emoji](#h--emoji) + * [Other title 1](#h-other-title-1) + + + +## Привет non-latin 你好 + +Some text + + +## 😄 emoji + +Some text + + +### Other title 1 + +Some text`); + }); +}); diff --git a/src/tools/markdown-toc-generator/markdown-toc-generator.service.ts b/src/tools/markdown-toc-generator/markdown-toc-generator.service.ts new file mode 100644 index 00000000..e023c6b5 --- /dev/null +++ b/src/tools/markdown-toc-generator/markdown-toc-generator.service.ts @@ -0,0 +1,178 @@ +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 text.replace(/<.*?>/g, ''); +}; + +function stripMarkdownLinks(text: string, replacement: string = '$1') { + return text.replace(/\[([^\]]*)\]\([^\)]*\)/g, replacement); +}; + +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( + ([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('[', '\\[').replace(']', '\\]')}](${url.replace('(', '%28').replace(')', '%29')})`; + }; + + let markdownTOC = ''; + let resultMarkdown = markdown; + const commentOpen = commentStyle === 'html' ? '' : '-%}'; + + resultMarkdown = resultMarkdown.replace( + new RegExp(`\n${commentOpen} TOC START.*?TOC END ${commentClose}\n`, 'smg'), + '\n[TOC]\n', + ); + resultMarkdown = resultMarkdown.replace( + new RegExp(`^${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(`(?\n${title.md}`, + ); + } + }); + + resultMarkdown = resultMarkdown.replace( + /^\[TOC\]\n/mg, + `${commentOpen} TOC START ${commentClose}\n${markdownTOC}${commentOpen} TOC END ${commentClose}\n`, + ); + + return resultMarkdown; +} diff --git a/src/tools/markdown-toc-generator/markdown-toc-generator.vue b/src/tools/markdown-toc-generator/markdown-toc-generator.vue new file mode 100644 index 00000000..aa12bd89 --- /dev/null +++ b/src/tools/markdown-toc-generator/markdown-toc-generator.vue @@ -0,0 +1,91 @@ + + + From dbfe5113b0e06e04f648cc81f91450db5c12e77a Mon Sep 17 00:00:00 2001 From: ShareVB Date: Sun, 9 Jun 2024 12:32:10 +0200 Subject: [PATCH 2/6] chore: fix strange corepack message Fix corepack claiming strange thing : UsageError: This project is configured to use yarn because /home/runner/work/it-tools/it-tools/package.json has a "packageManager" field --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f39ff1d..a458d7fc 100644 --- a/package.json +++ b/package.json @@ -137,5 +137,6 @@ "vitest": "^0.34.0", "workbox-window": "^7.0.0", "zx": "^7.2.1" - } + }, + "packageManager": "pnpm@8.15.3" } From d92a285cb2d0cb5e0973f03282ce155b2c162604 Mon Sep 17 00:00:00 2001 From: ShareVB Date: Fri, 12 Jul 2024 23:26:26 +0200 Subject: [PATCH 3/6] fix: lock file --- pnpm-lock.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4788c9c0..02f7666b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3354,7 +3354,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.8.0(vue@3.3.4) + '@vueuse/shared': 10.11.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3996,10 +3996,10 @@ packages: - vue dev: false - /@vueuse/shared@10.8.0(vue@3.3.4): - resolution: {integrity: sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ==} + /@vueuse/shared@10.3.0(vue@3.3.4): + resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} dependencies: - vue-demi: 0.14.7(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -9158,8 +9158,8 @@ packages: vue: 3.3.4 dev: false - /vue-demi@0.14.7(vue@3.3.4): - resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} + /vue-demi@0.14.8(vue@3.3.4): + resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==} engines: {node: '>=12'} hasBin: true requiresBuild: true From 786991433aeb440262c7560467e34b491d3283b1 Mon Sep 17 00:00:00 2001 From: ShareVB Date: Sun, 14 Jul 2024 22:21:21 +0200 Subject: [PATCH 4/6] feat: better UI --- components.d.ts | 3 + src/components/TextareaCopyable.vue | 2 + src/tools/markdown-toc-generator/index.ts | 2 +- .../markdown-toc-generator.vue | 118 ++++++++++++------ 4 files changed, 83 insertions(+), 42 deletions(-) diff --git a/components.d.ts b/components.d.ts index 2aa235c0..0f7600cb 100644 --- a/components.d.ts +++ b/components.d.ts @@ -128,6 +128,7 @@ declare module '@vue/runtime-core' { MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] + NCheckbox: typeof import('naive-ui')['NCheckbox'] NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NColorPicker: typeof import('naive-ui')['NColorPicker'] @@ -147,7 +148,9 @@ declare module '@vue/runtime-core' { NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] + NP: typeof import('naive-ui')['NP'] NScrollbar: typeof import('naive-ui')['NScrollbar'] + NSpace: typeof import('naive-ui')['NSpace'] NSpin: typeof import('naive-ui')['NSpin'] 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'] diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue index 8b0aae61..9585177d 100644 --- a/src/components/TextareaCopyable.vue +++ b/src/components/TextareaCopyable.vue @@ -7,6 +7,7 @@ import sqlHljs from 'highlight.js/lib/languages/sql'; import xmlHljs from 'highlight.js/lib/languages/xml'; import yamlHljs from 'highlight.js/lib/languages/yaml'; import iniHljs from 'highlight.js/lib/languages/ini'; +import markdownHljs from 'highlight.js/lib/languages/markdown'; import { useCopy } from '@/composable/copy'; const props = withDefaults( @@ -30,6 +31,7 @@ hljs.registerLanguage('html', xmlHljs); hljs.registerLanguage('xml', xmlHljs); hljs.registerLanguage('yaml', yamlHljs); hljs.registerLanguage('toml', iniHljs); +hljs.registerLanguage('markdown', markdownHljs); const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props); const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) }; diff --git a/src/tools/markdown-toc-generator/index.ts b/src/tools/markdown-toc-generator/index.ts index 37f540d3..d6ad8972 100644 --- a/src/tools/markdown-toc-generator/index.ts +++ b/src/tools/markdown-toc-generator/index.ts @@ -5,7 +5,7 @@ export const tool = defineTool({ name: 'Markdown toc generator', path: '/markdown-toc-generator', description: 'Generate a TOC from a markdown file/content', - keywords: ['markdown', 'toc', 'generator'], + keywords: ['markdown', 'md', 'toc', 'generator'], component: () => import('./markdown-toc-generator.vue'), icon: Table, createdAt: new Date('2024-05-11'), diff --git a/src/tools/markdown-toc-generator/markdown-toc-generator.vue b/src/tools/markdown-toc-generator/markdown-toc-generator.vue index aa12bd89..a479829c 100644 --- a/src/tools/markdown-toc-generator/markdown-toc-generator.vue +++ b/src/tools/markdown-toc-generator/markdown-toc-generator.vue @@ -5,13 +5,36 @@ import { } from './markdown-toc-generator.service'; import { useQueryParamOrStorage } from '@/composable/queryParams'; -const markdown = ref(''); +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(2); +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: true }); +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(() => { @@ -31,42 +54,57 @@ const markdownWithTOC = computed(() => withDefaultOnError(() => {