Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/extension/lib/i18n.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { formatLocale, messagesFor } from "@pluck/i18n";
import { formatLocale, extensionMessages } from "@pluck/i18n/extension";
import type { ReactNode } from "react";
import { IntlProvider } from "use-intl";
import { locale } from "./t";

export { locale };

export function I18n({ children }: { children: ReactNode }) {
return <IntlProvider locale={formatLocale(locale)} messages={{ extension: messagesFor("extension", locale) }}>{children}</IntlProvider>;
return <IntlProvider locale={formatLocale(locale)} messages={{ extension: extensionMessages(locale) }}>{children}</IntlProvider>;
}
4 changes: 2 additions & 2 deletions apps/extension/lib/t.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { formatLocale, matchLocale, messagesFor } from "@pluck/i18n";
import { formatLocale, matchLocale, extensionMessages } from "@pluck/i18n/extension";
import { createTranslator } from "use-intl/core";

// The browser's language, for the popup, options and the service worker (which has no React).
// Chrome's own UI strings (the manifest) come from public/_locales instead.
export const locale = matchLocale(navigator.languages);
export const t = createTranslator({ locale: formatLocale(locale), messages: { extension: messagesFor("extension", locale) }, namespace: "extension" });
export const t = createTranslator({ locale: formatLocale(locale), messages: { extension: extensionMessages(locale) }, namespace: "extension" });
2 changes: 1 addition & 1 deletion apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@pluck/extension",
"description": "The Pluck browser extension: pluck the page you are on, a link, an image or a selection.",
"repository": { "type": "git", "url": "https://github.com/codu-code/pluck.git", "directory": "apps/extension" },
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"license": "MIT",
"type": "module",
Expand Down
4 changes: 3 additions & 1 deletion apps/extension/store/listing.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ What to paste into each field of the developer dashboard. The zip is `pnpm --fil
```
Pluck the page you are on. One click keeps it in your Pluck library, with the text as you see it, so you can read it later, find it again, or ask your AI about it.

Click Pluck, or press Alt+Shift+P. "Plucked" appears with the title, and the popup lets you add a tag, change the Kind, put it on a List, write a note, or send it straight to Later or the Archive.
Click Pluck, or press Alt+Shift+P (⌥⇧P on a Mac). "Plucked" appears with the title, and the popup lets you add a tag, change the Kind, put it on a List, write a note, or send it straight to Later or the Archive.

Right-click a link, an image or the page to pluck it without opening it. Select a sentence and press Alt+Shift+H to keep it as a highlight. Pluck all your tabs into a new List at once.

Expand All @@ -27,6 +27,8 @@ The Pluck icon fills in on pages already in your library, so you can see at a gl

No connection? Plucks wait on your computer and go when you are back online.

Pluck speaks English, Spanish, Brazilian Portuguese, French, German, Japanese, Italian, Dutch, Korean and Simplified Chinese, and follows your browser's language.

Pluck is open source. Use getpluck.io, or build the extension for a server you run yourself. There is no AI inside Pluck: you connect the AI tools you already use, if you want to.

