feat: add agent workflow messaging and skill
This commit is contained in:
1 parent
b451771169
commit
ee82019d0b
30 files changed
+1462
-87
No files matched your search
+6
-6
@@ -8,7 +8,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Xdrop is an open source file transfer app that encrypts files in your browser and keeps plaintext file names, contents, and keys off the server."
|
||||
content="Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server."
|
||||
/>
|
||||
<meta
|
||||
name="robots"
|
||||
@@ -20,26 +20,26 @@
|
||||
<meta property="og:type" content="website" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Open Source Encrypted File Transfer in the Browser | Xdrop"
|
||||
content="Open Source Encrypted File Transfer for Browsers and Agents | Xdrop"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Xdrop is an open source file transfer app that encrypts files in your browser and keeps plaintext file names, contents, and keys off the server."
|
||||
content="Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server."
|
||||
/>
|
||||
<meta property="og:image" content="/brand-lockup-horizontal.png" />
|
||||
<meta property="og:image:alt" content="Xdrop horizontal brand lockup" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Open Source Encrypted File Transfer in the Browser | Xdrop"
|
||||
content="Open Source Encrypted File Transfer for Browsers and Agents | Xdrop"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Xdrop is an open source file transfer app that encrypts files in your browser and keeps plaintext file names, contents, and keys off the server."
|
||||
content="Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server."
|
||||
/>
|
||||
<meta name="twitter:image" content="/brand-lockup-horizontal.png" />
|
||||
<meta name="theme-color" content="#12140f" />
|
||||
<title>Open Source Encrypted File Transfer in the Browser | Xdrop</title>
|
||||
<title>Open Source Encrypted File Transfer for Browsers and Agents | Xdrop</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Xdrop",
|
||||
"short_name": "Xdrop",
|
||||
"description": "Xdrop is an open source file transfer app that encrypts files in your browser and keeps plaintext file names, contents, and keys off the server.",
|
||||
"description": "Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.",
|
||||
"theme_color": "#12140f",
|
||||
"background_color": "#f7f2e8",
|
||||
"display": "standalone",
|
||||
|
||||
@@ -18,9 +18,21 @@ const DEFAULT_ROBOTS =
|
||||
const PRIVATE_ROBOTS = 'noindex, nofollow, noarchive, nosnippet'
|
||||
const STRUCTURED_DATA_ID = 'xdrop-structured-data-static'
|
||||
|
||||
const HOME_TITLE = 'Open Source Encrypted File Transfer in the Browser | Xdrop'
|
||||
const HOME_TITLE = 'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop'
|
||||
const HOME_DESCRIPTION =
|
||||
'Xdrop is an open source file transfer app that encrypts files in your browser and keeps plaintext file names, contents, and keys off the server.'
|
||||
'Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.'
|
||||
const HISTORY_PAGE_TITLE = 'Manage Transfers on This Device | Xdrop'
|
||||
const HISTORY_PAGE_DESCRIPTION =
|
||||
'Manage encrypted transfers stored in this browser on this device. There is no account or cross-device history.'
|
||||
const SHARE_PAGE_TITLE = 'Share the Full Link | Xdrop'
|
||||
const SHARE_PAGE_DESCRIPTION =
|
||||
'Review upload status and copy the full share link for a transfer staged in this browser on this device.'
|
||||
const RECEIVE_PAGE_TITLE = 'Download and Decrypt in the Browser | Xdrop'
|
||||
const RECEIVE_PAGE_DESCRIPTION =
|
||||
'Download files from this transfer and decrypt them in the browser. The decryption key stays in the share link fragment.'
|
||||
const NOT_FOUND_PAGE_TITLE = 'Page Not Found | Xdrop'
|
||||
const NOT_FOUND_PAGE_DESCRIPTION =
|
||||
'The address does not map to a page in Xdrop. If this came from a shared transfer, ask for the full URL, including the #k=... decryption fragment.'
|
||||
|
||||
const routeShells = [
|
||||
{
|
||||
@@ -34,35 +46,31 @@ const routeShells = [
|
||||
{
|
||||
outputPath: 'transfers/index.html',
|
||||
pagePath: '/transfers',
|
||||
title: 'Manage Transfers on This Device | Xdrop',
|
||||
description:
|
||||
'Manage encrypted transfers stored in this browser. There is no account or cross-device history.',
|
||||
title: HISTORY_PAGE_TITLE,
|
||||
description: HISTORY_PAGE_DESCRIPTION,
|
||||
robots: PRIVATE_ROBOTS,
|
||||
exposeUrl: false,
|
||||
},
|
||||
{
|
||||
outputPath: 'share/index.html',
|
||||
pagePath: '/share/',
|
||||
title: 'Share the Full Link | Xdrop',
|
||||
description:
|
||||
'Review upload status and copy the full share link for a browser-encrypted transfer.',
|
||||
title: SHARE_PAGE_TITLE,
|
||||
description: SHARE_PAGE_DESCRIPTION,
|
||||
robots: PRIVATE_ROBOTS,
|
||||
exposeUrl: false,
|
||||
},
|
||||
{
|
||||
outputPath: 't/index.html',
|
||||
pagePath: '/t/',
|
||||
title: 'Download and Decrypt in the Browser | Xdrop',
|
||||
description:
|
||||
'Download files from this transfer and decrypt them in the browser. The decryption key stays in the share link fragment.',
|
||||
title: RECEIVE_PAGE_TITLE,
|
||||
description: RECEIVE_PAGE_DESCRIPTION,
|
||||
robots: PRIVATE_ROBOTS,
|
||||
exposeUrl: false,
|
||||
},
|
||||
{
|
||||
outputPath: 'not-found/index.html',
|
||||
title: 'Page Not Found | Xdrop',
|
||||
description:
|
||||
'The address does not map to a page in Xdrop. If this came from a shared transfer, ask for the full URL, including the #k=... decryption fragment.',
|
||||
title: NOT_FOUND_PAGE_TITLE,
|
||||
description: NOT_FOUND_PAGE_DESCRIPTION,
|
||||
robots: PRIVATE_ROBOTS,
|
||||
exposeUrl: false,
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ export function Shell() {
|
||||
/>
|
||||
<span className="brand-copy">
|
||||
<span className="brand-mark">Xdrop</span>
|
||||
<span className="brand-note">Browser-encrypted transfer</span>
|
||||
<span className="brand-note">Encrypted file transfer</span>
|
||||
</span>
|
||||
</NavLink>
|
||||
<nav className="nav">
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { HistoryBoard } from '@/features/history/HistoryBoard'
|
||||
import { PRIVATE_ROBOTS } from '@/lib/seo/site'
|
||||
import { HISTORY_PAGE_DESCRIPTION, HISTORY_PAGE_TITLE, PRIVATE_ROBOTS } from '@/lib/seo/site'
|
||||
import { usePageMetadata } from '@/lib/seo/usePageMetadata'
|
||||
|
||||
/** HistoryPage hides local-only transfer history from search engines. */
|
||||
export function HistoryPage() {
|
||||
usePageMetadata({
|
||||
title: 'Manage Transfers on This Device | Xdrop',
|
||||
description:
|
||||
'Manage encrypted transfers stored in this browser. There is no account or cross-device history.',
|
||||
title: HISTORY_PAGE_TITLE,
|
||||
description: HISTORY_PAGE_DESCRIPTION,
|
||||
robots: PRIVATE_ROBOTS,
|
||||
exposeUrl: false,
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('HomePage', () => {
|
||||
|
||||
expect(usePageMetadataMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
title: 'Open Source Encrypted File Transfer in the Browser | Xdrop',
|
||||
title: 'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop',
|
||||
structuredData: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
'@type': 'Organization',
|
||||
|
||||
@@ -14,6 +14,8 @@ describe('NotFoundPage', () => {
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'This page was not found.' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('heading', { name: 'Try these checks' })).toBeInTheDocument()
|
||||
expect(screen.getByText('Try the browser that created the transfer')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('Try the device and browser that created the transfer'),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Card } from '@/components/ui/Card'
|
||||
import { PRIVATE_ROBOTS } from '@/lib/seo/site'
|
||||
import { NOT_FOUND_PAGE_DESCRIPTION, NOT_FOUND_PAGE_TITLE, PRIVATE_ROBOTS } from '@/lib/seo/site'
|
||||
import { usePageMetadata } from '@/lib/seo/usePageMetadata'
|
||||
|
||||
/** NotFoundPage explains the most common ways secure share links break. */
|
||||
export function NotFoundPage() {
|
||||
usePageMetadata({
|
||||
title: 'Page Not Found | Xdrop',
|
||||
description:
|
||||
'The address does not map to a page in Xdrop. If this came from a shared transfer, ask for the full URL, including the #k=... decryption fragment.',
|
||||
title: NOT_FOUND_PAGE_TITLE,
|
||||
description: NOT_FOUND_PAGE_DESCRIPTION,
|
||||
robots: PRIVATE_ROBOTS,
|
||||
exposeUrl: false,
|
||||
})
|
||||
@@ -37,10 +36,10 @@ export function NotFoundPage() {
|
||||
<ul className="file-list">
|
||||
<li className="file-row not-found-tip">
|
||||
<div className="file-stack">
|
||||
<strong>Try the browser that created the transfer</strong>
|
||||
<strong>Try the device and browser that created the transfer</strong>
|
||||
<p className="muted">
|
||||
Sender history and local transfer controls only exist on the device that created the
|
||||
transfer.
|
||||
Sender history and local transfer controls only exist in the browser that created
|
||||
the transfer on that device.
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useParams } from 'react-router-dom'
|
||||
|
||||
import { ShareCard } from '@/features/share/ShareCard'
|
||||
import { useTransfers } from '@/features/upload/TransferContext'
|
||||
import { PRIVATE_ROBOTS } from '@/lib/seo/site'
|
||||
import { PRIVATE_ROBOTS, SHARE_PAGE_DESCRIPTION, SHARE_PAGE_TITLE } from '@/lib/seo/site'
|
||||
import { usePageMetadata } from '@/lib/seo/usePageMetadata'
|
||||
|
||||
/** SharePage shows sender-side progress and sharing controls for one local transfer. */
|
||||
@@ -12,9 +12,8 @@ export function SharePage() {
|
||||
const transfer = transfers.find((item) => item.id === transferId)
|
||||
|
||||
usePageMetadata({
|
||||
title: 'Share the Full Link | Xdrop',
|
||||
description:
|
||||
'Review upload status and copy the full share link for a browser-encrypted transfer.',
|
||||
title: SHARE_PAGE_TITLE,
|
||||
description: SHARE_PAGE_DESCRIPTION,
|
||||
robots: PRIVATE_ROBOTS,
|
||||
exposeUrl: false,
|
||||
})
|
||||
|
||||
@@ -198,7 +198,7 @@ describe('HistoryBoard', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Delete' }))
|
||||
expect(
|
||||
screen.getByText(/Confirm delete to remove this transfer from storage and from this device/i),
|
||||
screen.getByText(/Confirm delete to remove this transfer from this device/i),
|
||||
).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Confirm delete' }))
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ export function HistoryBoard() {
|
||||
{pendingDeleteTransferId === transfer.id ? (
|
||||
<p aria-live="polite" className="warning">
|
||||
{canManageTransfer
|
||||
? 'Confirm delete to remove this transfer from storage and from this device.'
|
||||
? 'Confirm delete to remove this transfer from this device.'
|
||||
: 'Confirm forget to remove this local record from this device.'}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { formatBytes } from '@/lib/files/formatBytes'
|
||||
import { safeDownloadName, sanitizePath } from '@/lib/files/paths'
|
||||
import { isAbortError, openSaveWritable, saveBlob, supportsStreamingSave } from '@/lib/files/save'
|
||||
import { formatLocalDateTime } from '@/lib/i18n/formatDateTime'
|
||||
import { PRIVATE_ROBOTS } from '@/lib/seo/site'
|
||||
import { PRIVATE_ROBOTS, RECEIVE_PAGE_DESCRIPTION, RECEIVE_PAGE_TITLE } from '@/lib/seo/site'
|
||||
import { usePageMetadata } from '@/lib/seo/usePageMetadata'
|
||||
|
||||
type Props = {
|
||||
@@ -49,9 +49,8 @@ export function ReceiveTransfer({ transferId }: Props) {
|
||||
activeDownload !== null || downloadProgress > 0 || Boolean(downloadError)
|
||||
|
||||
usePageMetadata({
|
||||
title: 'Download and Decrypt in the Browser | Xdrop',
|
||||
description:
|
||||
'Download files from this transfer and decrypt them in the browser. The decryption key stays in the share link fragment.',
|
||||
title: RECEIVE_PAGE_TITLE,
|
||||
description: RECEIVE_PAGE_DESCRIPTION,
|
||||
robots: PRIVATE_ROBOTS,
|
||||
exposeUrl: false,
|
||||
})
|
||||
|
||||
@@ -109,7 +109,9 @@ describe('ShareCard', () => {
|
||||
)
|
||||
|
||||
expect(screen.getByText('Transfer not on this device')).toBeInTheDocument()
|
||||
expect(screen.getByText('Open it in the browser that created it.')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('Open it in the same browser on the device that created it.'),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('copies the full link and resets the copied state', async () => {
|
||||
@@ -310,7 +312,9 @@ describe('ShareCard', () => {
|
||||
|
||||
expect(screen.getByText('25% uploaded')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('Upload will continue automatically when this browser returns.'),
|
||||
screen.getByText(
|
||||
'Upload will continue automatically when you return here in the same browser on this device.',
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText(/Privacy mode removed local transfer controls/i)).toBeInTheDocument()
|
||||
|
||||
@@ -322,7 +326,7 @@ describe('ShareCard', () => {
|
||||
await act(async () => {
|
||||
await Promise.resolve()
|
||||
})
|
||||
expect(screen.getByText('Upload stopped in this browser.')).toBeInTheDocument()
|
||||
expect(screen.getByText('Upload stopped in this browser on this device.')).toBeInTheDocument()
|
||||
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
|
||||
@@ -115,7 +115,7 @@ export function ShareCard({ transfer }: Props) {
|
||||
<PageStateCard
|
||||
eyebrow="Share"
|
||||
title="Transfer not on this device"
|
||||
body="Open it in the browser that created it."
|
||||
body="Open it in the same browser on the device that created it."
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -260,9 +260,9 @@ function shareStatusCopy(status: LocalTransferRecord['status']) {
|
||||
case 'ready':
|
||||
return undefined
|
||||
case 'paused':
|
||||
return 'Upload will continue automatically when this browser returns.'
|
||||
return 'Upload will continue automatically when you return here in the same browser on this device.'
|
||||
case 'failed':
|
||||
return 'Upload stopped in this browser.'
|
||||
return 'Upload stopped in this browser on this device.'
|
||||
case 'deleted':
|
||||
return 'This transfer was deleted.'
|
||||
default:
|
||||
|
||||
@@ -549,7 +549,7 @@ describe('TransferProvider actions', () => {
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
't1:paused:This page was closed or refreshed. Upload will continue automatically when this browser returns.',
|
||||
't1:paused:This page was closed or refreshed. Upload will continue automatically when you return here in the same browser on this device.',
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -360,7 +360,7 @@ describe('TransferContext helpers', () => {
|
||||
makeTransferRecord('paused', {
|
||||
id: 't2',
|
||||
lastError:
|
||||
'This page was closed or refreshed. Upload will continue automatically when this browser returns.',
|
||||
'This page was closed or refreshed. Upload will continue automatically when you return here in the same browser on this device.',
|
||||
}),
|
||||
makeTransferRecord('ready', { id: 't3' }),
|
||||
])
|
||||
@@ -370,7 +370,7 @@ describe('TransferContext helpers', () => {
|
||||
expect.objectContaining({
|
||||
id: 't1',
|
||||
lastError:
|
||||
'This page was closed or refreshed. Upload will continue automatically when this browser returns.',
|
||||
'This page was closed or refreshed. Upload will continue automatically when you return here in the same browser on this device.',
|
||||
status: 'paused',
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -226,7 +226,7 @@ describe('TransferProvider', () => {
|
||||
't1',
|
||||
createTransferRecord('paused', {
|
||||
lastError:
|
||||
'This page was closed or refreshed. Upload will continue automatically when this browser returns.',
|
||||
'This page was closed or refreshed. Upload will continue automatically when you return here in the same browser on this device.',
|
||||
}),
|
||||
)
|
||||
sourcesStore.set('t1:file-1', createSourceRecord())
|
||||
|
||||
@@ -74,7 +74,7 @@ type UploadSource = PersistedSourceRecord & {
|
||||
|
||||
/** This message marks transfers that should resume automatically after a page return. */
|
||||
const RESUME_AFTER_NAVIGATION_MESSAGE =
|
||||
'This page was closed or refreshed. Upload will continue automatically when this browser returns.'
|
||||
'This page was closed or refreshed. Upload will continue automatically when you return here in the same browser on this device.'
|
||||
|
||||
type TransferContextValue = {
|
||||
transfers: LocalTransferRecord[]
|
||||
|
||||
@@ -160,6 +160,27 @@ describe('UploadStudio', () => {
|
||||
draftState.sources = []
|
||||
})
|
||||
|
||||
it('shows browser-first positioning with agent workflow support copy', async () => {
|
||||
renderStudio()
|
||||
|
||||
expect(await screen.findByText('Encrypted file transfer.')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText(
|
||||
/agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server\./,
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText(/Use Xdrop in the browser for normal sharing, or/i)).toBeInTheDocument()
|
||||
expect(screen.getByRole('link', { name: 'through an agent' })).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/xixu-me/xdrop?tab=readme-ov-file#use-via-agents',
|
||||
)
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Use the web app to drop files or a folder\. Names, paths, and file contents are encrypted in this browser before upload\./,
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('blocks selections that exceed the transfer size limit', async () => {
|
||||
const { container } = renderStudio()
|
||||
|
||||
|
||||
@@ -246,6 +246,19 @@ export function UploadStudio() {
|
||||
<p className="eyebrow">End-to-end encryption</p>
|
||||
<h1 className="page-hero-title">Encrypted file transfer.</h1>
|
||||
<p className="lead">{PROJECT_ONE_LINER}</p>
|
||||
<p className="muted">
|
||||
Use Xdrop in the browser for normal sharing, or{' '}
|
||||
<a
|
||||
className="inline-link"
|
||||
href="https://github.com/xixu-me/xdrop?tab=readme-ov-file#use-via-agents"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
through an agent
|
||||
</a>{' '}
|
||||
when you need to move files out of a cloud server, remote container, or automated
|
||||
terminal workflow.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -265,8 +278,8 @@ export function UploadStudio() {
|
||||
>
|
||||
<div className="dropzone-shell">
|
||||
<p className="muted dropzone-copy">
|
||||
Drop files or a folder. Names, paths, and file contents are encrypted in this browser
|
||||
before upload.
|
||||
Use the web app to drop files or a folder. Names, paths, and file contents are
|
||||
encrypted in this browser before upload.
|
||||
</p>
|
||||
{uploadError ? <p className="warning dropzone-copy">{uploadError}</p> : null}
|
||||
<div className="button-row">
|
||||
@@ -401,8 +414,9 @@ export function UploadStudio() {
|
||||
</div>
|
||||
</label>
|
||||
<p className="warning">
|
||||
Uploads continue automatically when this browser returns after a refresh or reopen.
|
||||
Privacy mode keeps less sensitive state on this device, with fewer local controls.
|
||||
Uploads continue automatically when you return here in the same browser on this device
|
||||
after a refresh or reopen. Privacy mode keeps less sensitive state on this device, with
|
||||
fewer local controls.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -858,7 +858,7 @@ h3 {
|
||||
0 18px 30px rgba(2, 5, 2, 0.18);
|
||||
}
|
||||
|
||||
.dropzone > .warning {
|
||||
.dropzone-shell > .warning {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,26 @@
|
||||
|
||||
export const SITE_NAME = 'Xdrop'
|
||||
export const PROJECT_ONE_LINER =
|
||||
'Xdrop is an open source file transfer app that encrypts files in your browser and keeps plaintext file names, contents, and keys off the server.'
|
||||
export const DEFAULT_SEO_TITLE = 'Open Source Encrypted File Transfer in the Browser | Xdrop'
|
||||
'Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.'
|
||||
export const SHORT_POSITIONING_SUMMARY =
|
||||
'Browser-first encrypted file transfer, with agent-ready terminal workflows.'
|
||||
export const TERMINAL_SUPPORT_BLURB =
|
||||
'Use Xdrop in the browser for normal sharing, or through an agent when you need to move files out of a cloud server, remote container, or automated terminal workflow.'
|
||||
export const DEFAULT_SEO_TITLE =
|
||||
'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop'
|
||||
export const DEFAULT_SEO_DESCRIPTION = PROJECT_ONE_LINER
|
||||
export const HISTORY_PAGE_TITLE = 'Manage Transfers on This Device | Xdrop'
|
||||
export const HISTORY_PAGE_DESCRIPTION =
|
||||
'Manage encrypted transfers stored in this browser on this device. There is no account or cross-device history.'
|
||||
export const SHARE_PAGE_TITLE = 'Share the Full Link | Xdrop'
|
||||
export const SHARE_PAGE_DESCRIPTION =
|
||||
'Review upload status and copy the full share link for a transfer staged in this browser on this device.'
|
||||
export const RECEIVE_PAGE_TITLE = 'Download and Decrypt in the Browser | Xdrop'
|
||||
export const RECEIVE_PAGE_DESCRIPTION =
|
||||
'Download files from this transfer and decrypt them in the browser. The decryption key stays in the share link fragment.'
|
||||
export const NOT_FOUND_PAGE_TITLE = 'Page Not Found | Xdrop'
|
||||
export const NOT_FOUND_PAGE_DESCRIPTION =
|
||||
'The address does not map to a page in Xdrop. If this came from a shared transfer, ask for the full URL, including the #k=... decryption fragment.'
|
||||
export const DEFAULT_OG_IMAGE_PATH = '/brand-lockup-horizontal.png'
|
||||
export const DEFAULT_OG_IMAGE_ALT = 'Xdrop horizontal brand lockup'
|
||||
export const DEFAULT_LOGO_PATH = '/brand-symbol-512.png'
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('usePageMetadata', () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/']}>
|
||||
<MetadataProbe
|
||||
title="Open Source Encrypted File Transfer in the Browser | Xdrop"
|
||||
title="Open Source Encrypted File Transfer for Browsers and Agents | Xdrop"
|
||||
description="SEO test description"
|
||||
structuredData={{
|
||||
'@context': 'https://schema.org',
|
||||
@@ -38,11 +38,13 @@ describe('usePageMetadata', () => {
|
||||
</MemoryRouter>,
|
||||
)
|
||||
|
||||
expect(document.title).toBe('Open Source Encrypted File Transfer in the Browser | Xdrop')
|
||||
expect(document.title).toBe(
|
||||
'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop',
|
||||
)
|
||||
expect(readMetaByName('description')).toBe('SEO test description')
|
||||
expect(readMetaByName('robots')).toContain('index, follow')
|
||||
expect(readMetaByProperty('og:title')).toBe(
|
||||
'Open Source Encrypted File Transfer in the Browser | Xdrop',
|
||||
'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop',
|
||||
)
|
||||
expect(readMetaByProperty('og:url')).toBe(new URL('/', window.location.origin).toString())
|
||||
expect(readCanonical()).toBe(new URL('/', window.location.origin).toString())
|
||||
@@ -89,7 +91,7 @@ describe('usePageMetadata', () => {
|
||||
expect(document.head.querySelectorAll('meta[name="description"]')).toHaveLength(1)
|
||||
expect(document.head.querySelectorAll('link[rel="canonical"]')).toHaveLength(1)
|
||||
expect(readMetaByName('description')).toContain(
|
||||
'Xdrop is an open source file transfer app that encrypts files in your browser',
|
||||
'Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows',
|
||||
)
|
||||
expect(readMetaByName('robots')).toContain('index, follow')
|
||||
expect(readMetaByProperty('og:type')).toBe('website')
|
||||
|
||||
Reference in new issue
Block a user