diff --git a/e2e/tests/help-menu.spec.ts b/e2e/tests/help-menu.spec.ts new file mode 100644 index 00000000..9f8e967d --- /dev/null +++ b/e2e/tests/help-menu.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test'; +import { EditorPage } from '../helpers/editor-page'; + +test.describe('Help menu', () => { + let editor: EditorPage; + + test.beforeEach(async ({ page }) => { + editor = new EditorPage(page); + await editor.goto(); + await editor.waitForReady(); + }); + + test('Help > Report issue opens GitHub issues URL with pre-filled body', async ({ page }) => { + await page.evaluate(() => { + (window as unknown as { __lastOpenedUrl?: string }).__lastOpenedUrl = undefined; + window.open = ((url?: string | URL) => { + (window as unknown as { __lastOpenedUrl?: string }).__lastOpenedUrl = String(url ?? ''); + return null; + }) as typeof window.open; + }); + + await page.getByRole('button', { name: 'Help' }).click(); + await page.getByRole('button', { name: 'Report issue' }).click(); + + const openedUrl = await page.evaluate( + () => (window as unknown as { __lastOpenedUrl?: string }).__lastOpenedUrl + ); + expect(openedUrl).toBeDefined(); + + const url = new URL(openedUrl!); + expect(url.origin + url.pathname).toBe('https://github.com/eigenpal/docx-editor/issues/new'); + + expect(url.searchParams.get('title')).toBe('[Bug] '); + const body = url.searchParams.get('body') ?? ''; + expect(body).toContain('Steps to reproduce'); + expect(body).toContain('Attach the DOCX'); + expect(body).toContain('User agent:'); + expect(body).toContain('Viewport:'); + }); +}); diff --git a/packages/react/i18n/de.json b/packages/react/i18n/de.json index d56c60a0..adeaa427 100644 --- a/packages/react/i18n/de.json +++ b/packages/react/i18n/de.json @@ -34,7 +34,9 @@ "table": "Tabelle", "pageBreak": "Seitenumbruch", "tableOfContents": "Inhaltsverzeichnis", - "symbol": "Symbol" + "symbol": "Symbol", + "help": "Hilfe", + "reportIssue": "Problem melden" }, "formattingBar": { "groups": { diff --git a/packages/react/i18n/en.json b/packages/react/i18n/en.json index bca229fa..6c7a9f7c 100644 --- a/packages/react/i18n/en.json +++ b/packages/react/i18n/en.json @@ -34,7 +34,9 @@ "table": "Table", "pageBreak": "Page break", "tableOfContents": "Table of contents", - "symbol": "Symbol" + "symbol": "Symbol", + "help": "Help", + "reportIssue": "Report issue" }, "formattingBar": { "groups": { diff --git a/packages/react/i18n/pl.json b/packages/react/i18n/pl.json index e6012008..7fb33c82 100644 --- a/packages/react/i18n/pl.json +++ b/packages/react/i18n/pl.json @@ -34,7 +34,9 @@ "table": "Tabela", "pageBreak": "Podział strony", "tableOfContents": "Spis treści", - "symbol": "Symbol" + "symbol": "Symbol", + "help": "Pomoc", + "reportIssue": "Zgłoś problem" }, "formattingBar": { "groups": { diff --git a/packages/react/src/components/TitleBar.tsx b/packages/react/src/components/TitleBar.tsx index 1e2f4b3d..49007f12 100644 --- a/packages/react/src/components/TitleBar.tsx +++ b/packages/react/src/components/TitleBar.tsx @@ -16,6 +16,7 @@ import { TableGridInline } from './ui/TableGridInline'; import { useEditorToolbar } from './EditorToolbarContext'; import type { FormattingAction } from './Toolbar'; import { useTranslation } from '../i18n'; +import { openReportIssue } from './reportIssue'; // ============================================================================ // Default Doc Icon (shown when no Logo is provided) @@ -234,6 +235,18 @@ export function MenuBar() { }, ]} /> + + {/* Help Menu */} + openReportIssue(), + } as MenuEntry, + ]} + /> ); } diff --git a/packages/react/src/components/reportIssue.ts b/packages/react/src/components/reportIssue.ts new file mode 100644 index 00000000..0a492913 --- /dev/null +++ b/packages/react/src/components/reportIssue.ts @@ -0,0 +1,54 @@ +/** + * Builds a pre-filled GitHub "new issue" URL for the Help > Report issue action. + * + * GitHub does not allow file attachments via URL params, so the body asks the + * user to drag-drop their DOCX onto the issue after it opens. + */ + +const ISSUE_URL = 'https://github.com/eigenpal/docx-editor/issues/new'; + +export interface ReportIssueEnv { + userAgent?: string; + viewport?: { width: number; height: number }; + pageUrl?: string; +} + +export function buildReportIssueUrl(env: ReportIssueEnv = {}): string { + const ua = env.userAgent ?? (typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown'); + const vp = + env.viewport ?? + (typeof window !== 'undefined' + ? { width: window.innerWidth, height: window.innerHeight } + : { width: 0, height: 0 }); + const pageUrl = env.pageUrl ?? (typeof window !== 'undefined' ? window.location.href : ''); + + const body = [ + '### What happened', + '', + '### Steps to reproduce', + '1. ', + '2. ', + '3. ', + '', + '### Expected', + '', + '### Actual', + '', + '### Attach the DOCX', + 'Please drag the `.docx` file that reproduces this onto the issue (below this text box). Bugs are much faster to fix with a real file.', + '', + '### Environment', + `- URL: ${pageUrl}`, + `- Viewport: ${vp.width} x ${vp.height}`, + `- User agent: ${ua}`, + '', + ].join('\n'); + + const params = new URLSearchParams({ title: '[Bug] ', body }); + return `${ISSUE_URL}?${params.toString()}`; +} + +export function openReportIssue(env?: ReportIssueEnv): void { + if (typeof window === 'undefined') return; + window.open(buildReportIssueUrl(env), '_blank', 'noopener,noreferrer'); +}