Skip to content

Commit 3867758

Browse files
committed
fix: removed html-to-markdown helper, restored escape html to server with imports, reverted packages, new table to md function
1 parent 39e97d3 commit 3867758

8 files changed

Lines changed: 69 additions & 106 deletions

File tree

app/pages/compare.vue

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
<script setup lang="ts">
22
import { NO_DEPENDENCY_ID } from '~/composables/usePackageComparison'
33
import { useRouteQuery } from '@vueuse/router'
4-
import { escapeHtml } from '#shared/utils/html'
54
import { NPMX_SITE } from '#shared/utils/constants'
65
76
definePageMeta({
87
name: 'compare',
98
})
109
10+
const { locale } = useI18n()
1111
const router = useRouter()
1212
const canGoBack = useCanGoBack()
1313
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
14-
const gridRef = useTemplateRef<HTMLDivElement>('gridRef')
1514
1615
// Sync packages with URL query param (stable ref - doesn't change on other query changes)
1716
const packagesParam = useRouteQuery<string>('packages', '', { mode: 'replace' })
@@ -83,55 +82,56 @@ const gridHeaders = computed(() =>
8382
gridColumns.value.map(col => (col.version ? `${col.name}@${col.version}` : col.name)),
8483
)
8584
86-
function copyComparisonGridAsMd() {
87-
const grid = gridRef.value?.querySelector('.comparison-grid')
88-
if (!grid) return
89-
const md = gridToMarkdown(grid as HTMLElement)
90-
copy(md)
91-
}
92-
9385
/*
94-
* Convert the comparison grid DOM to a Markdown table.
95-
* We build a proper HTML <table> from the grid structure and delegate to `htmlToMarkdown` for the actual conversion.
86+
* Convert the comparison grid data to a Markdown table.
9687
*/
97-
function gridToMarkdown(gridEl: HTMLElement): string {
98-
const children = Array.from(gridEl.children)
99-
const headerRow = children[0]
100-
const dataRows = children.slice(1)
101-
102-
if (!headerRow || dataRows.length === 0) return ''
103-
104-
const headerCells = Array.from(headerRow.children).slice(1)
105-
if (headerCells.length === 0) return ''
106-
107-
const ths = headerCells.map(cell => {
108-
const link = cell.querySelector('a')
109-
if (link) {
110-
const href = link.getAttribute('href') || ''
111-
const absoluteHref = /^https?:\/\/|^\/\//.test(href) ? href : `${NPMX_SITE}${href}`
112-
return `<th><a href="${escapeHtml(absoluteHref)}">${escapeHtml(link.textContent?.trim() || '')}</a></th>`
113-
}
114-
return `<th>${escapeHtml(cell.textContent?.trim() || '')}</th>`
115-
})
116-
117-
const trs = dataRows.map(row => {
118-
const rowChildren = Array.from(row.children)
119-
const label = rowChildren[0]?.textContent?.trim() || ''
120-
const valueCells = rowChildren.slice(1)
121-
const tds = [label, ...valueCells.map(cell => cell.textContent?.trim() || '-')]
122-
.map(v => `<td>${escapeHtml(v)}</td>`)
123-
.join('')
124-
return `<tr>${tds}</tr>`
88+
function exportComparisonDataAsMarkdown() {
89+
const mdData: Array<Array<string>> = []
90+
mdData.push([
91+
'',
92+
...gridColumns.value.map(
93+
(col, index) =>
94+
`[${gridHeaders.value[index]}](${NPMX_SITE}/package/${col?.name}/v/${col?.version})`,
95+
),
96+
])
97+
const maxLengths = gridHeaders.value.map(header => header.length)
98+
99+
selectedFacets.value.forEach((facet, index) => {
100+
const label = facet.label
101+
const data = getFacetValues(facet.id)
102+
mdData.push([
103+
label,
104+
...data.map(item =>
105+
item?.type === 'date'
106+
? new Date(item.display).toLocaleDateString(locale.value, {
107+
year: 'numeric',
108+
month: 'short',
109+
day: 'numeric',
110+
})
111+
: item?.display || '',
112+
),
113+
])
114+
mdData?.[index + 1]?.forEach((item, index) => {
115+
if (item.length > (maxLengths?.[index] || 0)) {
116+
maxLengths[index] = item.length
117+
}
118+
})
125119
})
126120
127-
const tableHtml = [
128-
'<table>',
129-
`<thead><tr><th>Metric</th>${ths.join('')}</tr></thead>`,
130-
`<tbody>${trs.join('')}</tbody>`,
131-
'</table>',
132-
].join('')
121+
const markdown = mdData.reduce((result, row, index) => {
122+
// replacing pipe `|` with `ǀ` (U+01C0 Latin Letter Dental Click) to avoid breaking tables
123+
result += `| ${row
124+
.map((el, ind) => el.padEnd(maxLengths[ind] || 0, ' ').replace(/\|/g, 'ǀ'))
125+
.join(' | ')} |`
126+
if (index === 0) {
127+
result += `\n|`
128+
maxLengths.forEach(len => (result += ` ${'-'.padEnd(len, '-')} |`))
129+
}
130+
result += `\n`
131+
return result
132+
}, '')
133133
134-
return htmlToMarkdown(tableHtml, { tablePipeAlign: false })
134+
copy(markdown)
135135
}
136136
137137
useSeoMeta({
@@ -253,7 +253,7 @@ useSeoMeta({
253253
:copied="copied"
254254
:copy-text="$t('compare.packages.copy_as_markdown')"
255255
class="mb-4 inline-block hidden md:inline-flex"
256-
@click="copyComparisonGridAsMd"
256+
@click="exportComparisonDataAsMarkdown"
257257
>
258258
<h2 id="comparison-heading" class="text-xs text-fg-subtle uppercase tracking-wider">
259259
{{ $t('compare.packages.section_comparison') }}
@@ -280,7 +280,7 @@ useSeoMeta({
280280

281281
<div v-else-if="packagesData && packagesData.some(p => p !== null)">
282282
<!-- Desktop: Grid layout -->
283-
<div ref="gridRef" class="hidden md:block overflow-x-auto">
283+
<div class="hidden md:block overflow-x-auto">
284284
<CompareComparisonGrid :columns="gridColumns" :show-no-dependency="showNoDependency">
285285
<CompareFacetRow
286286
v-for="facet in selectedFacets"

app/utils/html-to-markdown.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,7 @@
8686
"fast-npm-meta": "1.0.0",
8787
"focus-trap": "^7.8.0",
8888
"gray-matter": "4.0.3",
89-
"hast-util-from-parse5": "8.0.3",
90-
"hast-util-to-mdast": "10.1.2",
9189
"marked": "17.0.1",
92-
"mdast-util-gfm-table": "2.0.0",
93-
"mdast-util-to-markdown": "2.1.2",
94-
"parse5": "8.0.0",
9590
"module-replacements": "2.11.0",
9691
"nuxt": "4.3.1",
9792
"nuxt-og-image": "5.1.13",

pnpm-lock.yaml

Lines changed: 0 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/utils/docs/render.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import type { DenoDocNode, JsDocTag } from '#shared/types/deno-doc'
1010
import { highlightCodeBlock } from '../shiki'
1111
import { formatParam, formatType, getNodeSignature } from './format'
1212
import { groupMergedByKind } from './processing'
13-
import { escapeHtml } from '#shared/utils/html'
14-
import { createSymbolId, parseJsDocLinks, renderMarkdown } from './text'
13+
import { escapeHtml, createSymbolId, parseJsDocLinks, renderMarkdown } from './text'
1514
import type { MergedSymbol, SymbolLookup } from './types'
1615

1716
// =============================================================================

server/utils/docs/text.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import { highlightCodeBlock } from '../shiki'
1111
import type { SymbolLookup } from './types'
12-
import { escapeHtml } from '#shared/utils/html'
1312

1413
/**
1514
* Strip ANSI escape codes from text.
@@ -22,6 +21,20 @@ export function stripAnsi(text: string): string {
2221
return text.replace(ANSI_PATTERN, '')
2322
}
2423

24+
/**
25+
* Escape HTML special characters.
26+
*
27+
* @internal Exported for testing
28+
*/
29+
export function escapeHtml(text: string): string {
30+
return text
31+
.replace(/&/g, '&amp;')
32+
.replace(/</g, '&lt;')
33+
.replace(/>/g, '&gt;')
34+
.replace(/"/g, '&quot;')
35+
.replace(/'/g, '&#39;')
36+
}
37+
2538
/**
2639
* Clean up symbol names by stripping esm.sh prefixes.
2740
*

shared/utils/html.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

test/unit/server/utils/docs/text.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { describe, expect, it } from 'vitest'
22
import * as fc from 'fast-check'
3-
import { escapeHtml } from '#shared/utils/html'
4-
import { parseJsDocLinks, renderMarkdown, stripAnsi } from '../../../../../server/utils/docs/text'
3+
import {
4+
escapeHtml,
5+
parseJsDocLinks,
6+
renderMarkdown,
7+
stripAnsi,
8+
} from '../../../../../server/utils/docs/text'
59
import type { SymbolLookup } from '../../../../../server/utils/docs/types'
610

711
describe('stripAnsi', () => {

0 commit comments

Comments
 (0)