[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-85145":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":8,"language":10,"languages":8,"totalLinesOfCode":8,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":14,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":15,"rankGlobal":8,"rankLanguage":8,"license":16,"archived":17,"fork":17,"defaultBranch":18,"hasWiki":19,"hasPages":19,"topics":20,"createdAt":8,"pushedAt":8,"updatedAt":21,"readmeContent":22,"aiSummary":8,"trendingCount":14,"starSnapshotCount":14,"syncStatus":23,"lastSyncTime":24,"discoverSource":25},85145,"Polypost","markrussinovich\u002FPolypost","markrussinovich",null,"https:\u002F\u002Fmarkrussinovich.github.io\u002FPolypost\u002F","TypeScript",109,23,4,0,37.14,"MIT License",false,"main",true,[],"2026-06-15 10:04:50","# Polypost\n\nDraft a post once and format it for every platform at the same time. The main editor uses TipTap for word-processor-style editing, and a live preview rail shows your post on **LinkedIn, X, Bluesky, Threads, Mastodon, Facebook, and Instagram**, each with its own character limit, counting rule, and formatting applied.\n\nIt ships in two forms:\n\n- **Web app** — the multi-platform editor. Live at https:\u002F\u002Fmarkrussinovich.github.io\u002FPolypost\u002F\n- **LinkedIn browser extension** — a LinkedIn-only companion that turns LinkedIn's own composer into the editor: **Start a post** opens it in place of the native box, and **Post** publishes through LinkedIn's own flow. See [LinkedIn browser extension](#linkedin-browser-extension).\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"docs\u002Fscreenshot.png\" alt=\"Polypost screenshot\" width=\"480\">\n\u003C\u002Fp>\n\nThis is not an official app of any platform. Drafts stay in your browser; the extension only acts on LinkedIn when you click Post.\n\n## Using it\n\n- **Write once, preview everywhere.** Type in the main editor and toggle the platforms you care about with the chips; each enabled platform shows a live preview card with its character count and any warnings.\n- **Tailor per platform.** Edit inside a card to *fork* a platform-specific version (it gets a Customized badge), and re-sync to the main draft any time. Newlines map 1:1 like LinkedIn's composer — Enter for a new line, again for a blank line.\n- **Copy or open.** Use **Copy** for platform-ready text, or **Copy & open** to launch that platform's composer pre-filled (X, Bluesky, Threads, Mastodon).\n- **Mention people.** Write `@[Name]` — it shows as `@Scott Hanselman` on LinkedIn (where the extension resolves it into a real, clickable mention) and collapses to a single handle-style token like `@ScottHanselman` on X, Bluesky, Threads, and Mastodon, so their autocomplete fires on the whole name (and can match a handle) instead of splitting it at the space.\n- **Add media & links once.** Use the **Images & links** tray to reuse an image or link across platforms: links fold into each platform's text and count, and an image can be copied to the clipboard to paste into LinkedIn (or downloaded \u002F dragged into any composer).\n- **Optional AI.** Connect your own LLM key (Anthropic Claude, Google Gemini, or any OpenAI-compatible endpoint) to write, adapt, and auto-fit posts — with documents or URLs as reference context. Your key stays in your browser.\n\n## Features\n\n- **Live multi-platform previews** with each platform's character limit, counting rule, formatting, and warnings — LinkedIn, X, Bluesky, Threads, Mastodon, Facebook, and Instagram.\n- **Fork-on-edit** per platform with one-click re-sync, plus local autosave and saved drafts.\n- **`@[Name]` mentions**, highlighted in the editor and previews — kept spaced for LinkedIn (where the extension resolves them into real, clickable mentions) and collapsed to a single handle-style token elsewhere.\n- **Shared images & links** — add once, reuse everywhere; *Copy image* pastes a picture straight into LinkedIn.\n- **Optional, bring-your-own-key AI** — write, adapt a single platform, auto-fit over-limit posts, and feed in reference sources (files, URLs, or pasted text), with a multiline prompt box and remembered prompt history.\n- **Rich-text editing** — Markdown and Word paste, file import, a searchable emoji picker, lists, and links, with LinkedIn-style Unicode styling where it helps.\n- **Private by default** — drafts, settings, and API keys stay in your browser; nothing leaves it except the AI endpoint you choose to configure.\n\n## Local Development\n\n```bash\nnpm install\nnpm run dev\n```\n\nRun tests:\n\n```bash\nnpm test\n```\n\nBuild the web app for production:\n\n```bash\nnpm run build\n```\n\nPreview the production build locally:\n\n```bash\nnpm run preview\n```\n\n## LinkedIn browser extension\n\nThe extension is **LinkedIn-only** — it runs on, and posts to, LinkedIn and nowhere else. It turns LinkedIn's composer into the editor: clicking **Start a post** on LinkedIn opens the rich-text editor in place of the native post box. When you click **Post**, it exports LinkedIn-ready Unicode text (resolving `@[Name]` mentions through LinkedIn's typeahead into real, clickable mentions), writes it into LinkedIn's native composer behind the scenes, and clicks LinkedIn's own **Post** button. The native composer stays hidden throughout, so it feels like you are posting directly from the editor.\n\n### How it works\n\n- A content script (`src\u002Fextension\u002Fcontent-script.tsx`) runs on `linkedin.com`, mounts the formatter UI, and listens for clicks on LinkedIn's **Start a post** control.\n- LinkedIn renders its composer inside a **shadow root**, so the script pierces shadow boundaries to find the composer, suppress it (CSS `visibility:hidden` while you edit, so its focus trap cannot steal focus from the formatter), and drive it.\n- On **Post**, the script briefly makes the hidden composer focusable, hands any attached images\u002Fvideos to LinkedIn's media upload input (confirming the media editor's **Next** step), inserts the exported text (resolving `@[Name]` mention tokens through the composer's mention typeahead), waits for LinkedIn's link preview card when the text contains a URL, waits for LinkedIn's Post button to enable, clicks it, and confirms the composer closed.\n- A service worker (`src\u002Fextension\u002Fpublic\u002Fbackground.js`) re-injects the script if you click the toolbar icon on a LinkedIn tab.\n\n### Permissions\n\n- `host_permissions` for `*.linkedin.com` — the extension only runs on LinkedIn.\n- `clipboardWrite` — the **Copy for LinkedIn** button.\n- `scripting` — re-inject the formatter when the toolbar icon is clicked.\n\nNo analytics, no remote servers, no `chrome.storage` — drafts are kept in the page's `localStorage`.\n\n### Build and load unpacked (for development)\n\n```bash\nnpm run build:extension\n```\n\nThen load `dist-extension` as an unpacked extension from `chrome:\u002F\u002Fextensions` or `edge:\u002F\u002Fextensions` (enable Developer mode first). Do **not** load `src\u002Fextension`; only `dist-extension` contains the built `content-script.js`, `style.css`, `manifest.json`, and icons the browser runs.\n\nAfter rebuilding, click **Reload** on the extension card, then reload any open LinkedIn tab (the content script injects its CSS on page load, so an extension reload alone keeps the old styles). If the extension is enabled but no formatter appears, remove the unpacked extension and load `dist-extension` again.\n\n### Package for the Chrome Web Store\n\n```bash\nnpm run package:extension\n```\n\nThis builds the extension and writes `release\u002Flinkedin-post-formatter-v\u003Cversion>.zip` with `manifest.json` at the archive root, ready to upload.\n\n### Regenerate icons\n\nThe extension icons (`src\u002Fextension\u002Fpublic\u002Ficons\u002Ficon-{16,48,128}.png`) are rendered from `public\u002Ffavicon.svg`. To regenerate them after changing the source art, run a Chromium browser with remote debugging on port 9222 and:\n\n```bash\nnode scripts\u002Fgenerate-extension-icons.mjs\n```\n\n## GitHub Pages Deployment\n\nThe workflow in `.github\u002Fworkflows\u002Fpages.yml` builds the app and deploys `dist` to GitHub Pages on pushes to `main`.\n\nIn the repository settings, set Pages source to **GitHub Actions**. The workflow passes `VITE_BASE_PATH` as `\u002F${{ github.event.repository.name }}\u002F`, which matches the standard project Pages URL path. For a custom domain, set `VITE_BASE_PATH` to `\u002F` in the workflow.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n",2,"2026-06-15 02:30:10","CREATED_QUERY"]