diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue index 388e286b9..088aa648d 100644 --- a/app/pages/package/[[org]]/[name].vue +++ b/app/pages/package/[[org]]/[name].vue @@ -62,9 +62,15 @@ const { data: readmeData } = useLazyFetch( const version = requestedVersion.value return version ? `${base}/v/${version}` : base }, - { default: () => ({ html: '', playgroundLinks: [], toc: [] }) }, + { default: () => ({ html: '', md: '', playgroundLinks: [], toc: [] }) }, ) +//copy README file as Markdown +const { copied: copiedReadme, copy: copyReadme } = useClipboard({ + source: () => readmeData.value?.md ?? '', + copiedDuring: 2000, +}) + // Track active TOC item based on scroll position const tocItems = computed(() => readmeData.value?.toc ?? []) const { activeId: activeTocId, scrollToHeading } = useActiveTocItem(tocItems) @@ -1136,12 +1142,39 @@ onKeyStroke( - +
+ + + + + +
diff --git a/i18n/locales/de-DE.json b/i18n/locales/de-DE.json index 1a66ed171..683be5cb6 100644 --- a/i18n/locales/de-DE.json +++ b/i18n/locales/de-DE.json @@ -221,7 +221,8 @@ "important": "Wichtig", "warning": "Warnung", "caution": "Vorsicht" - } + }, + "copy_as_markdown": "README als Markdown kopieren" }, "provenance_section": { "title": "Herkunft", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 4c17ad717..3673dd9c6 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -221,7 +221,8 @@ "important": "Important", "warning": "Warning", "caution": "Caution" - } + }, + "copy_as_markdown": "Copy README as Markdown" }, "provenance_section": { "title": "Provenance", diff --git a/i18n/locales/es.json b/i18n/locales/es.json index fe40402f0..966190f23 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -208,7 +208,8 @@ "title": "Léeme", "no_readme": "No hay README disponible.", "view_on_github": "Ver en GitHub", - "toc_title": "Índice" + "toc_title": "Índice", + "copy_as_markdown": "Copiar README como Markdown" }, "keywords_title": "Palabras clave", "compatibility": "Compatibilidad", diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 246460c0e..f7b379a27 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -206,7 +206,8 @@ "title": "Readme", "no_readme": "Aucun README disponible.", "view_on_github": "Voir sur GitHub", - "toc_title": "Sommaire" + "toc_title": "Sommaire", + "copy_as_markdown": "Copier le README en markdown" }, "keywords_title": "Mots-clés", "compatibility": "Compatibilité", diff --git a/i18n/locales/it-IT.json b/i18n/locales/it-IT.json index 3740ba688..fa5688697 100644 --- a/i18n/locales/it-IT.json +++ b/i18n/locales/it-IT.json @@ -221,7 +221,8 @@ "important": "Importante", "warning": "Avvertenza", "caution": "Cautela" - } + }, + "copy_as_markdown": "Copia README come Markdown" }, "provenance_section": { "title": "Provenienza", diff --git a/lunaria/files/de-DE.json b/lunaria/files/de-DE.json index 1a66ed171..683be5cb6 100644 --- a/lunaria/files/de-DE.json +++ b/lunaria/files/de-DE.json @@ -221,7 +221,8 @@ "important": "Wichtig", "warning": "Warnung", "caution": "Vorsicht" - } + }, + "copy_as_markdown": "README als Markdown kopieren" }, "provenance_section": { "title": "Herkunft", diff --git a/lunaria/files/en-GB.json b/lunaria/files/en-GB.json index f74bb27e8..0fdda09dc 100644 --- a/lunaria/files/en-GB.json +++ b/lunaria/files/en-GB.json @@ -221,7 +221,8 @@ "important": "Important", "warning": "Warning", "caution": "Caution" - } + }, + "copy_as_markdown": "Copy README as Markdown" }, "provenance_section": { "title": "Provenance", diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json index 4c17ad717..3673dd9c6 100644 --- a/lunaria/files/en-US.json +++ b/lunaria/files/en-US.json @@ -221,7 +221,8 @@ "important": "Important", "warning": "Warning", "caution": "Caution" - } + }, + "copy_as_markdown": "Copy README as Markdown" }, "provenance_section": { "title": "Provenance", diff --git a/lunaria/files/es-419.json b/lunaria/files/es-419.json index 2e650460e..52128330b 100644 --- a/lunaria/files/es-419.json +++ b/lunaria/files/es-419.json @@ -208,7 +208,8 @@ "title": "Léame", "no_readme": "No hay README disponible.", "view_on_github": "Ver en GitHub", - "toc_title": "Índice" + "toc_title": "Índice", + "copy_as_markdown": "Copiar README como Markdown" }, "keywords_title": "Palabras clave", "compatibility": "Compatibilidad", diff --git a/lunaria/files/es-ES.json b/lunaria/files/es-ES.json index fe40402f0..966190f23 100644 --- a/lunaria/files/es-ES.json +++ b/lunaria/files/es-ES.json @@ -208,7 +208,8 @@ "title": "Léeme", "no_readme": "No hay README disponible.", "view_on_github": "Ver en GitHub", - "toc_title": "Índice" + "toc_title": "Índice", + "copy_as_markdown": "Copiar README como Markdown" }, "keywords_title": "Palabras clave", "compatibility": "Compatibilidad", diff --git a/lunaria/files/fr-FR.json b/lunaria/files/fr-FR.json index 246460c0e..f7b379a27 100644 --- a/lunaria/files/fr-FR.json +++ b/lunaria/files/fr-FR.json @@ -206,7 +206,8 @@ "title": "Readme", "no_readme": "Aucun README disponible.", "view_on_github": "Voir sur GitHub", - "toc_title": "Sommaire" + "toc_title": "Sommaire", + "copy_as_markdown": "Copier le README en markdown" }, "keywords_title": "Mots-clés", "compatibility": "Compatibilité", diff --git a/lunaria/files/it-IT.json b/lunaria/files/it-IT.json index 3740ba688..fa5688697 100644 --- a/lunaria/files/it-IT.json +++ b/lunaria/files/it-IT.json @@ -221,7 +221,8 @@ "important": "Importante", "warning": "Avvertenza", "caution": "Cautela" - } + }, + "copy_as_markdown": "Copia README come Markdown" }, "provenance_section": { "title": "Provenienza", diff --git a/server/utils/readme.ts b/server/utils/readme.ts index a1fcfa36d..dc2a54043 100644 --- a/server/utils/readme.ts +++ b/server/utils/readme.ts @@ -277,7 +277,7 @@ export async function renderReadmeHtml( packageName: string, repoInfo?: RepositoryInfo, ): Promise { - if (!content) return { html: '', playgroundLinks: [], toc: [] } + if (!content) return { html: '', md: '', playgroundLinks: [], toc: [] } const shiki = await getShikiHighlighter() const renderer = new marked.Renderer() @@ -455,6 +455,7 @@ ${html} return { html: convertToEmoji(sanitized), + md: content, playgroundLinks: collectedLinks, toc, } diff --git a/shared/types/readme.ts b/shared/types/readme.ts index 350450c4d..3e8c9607c 100644 --- a/shared/types/readme.ts +++ b/shared/types/readme.ts @@ -30,6 +30,8 @@ export interface TocItem { export interface ReadmeResponse { /** Rendered HTML content */ html: string + /** Original markdown content */ + md: string /** Extracted playground/demo links */ playgroundLinks: PlaygroundLink[] /** Table of contents extracted from headings */ diff --git a/test/unit/server/utils/readme.spec.ts b/test/unit/server/utils/readme.spec.ts index 808e18b6e..7790d4d04 100644 --- a/test/unit/server/utils/readme.spec.ts +++ b/test/unit/server/utils/readme.spec.ts @@ -307,3 +307,24 @@ describe('Markdown File URL Resolution', () => { }) }) }) + +describe('Markdown Content Extraction', () => { + describe('Markdown', () => { + it('returns original markdown content unchanged', async () => { + const markdown = `# Title\n\nSome **bold** text and a [link](https://example.com).` + const result = await renderReadmeHtml(markdown, 'test-pkg') + + expect(result.md).toBe(markdown) + }) + }) + describe('HTML', () => { + it('returns sanitized html', async () => { + const markdown = `# Title\n\nSome **bold** text and a [link](https://example.com).` + const result = await renderReadmeHtml(markdown, 'test-pkg') + + expect(result.html).toBe(`

Title

+

Some bold text and a link.

+`) + }) + }) +})