Needs a free Pluck account at https://getpluck.io.
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/(app)/items/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isTextKind, withReferral, type Item } from "@pluck/shared-schemas";
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import Link from "next/link";
import { CHROME_STORE_URL } from "@/lib/siteUrls";
import { cache } from "react";
import { notFound } from "next/navigation";
import { DeleteButton, ItemBar } from "@/components/ItemBar";
Expand Down Expand Up @@ -68,7 +68,7 @@ export default async function ItemPage({ params }: { params: Promise<{ id: strin
<div className={styles.tip}>
<b>{t("failed.loginTitle")}</b>
<span>{t("failed.loginBody")}</span>
<Link href="/connect#extension" className={styles.tipLink}>{t("failed.extension")}</Link>
<a href={CHROME_STORE_URL} className={styles.tipLink} target="_blank" rel="noopener">{t("failed.extension")}</a>
</div>
<div className={styles.kept}>
<span className={styles.keptTitle}>{t("failed.keptTitle")}</span>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(site)/SiteChrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { LanguageSelect } from "@/components/LanguageSelect";
import { REPO_URL } from "@/lib/site";
import styles from "./site.module.css";

type Page = "home" | "chrome" | "iphone" | "connect" | "open-source" | "support" | "privacy" | "terms" | "blog";
type Page = "home" | "iphone" | "connect" | "open-source" | "support" | "privacy" | "terms" | "blog";

// Header and footer for the marketing pages (the boards' shared chrome).
export async function SitePage({ page, dark = false, footerNote, children }: { page: Page; dark?: boolean; footerNote?: ReactNode; children: ReactNode }) {
Expand Down
11 changes: 0 additions & 11 deletions apps/web/app/(site)/chrome/opengraph-image.tsx

This file was deleted.

29 changes: 0 additions & 29 deletions apps/web/app/(site)/chrome/page.tsx

This file was deleted.

3 changes: 2 additions & 1 deletion apps/web/app/(site)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { getTranslations } from "next-intl/server";
import Link from "@/components/SiteLink";
import type { ReactNode } from "react";
import { JsonLd } from "@/components/JsonLd";
import { APP_STORE_URL, CHROME_STORE_URL, REPO_URL, pageMeta } from "@/lib/site";
import { APP_STORE_URL, REPO_URL, pageMeta } from "@/lib/site";
import { CHROME_STORE_URL } from "@/lib/siteUrls";
import { PasteBox } from "./PasteBox";
import { Reveal } from "./Reveal";
import { SitePage } from "./SiteChrome";
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(site)/site.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@
@media (max-width: 760px) {
.beta { grid-template-columns: minmax(0, 1fr); gap: 0; }
.qr { display: none; }
} /* the iPhone and Chrome pages: room above the footer */
} /* the iPhone page: room above the footer */
.supportTitle { margin: 0 0 0.16em; font-size: clamp(88px, 15vw, 210px); line-height: 0.86; } /* 0.16em: room for the tail of a y or a g */
.heroPara a { text-decoration-thickness: 2px; text-underline-offset: 4px; text-decoration-color: var(--accent); }
.topics { display: flex; flex-wrap: wrap; gap: 8px; }
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LOCALE_NAMES, LOCALES } from "@pluck/i18n";
import { postsIn } from "@/lib/blog";
import { localePath } from "@/lib/siteUrls";
import { CHROME_STORE_URL, localePath } from "@/lib/siteUrls";
import { CONNECTOR_URL, LICENCE, REPO_URL } from "@/lib/site";

const base = process.env.BETTER_AUTH_URL ?? "http://localhost:3000";
Expand Down Expand Up @@ -30,7 +30,7 @@ Key facts:
- [Open source](${base}/open-source): self-hosting, export and what Pluck does with your data
- [Support](${base}/support): answers about the extension, iPhone, your AI and your account
- [iPhone](${base}/iphone): the iPhone app
- [Chrome](${base}/chrome): the Chrome extension
- [Chrome](${CHROME_STORE_URL}): the Chrome extension, in the Chrome Web Store

## Blog

Expand Down
5 changes: 3 additions & 2 deletions apps/web/components/EmptyInbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslations } from "next-intl";
import { useSyncExternalStore } from "react";
import Link from "next/link";
import { isMacAgent, shortcutText } from "@/lib/shortcut";
import { CHROME_STORE_URL } from "@/lib/siteUrls";
import styles from "./EmptyInbox.module.css";

// The Empty board's three ways to start.
Expand Down Expand Up @@ -32,7 +33,7 @@ export function EmptyInbox() {
<span>{t("two.body", { keys: shortcutText(mac, "P") })}</span>
</span>
<span className={styles.actions}>
<Link href="/connect#extension" className={styles.action}>{t("two.chrome")}</Link>
<a href={CHROME_STORE_URL} className={styles.action} target="_blank" rel="noopener">{t("two.chrome")}</a>
</span>
</li>
<li className={styles.step}>
Expand All @@ -42,7 +43,7 @@ export function EmptyInbox() {
<span>{t("three.body")}</span>
</span>
<span className={styles.actions}>
<Link href="/connect#ios" className={styles.action}>{t("three.ios")}</Link>
<Link href="/iphone" className={styles.action}>{t("three.ios")}</Link>
<Link href="/settings#shortcut" className={styles.action}>{t("three.shortcut")}</Link>
</span>
</li>
Expand Down
2 changes: 0 additions & 2 deletions apps/web/lib/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { siteAlternates } from "./siteUrls";
// Links the marketing site needs that only exist at launch. Placeholders stay [LIKE THIS] until then.
export const REPO_URL = "https://github.com/codu-code/pluck";
export const CLONE_URL = "https://github.com/codu-code/pluck";
// shortcut: a "coming soon" page while the listing is in review; swap in the store URL once it is live.
export const CHROME_STORE_URL = "/chrome";
// shortcut: the TestFlight beta page while the app is in public beta; swap in the App Store URL once it is live.
export const APP_STORE_URL = "/iphone";
// The public beta: anyone with the link can join (up to the limit set in App Store Connect, TestFlight, Public beta).
Expand Down
4 changes: 3 additions & 1 deletion apps/web/lib/siteUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { DEFAULT_LOCALE, LOCALES, type Locale } from "@pluck/i18n";
// The marketing site has one URL per language so search engines can list each: English at the root, the others
// under a prefix (/de/connect, /pt-br/blog/pocket-is-gone). proxy.ts reads the prefix; the signed-in app keeps one
// URL and goes by the cookie (i18n/request.ts).
export const SITE_PAGES = ["/", "/connect", "/open-source", "/iphone", "/chrome", "/support", "/privacy", "/terms", "/blog"];
export const CHROME_STORE_URL = "https://chromewebstore.google.com/detail/pluck/plcknnahikagnmlliangabeofipkhecc";

export const SITE_PAGES = ["/", "/connect", "/open-source", "/iphone", "/support", "/privacy", "/terms", "/blog"];
export const isSitePath = (path: string) => SITE_PAGES.includes(path) || /^\/blog\/[a-z0-9-]+$/.test(path);
const prefix = (l: Locale) => l.toLowerCase();

Expand Down
4 changes: 2 additions & 2 deletions apps/web/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
import "./.next/dev/types/root-params.d.ts";
import "./.next/types/routes.d.ts";
import "./.next/types/root-params.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
3 changes: 2 additions & 1 deletion apps/web/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextResponse, type NextRequest } from "next/server";
import { LOCALE_COOKIE, LOCALE_HEADER, localeFrom } from "@/i18n/request";
import { cardPage, isSitePath, localePath, splitLocale } from "@/lib/siteUrls";
import { CHROME_STORE_URL, cardPage, isSitePath, localePath, splitLocale } from "@/lib/siteUrls";

// The marketing site's language URLs (lib/siteUrls.ts). /de/connect is served by the /connect page in German.
// An English URL sends someone who picked another language, or whose browser asks for one, to that language's URL;
Expand All @@ -9,6 +9,7 @@ export function proxy(req: NextRequest) {
const { pathname, search } = req.nextUrl;
if (pathname === "/en" || pathname.startsWith("/en/")) return NextResponse.redirect(new URL((pathname.slice(3) || "/") + search, req.url), 308);
const { locale, path } = splitLocale(pathname);
if (path === "/chrome") return NextResponse.redirect(CHROME_STORE_URL, 308); // the coming-soon page, before the listing
const headers = new Headers(req.headers);
headers.delete(LOCALE_HEADER); // only a site URL says which language it is
const card = cardPage(path);
Expand Down
4 changes: 4 additions & 0 deletions apps/web/public/BingSiteAuth.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<users>
<user>B9B28C6BDED698D74C50526F4ADE328F</user>
</users>
2 changes: 1 addition & 1 deletion docs/launch-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Everything between the code in this repo and Pluck being live, in order. Each li
- [ ] Load it unpacked once and go through `docs/loop/BLOCKED.md` (slice 14).
- [ ] `pnpm --filter @pluck/extension zip`. (Firefox is not planned for launch; the build still supports it.)
- [ ] Chrome Web Store listing: description, screenshots, a 30-second video, privacy policy `https://getpluck.io/privacy`, data-use answers (the page you pluck and its address go to your own Pluck server; nothing else, no selling, no ads).
- [ ] Once the listings are live, put the store URL in `CHROME_STORE_URL` (it points at the `/chrome` coming-soon page until then).
- [x] Once the listings are live, put the store URL in `CHROME_STORE_URL` (`apps/web/lib/siteUrls.ts`; `/chrome` redirects there).

## iPhone

Expand Down
2 changes: 1 addition & 1 deletion e2e/design.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test("snapshot app pages for the design detector", async ({ page }) => {
await signIn(page);
const snap = (name: string) => snapshot(page, name);

for (const route of ["/", "/chrome", "/iphone", "/connect", "/open-source", "/support", "/privacy", "/terms", "/blog", "/blog/ask-your-ai-what-you-plucked"]) {
for (const route of ["/", "/iphone", "/connect", "/open-source", "/support", "/privacy", "/terms", "/blog", "/blog/ask-your-ai-what-you-plucked"]) {
await page.goto(route);
await snap(`site-${route.slice(1).replaceAll("/", "-") || "home"}`);
}
Expand Down
13 changes: 8 additions & 5 deletions e2e/site.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect, test } from "@playwright/test";
import { linkFromEmail, openSignInLink, signIn, uniqueEmail } from "./helpers";

const FIX = "http://127.0.0.1:4180";
const CHROME_STORE_URL = "https://chromewebstore.google.com/detail/pluck/plcknnahikagnmlliangabeofipkhecc";

test("a visitor pastes links on the home page, signs up, and finds them in the Inbox", async ({ page }) => {
await page.goto("/");
Expand Down Expand Up @@ -61,12 +62,14 @@ test("the site: connect your AI with tabs, open source, privacy and terms", asyn
await expect(page.getByRole("heading", { level: 1 })).toHaveText("Pluck it.");
});

test("Add to Chrome: a coming-soon page until the store listing is live", async ({ page }) => {
test("Add to Chrome goes to the store listing, and so does the old coming-soon page", async ({ page, request }) => {
await page.goto("/");
await page.getByRole("link", { name: "Add to Chrome" }).first().click();
await expect(page).toHaveURL(/\/chrome$/);
await expect(page.getByRole("heading", { level: 1 })).toHaveText("Soon.");
await expect(page.getByRole("link", { name: "Use it on the web" })).toHaveAttribute("href", "/signin");
await expect(page.getByRole("link", { name: "Add to Chrome" }).first()).toHaveAttribute("href", CHROME_STORE_URL);
for (const old of ["/chrome", "/de/chrome"]) {
const res = await request.get(old, { maxRedirects: 0 });
expect(res.status(), old).toBe(308);
expect(res.headers().location, old).toBe(CHROME_STORE_URL);
}
});

test("home: drawn pieces below the fold play once in view; reduced motion leaves them drawn", async ({ page, browser }) => {
Expand Down
13 changes: 0 additions & 13 deletions packages/i18n/messages/site/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,6 @@
"web": "Im Web nutzen"
}
},
"soon": {
"title": "Bald<dot>.</dot>",
"web": "Im Web nutzen",
"connect": "Deine KI verbinden",
"chrome": {
"meta": {
"title": "Pluck für Chrome · Pluck",
"description": "Pluck für Chrome wartet auf die Prüfung im Chrome Web Store. Es kann jeden Tag so weit sein."
},
"para1": "Pluck für Chrome wartet auf die Prüfung im Chrome Web Store, irgendwo hinter ein paar Tausend Gutschein-Findern. Es kann jeden Tag so weit sein.",
"para2": "Bis dahin funktioniert Pluck im Web: Füg einen Link ein, und er ist gepflückt. Auch deine KI kann für dich pflücken."
}
},
"openSource": {
"meta": {
"title": "Open Source · Pluck",
Expand Down
13 changes: 0 additions & 13 deletions packages/i18n/messages/site/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,6 @@
"web": "Use it on the web"
}
},
"soon": {
"title": "Soon<dot>.</dot>",
"web": "Use it on the web",
"connect": "Connect your AI",
"chrome": {
"meta": {
"title": "Pluck for Chrome · Pluck",
"description": "Pluck for Chrome is in the Chrome Web Store's review queue. Out any day now."
},
"para1": "Pluck for Chrome is in the Chrome Web Store's review queue, somewhere behind a few thousand coupon finders. Out any day now.",
"para2": "Until then, Pluck works on the web: paste a link and it is plucked. Your AI can pluck for you too."
}
},
"openSource": {
"meta": {
"title": "Open source · Pluck",
Expand Down
13 changes: 0 additions & 13 deletions packages/i18n/messages/site/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,6 @@
"web": "Úsalo en la web"
}
},
"soon": {
"title": "Pronto<dot>.</dot>",
"web": "Úsalo en la web",
"connect": "Conecta tu IA",
"chrome": {
"meta": {
"title": "Pluck para Chrome · Pluck",
"description": "Pluck para Chrome está en la cola de revisión de Chrome Web Store. Saldrá cualquier día de estos."
},
"para1": "Pluck para Chrome está en la cola de revisión de Chrome Web Store, en algún lugar detrás de unos cuantos miles de buscadores de cupones. Saldrá cualquier día de estos.",
"para2": "Mientras tanto, Pluck funciona en la web: pega un enlace y queda recogido. Tu IA también puede recoger por ti."
}
},
"openSource": {
"meta": {
"title": "Código abierto · Pluck",
Expand Down
13 changes: 0 additions & 13 deletions packages/i18n/messages/site/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,6 @@
"web": "L’utiliser sur le web"
}
},
"soon": {
"title": "Bientôt<dot>.</dot>",
"web": "L’utiliser sur le web",
"connect": "Connecter votre IA",
"chrome": {
"meta": {
"title": "Pluck pour Chrome · Pluck",
"description": "Pluck pour Chrome est dans la file d’examen du Chrome Web Store. Il sort d’un jour à l’autre."
},
"para1": "Pluck pour Chrome est dans la file d’examen du Chrome Web Store, quelque part derrière quelques milliers de chasseurs de codes promo. Il sort d’un jour à l’autre.",
"para2": "En attendant, Pluck marche sur le web : collez un lien et il est cueilli. Votre IA peut aussi cueillir pour vous."
}
},
"openSource": {
"meta": {
"title": "Open source · Pluck",
Expand Down
13 changes: 0 additions & 13 deletions packages/i18n/messages/site/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,6 @@
"web": "Usalo sul web"
}
},
"soon": {
"title": "Presto<dot>.</dot>",
"web": "Usalo sul web",
"connect": "Collega la tua IA",
"chrome": {
"meta": {
"title": "Pluck per Chrome · Pluck",
"description": "Pluck per Chrome è in coda per la revisione del Chrome Web Store. Esce a giorni."
},
"para1": "Pluck per Chrome è in coda per la revisione del Chrome Web Store, da qualche parte dietro qualche migliaio di estensioni per i coupon. Esce a giorni.",
"para2": "Nel frattempo, Pluck funziona sul web: incolla un link ed è preso. Anche la tua IA può prenderli per te."
}
},
"openSource": {
"meta": {
"title": "Open source · Pluck",
Expand Down
Loading
Loading