Commit Graph

2 Commits

Author SHA1 Message Date
Huang Xin 912e97cb82 feat(send): iOS share-extension picker + App Group queue + reliable host launch (#4267)
* feat(send): iOS share-extension picker + App Group queue + reliable host launch

Rework the iOS Share Extension to a Zotero-style sheet: URL preview row +
library group picker + "Save & Open". Queues each save into the shared
App Group container and best-effort launches the host app via the
Chrome-style responder-chain trick (IMP cast against
`openURL:options:completionHandler:`). The host plugin drains the queue
on `applicationDidBecomeActive`, so if the launch ever fails the article
still ingests next time Readest is opened.

A `WKScriptMessageHandler` named `readestShareBridge` lets the JS hook
post `{type:'ready'}` on mount, fixing the cold-start race when the
extension wakes the app before the React side has loaded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(send): cover generator, favicon fetcher, share-extension polish, locale sync

Builds on the previous commit (iOS share-extension picker + App Group queue +
reliable host launch) with three further additions to the send-to-Readest
pipeline:

* **Cover generator** — `services/send/conversion/coverGenerator.ts` renders a
  deterministic cover image into clipped EPUBs (favicon + page title + host).
  Hooked into `buildEpub.ts` so every clip path (desktop, mobile, browser
  extension) produces the same cover for the same source.
* **Favicon fetcher** — `services/send/conversion/faviconFetcher.ts` resolves
  the best-available site icon (Open Graph image → apple-touch-icon →
  /favicon.ico), with size + format normalization. Feeds the cover generator.
* **Unified page conversion** — `convertToEpub({kind:'page', ...})` replaces
  the older `convertPageToEpub(html, url)` so the share extension, /send page,
  and browser extension share one entry point. Test: `send-convert-page-unified`.
* **Share-extension project.yml comment** — clarifies why the ShareExtension
  target carries no `.lproj` files (system bar buttons + JS-supplied "Default"
  label, no per-locale strings to wire).
* **Locale sync** — 33 translation.json files updated with new "Default",
  "Saving article…", and cover-generator strings extracted by i18next-scanner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:46:13 +02:00
Huang Xin 62b5ed8138 feat(send): handle shared URLs from system share sheets (iOS + Android) (#4256)
Users can now tap "Share → Readest" in Safari, Chrome, or any other
browser on iOS / Android and the article URL flows through the same
clip-and-import pipeline the in-app "From Web URL" entry uses.

Android

`MainActivity.handleIncomingIntent` already routed file shares via
`ACTION_SEND` + `EXTRA_STREAM`. Extend it to also pick up URL shares
via `ACTION_SEND` + `EXTRA_TEXT`: parse the first http(s) token out of
the text payload and dispatch it on the existing `shared-intent` event
channel. No new event channel needed — `useAppUrlIngress` already
listens and re-broadcasts as `app-incoming-url`.

The existing `<intent-filter>` for `ACTION_SEND` with `*/*` MIME type
already accepts `text/plain` from browsers — no manifest change
required.

iOS

`gen/apple/` gains a new ShareExtension target. The extension's
`ShareViewController` extracts a URL from `NSExtensionContext.inputItems`
(prefers `public.url`, falls back to first http(s) token in
`public.plain-text`) and forwards it to the main app as
`readest://clip?url=<encoded>` via the responder-chain `openURL:`
selector — the standard share-extension trick used by Pocket,
Instapaper, Matter, etc.

`project.yml` adds the ShareExtension target and switches the main app's
Info.plist / entitlements references to `INFOPLIST_FILE` /
`CODE_SIGN_ENTITLEMENTS` build settings instead of xcodegen's `info:` /
`entitlements:` blocks. That way the hand-tuned `Readest_iOS/Info.plist`
(CFBundleDocumentTypes, UTExportedTypeDeclarations, locales,
CFBundleURLTypes for readest://, applesignin, associated-domains for
Universal Links) is treated as an opaque input — xcodegen won't
regenerate it.

JS

New `useClipUrlIngress` hook subscribes to `app-incoming-url`,
unwraps `readest://clip?url=<encoded>` into the inner URL (the iOS
forwarding path), filters out file URIs and annotation deep links,
and runs each remaining http(s) URL through `clip_url` →
`convertToEpubWithWorker` → `ingestFile` — the same path `/send` uses.

Mounted alongside `useOpenWithBooks` and `useOpenAnnotationLink` in
both `app/library/page.tsx` and `app/reader/page.tsx` so shares
arriving while the user is reading still process.

Notes

- The PR targets `feat/send-clip-mobile` (PR #4252) since the share
  pipeline depends on `clip_url` being available on mobile.
- iOS Share Extension built locally via xcodegen; the regenerated
  pbxproj is tracked because gen/apple is gitignored.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:50:33 +02:00