11<script setup lang="ts">
22import { NO_DEPENDENCY_ID } from ' ~/composables/usePackageComparison'
33import { useRouteQuery } from ' @vueuse/router'
4- import { escapeHtml } from ' #shared/utils/html'
54import { NPMX_SITE } from ' #shared/utils/constants'
65
76definePageMeta ({
87 name: ' compare' ,
98})
109
10+ const { locale } = useI18n ()
1111const router = useRouter ()
1212const canGoBack = useCanGoBack ()
1313const { 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)
1716const 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
137137useSeoMeta ({
@@ -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"
0 commit comments