diff --git a/apps/mana/apps/web/src/lib/modules/articles/ListView.svelte b/apps/mana/apps/web/src/lib/modules/articles/ListView.svelte index effe32cee..bd5cb4cc9 100644 --- a/apps/mana/apps/web/src/lib/modules/articles/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/articles/ListView.svelte @@ -79,9 +79,20 @@

Artikel

Später lesen — gespeicherte Web-Artikel, offline verfügbar.

- +
+ + +
@@ -175,6 +186,26 @@ margin: 0 0 0.25rem 0; font-size: 1.75rem; } + .header-actions { + display: flex; + gap: 0.4rem; + align-items: center; + flex-shrink: 0; + } + .icon-btn { + padding: 0.5rem 0.65rem; + border-radius: 0.55rem; + border: 1px solid var(--color-border, rgba(0, 0, 0, 0.15)); + background: transparent; + color: inherit; + font: inherit; + cursor: pointer; + font-size: 1rem; + line-height: 1; + } + .icon-btn:hover { + border-color: var(--color-border-strong, rgba(0, 0, 0, 0.3)); + } .add-btn { padding: 0.5rem 1rem; border-radius: 0.55rem; diff --git a/apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte b/apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte index 2142d94db..62fe91ee7 100644 --- a/apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte +++ b/apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte @@ -2,7 +2,9 @@ AddUrlForm — paste URL → preview → save. Flow: - 1. User pastes (or types) a URL + 1. User pastes (or types) a URL, OR the page is opened with a URL + pre-filled via query string (?url=… / ?text=… / ?title=…). The + Web Share Target + bookmarklet both land here that way. 2. On "Vorschau abrufen": check scope-local dedupe first; if found, offer "öffnen" instead of re-extracting (saves one round-trip). Otherwise call /api/v1/articles/extract and render the preview. @@ -10,9 +12,15 @@ articlesStore.saveFromExtracted — no second server call. 4. Navigate into the new article so the user lands directly in the reader view. + + Pre-filled URLs auto-trigger the preview on mount so the three-click + "share from browser → saved" flow really is three clicks: share → + pick Mana → hit "In Leseliste speichern". --> + + + Artikel-Einstellungen — Mana + + +
+
+

Artikel-Einstellungen

+

Schnellwege, um Artikel aus dem Browser in die Leseliste zu bekommen.

+
+ +
+

Bookmarklet

+

+ Zieh den Button unten in deine Lesezeichen-Leiste. Ein Klick auf einer beliebigen Webseite + öffnet + /articles/add mit der aktuellen URL vorausgefüllt — du bestätigst nur noch die Vorschau. +

+
+ + + + + + {#if bookmarklet} + e.preventDefault()}> + + In Mana speichern + + {:else} + Bookmarklet wird geladen… + {/if} + +
+
+ Quellcode anzeigen +
{bookmarklet}
+
+

+ Funktioniert in jedem Desktop-Browser. In Safari: Lesezeichen anlegen mit einer beliebigen + URL, dann nachträglich die URL durch das Snippet ersetzen. +

+
+ +
+

Share-Target (Android / Chromium)

+

+ Wenn du Mana als App installierst (Browser-Menü „Zum Startbildschirm hinzufügen"), taucht + „Mana" in deinem OS-Share-Sheet auf. Teilen aus dem Browser oder einer anderen App → Mana + auswählen → Artikel wird direkt in der Leseliste vorgeschlagen. +

+

+ iOS-Safari unterstützt die Web-Share-Target-API derzeit nicht — nutze dort das Bookmarklet. +

+
+
+ + diff --git a/apps/mana/apps/web/vite.config.ts b/apps/mana/apps/web/vite.config.ts index 9914b208c..2b2768eb4 100644 --- a/apps/mana/apps/web/vite.config.ts +++ b/apps/mana/apps/web/vite.config.ts @@ -49,6 +49,17 @@ export default defineConfig({ }, { name: 'Chat', short_name: 'Chat', url: '/chat', description: 'Chat öffnen' }, ], + // Web Share Target — installed PWA shows up in the OS share + // sheet as "Mana" and lands on /articles/add with the URL + // pre-filled (AddUrlForm reads ?url + ?text + ?title). The + // `text` param is listed alongside `url` because some + // senders (Chrome Android, WhatsApp) stuff the URL into the + // text field. + shareTarget: { + action: '/articles/add', + method: 'GET', + params: { title: 'title', text: 'text', url: 'url' }, + }, }) ), ], diff --git a/docs/plans/articles-module.md b/docs/plans/articles-module.md index bdc1e4ac0..20a175636 100644 --- a/docs/plans/articles-module.md +++ b/docs/plans/articles-module.md @@ -12,11 +12,15 @@ **M5 Migration von news:type='saved': DONE** (commit `04293ed5e`) — Boot-gated Migration in `modules/articles/migrations/from-news.ts` (localStorage-Sentinel `mana:articles:from-news-migration:v1`), decrypt→re-encrypt zwischen den beiden Field-Allowlists, Status-Mapping `isArchived→archived` / `isRead→finished` / sonst `unread`, Source-Rows werden soft-deletet. News-Code deprecated: `saveFromUrl` + `extractFromUrl` entfernt, `save_news_article` AI-Tool behält seinen Namen (wegen Mission-History) und leitet intern aufs `articles`-Modul um. `/news/add` + `/news/saved` sind Redirects. `news-research` „Speichern"-Buttons routen auf `/articles/[id]`. -**M6 AI-Tools: DONE** (commit pending) — 5 neue Einträge im `AI_TOOL_CATALOG` (`shared-ai/src/tools/schemas.ts`): `list_articles` (auto), `save_article` / `archive_article` / `tag_article` / `add_article_highlight` (alle propose). `modules/articles/tools.ts` enthält die `execute`-Funktionen, registriert in `data/tools/init.ts`. `tag_article` dedupliziert case-insensitive über den globalen Pool und legt Tags via `tagMutations.createTag` an falls nötig. `add_article_highlight` snappt auf die erste wörtliche Fundstelle in `article.content` und lehnt den Call ab wenn der Text nicht exakt vorkommt (kein Orphan-Highlight). Policy/Executor/Server-Planner leiten sich automatisch aus dem Katalog ab. +**M6 AI-Tools: DONE** (commit `5924f4fac`) — 5 neue Einträge im `AI_TOOL_CATALOG` (`shared-ai/src/tools/schemas.ts`): `list_articles` (auto), `save_article` / `archive_article` / `tag_article` / `add_article_highlight` (alle propose). `modules/articles/tools.ts` enthält die `execute`-Funktionen, registriert in `data/tools/init.ts`. `tag_article` dedupliziert case-insensitive über den globalen Pool und legt Tags via `tagMutations.createTag` an falls nötig. `add_article_highlight` snappt auf die erste wörtliche Fundstelle in `article.content` und lehnt den Call ab wenn der Text nicht exakt vorkommt (kein Orphan-Highlight). Policy/Executor/Server-Planner leiten sich automatisch aus dem Katalog ab. **Hinweis AiProposalInbox:** Der apps/mana/CLAUDE.md-Abschnitt erwähnt `` als Inline-Mount, aber die Komponente existiert im aktuellen Codebase nicht — nach dem `pendingProposals`-Table-Drop in Dexie v29 wurde die Proposal-Darstellung auf `server-iteration-staging` + den Cross-Module-Inbox im Mission-Detail-View umgestellt. Articles-Proposals tauchen dort automatisch auf. Falls die Inline-Komponente wieder reaktiviert wird, muss nur der Mount in `ListView.svelte` ergänzt werden. -Nächster Schritt: M7 (Share-Target + Bookmarklet) oder M8 (HighlightsView + Stats + Dashboard-Widget). +**M7 Share-Target + Bookmarklet: DONE** (commit pending) — `@mana/shared-pwa` bekommt neue Types (`PWAShareTarget`, `PWAShareTargetParams`), `createPWAConfig` threadet `shareTarget` in den Manifest, `ManifestConfig.share_target?` ergänzt. Web-App: `vite.config.ts` setzt `shareTarget: { action: '/articles/add', method: 'GET', params: { title, text, url } }`; `AddUrlForm` liest Query-Params in `onMount` (inkl. URL-Regex-Fallback auf `text` weil Chrome Android / WhatsApp den Link dort reinstecken), triggert auto-Vorschau. Neue Route `/articles/settings` rendert Bookmarklet-Karte (Drag-to-Bookmark + Copy-Snippet + expandable Quellcode) und Share-Target-Erklärung. `ListView` bekommt Zahnrad-Button zum Settings-Aufruf. + +Nicht im Scope (bewusst ausgelassen): die „optional" im Plan markierte `_pendingUrls`-Offline-Queue. Kann als M7b nachgereicht werden wenn das Problem auftaucht. + +Nächster Schritt: M8 (HighlightsView + Stats + Dashboard-Widget). ## Ziel diff --git a/packages/shared-pwa/src/config.ts b/packages/shared-pwa/src/config.ts index 7392739b4..135b837f5 100644 --- a/packages/shared-pwa/src/config.ts +++ b/packages/shared-pwa/src/config.ts @@ -48,6 +48,7 @@ export function createPWAConfig(options: PWAConfigOptions): PWAConfig { backgroundColor = DEFAULT_BACKGROUND_COLOR, preset = 'standard', shortcuts = [], + shareTarget, categories = DEFAULT_CATEGORIES, includeAssets = [], globIgnores = [], @@ -91,6 +92,17 @@ export function createPWAConfig(options: PWAConfigOptions): PWAConfig { })); } + // Web Share Target — lets installed PWAs appear in the OS share + // sheet. Browsers that don't support the spec ignore the field. + if (shareTarget) { + manifest.share_target = { + action: shareTarget.action, + method: shareTarget.method ?? 'GET', + enctype: shareTarget.enctype, + params: shareTarget.params, + }; + } + // Build workbox config const workbox: WorkboxConfig = { globPatterns: DEFAULT_GLOB_PATTERNS, diff --git a/packages/shared-pwa/src/index.ts b/packages/shared-pwa/src/index.ts index 8f15be8c2..28c83e7d2 100644 --- a/packages/shared-pwa/src/index.ts +++ b/packages/shared-pwa/src/index.ts @@ -46,6 +46,8 @@ export type { PWAConfigOptions, PWAConfig, PWAShortcut, + PWAShareTarget, + PWAShareTargetParams, WorkboxPreset, ManifestConfig, ManifestIcon, diff --git a/packages/shared-pwa/src/types.ts b/packages/shared-pwa/src/types.ts index a83b1a038..5a7d7ebca 100644 --- a/packages/shared-pwa/src/types.ts +++ b/packages/shared-pwa/src/types.ts @@ -20,6 +20,36 @@ export interface PWAShortcut { url: string; } +/** + * Web Share Target API configuration. When an installed PWA declares + * a share target, the OS share sheet offers the app as a destination + * for URLs / text / titles. The browser invokes `action` with the + * selected data mapped to the `params` field names. + * + * Reference: https://www.w3.org/TR/web-share-target/ + * Browser support: Chromium (Android + desktop installed PWAs). + * Safari / Firefox ignore the field gracefully. + */ +export interface PWAShareTargetParams { + /** Query/form param that carries the shared page title. */ + title?: string; + /** Query/form param for free-form shared text. */ + text?: string; + /** Query/form param for the shared URL. */ + url?: string; +} + +export interface PWAShareTarget { + /** In-app URL the browser should navigate to with the shared payload. */ + action: string; + /** HTTP method. GET maps data to query params, POST to form data. */ + method?: 'GET' | 'POST'; + /** Required for method=POST — typical value: 'multipart/form-data'. */ + enctype?: string; + /** Maps the spec's title/text/url slots onto your own param names. */ + params: PWAShareTargetParams; +} + /** * Configuration options for createPWAConfig */ @@ -67,6 +97,13 @@ export interface PWAConfigOptions { */ shortcuts?: PWAShortcut[]; + /** + * Web Share Target config. Installed PWA becomes a destination in + * the OS share sheet for URLs / text / titles. Ignored by browsers + * that don't support the spec (Safari / Firefox). + */ + shareTarget?: PWAShareTarget; + /** * App categories for store listings * @default ["productivity", "utilities"] @@ -165,6 +202,12 @@ export interface ManifestConfig { url: string; icons?: Array<{ src: string; sizes: string }>; }>; + share_target?: { + action: string; + method: 'GET' | 'POST'; + enctype?: string; + params: PWAShareTargetParams; + }; } /**