| 1 | # @alchemy.run/vite-plugin-copy-editor |
| 2 | |
| 3 | Edit hardcoded copy in the browser while `vite dev` runs, and have each edit |
| 4 | written back to the source file. |
| 5 | |
| 6 | ```ts |
| 7 | // vite.config.ts (or `vite.plugins` in astro.config.ts) |
| 8 | import { copyEditor } from "@alchemy.run/vite-plugin-copy-editor"; |
| 9 | |
| 10 | export default defineConfig({ |
| 11 | // Register before framework plugins so it sees the raw source. |
| 12 | plugins: [copyEditor(), react()], |
| 13 | }); |
| 14 | ``` |
| 15 | |
| 16 | Mark any piece of hardcoded copy with a bare `data-copy` attribute: |
| 17 | |
| 18 | ```html |
| 19 | <h1 data-copy>Reach for the better primitives.</h1> |
| 20 | <p data-copy>Snap together cloud resources with <code>Alchemy</code>.</p> |
| 21 | ``` |
| 22 | |
| 23 | - **Dev** (`vite serve`): every `data-copy` element is editable. Click and type; |
| 24 | leaving the element or pressing Enter saves, Escape reverts. Only the changed |
| 25 | words are rewritten, so tags, entities, and line wrapping are preserved. |
| 26 | Ctrl/Cmd+click still follows links inside editable copy. |
| 27 | - **Build**: the attribute is stripped and none of the editor ships. |
| 28 | |
| 29 | Works with `.astro`, `.html`, `.jsx`, `.tsx`, `.vue`, and `.svelte` files |
| 30 | (configure with `copyEditor({ include: /\.(tsx|html)$/ })`). An editable |
| 31 | element may contain only text and plain HTML tags; elements containing |
| 32 | `{expressions}` or components are rejected with an explanation in the browser. |
| 33 | |
| 34 | ## Markdown sections |
| 35 | |
| 36 | Content that comes from markdown (docs pages, a CMS, generated reference) can |
| 37 | be edited as markdown instead of rendered text. Mark the section's container |
| 38 | and supply a handler for its id scheme: |
| 39 | |
| 40 | ```html |
| 41 | <div data-copy="docs:intro" data-copy-format="markdown" data-copy-style="docs"> |
| 42 | …rendered markdown… |
| 43 | </div> |
| 44 | ``` |
| 45 | |
| 46 | ```ts |
| 47 | copyEditor({ |
| 48 | handlers: { |
| 49 | docs: { |
| 50 | readSource: async (id) => loadMarkdown(id), |
| 51 | writeSource: async ({ id, source, base }) => saveMarkdown(id, source, base), |
| 52 | }, |
| 53 | }, |
| 54 | markdownStyles: { |
| 55 | docs: { |
| 56 | elements: { |
| 57 | code: { class: "inline-code" }, // inline code |
| 58 | pre: { class: "code-block" }, // code blocks |
| 59 | a: { class: "link" }, |
| 60 | ul: { class: "bullets" }, |
| 61 | }, |
| 62 | css: `[data-copy-style="docs"] .code-block { background: #111; }`, |
| 63 | }, |
| 64 | }, |
| 65 | }); |
| 66 | ``` |
| 67 | |
| 68 | Focusing the section swaps in its markdown source (Enter adds a line, |
| 69 | Ctrl/Cmd+Enter or leaving the section saves, Escape cancels). One-line |
| 70 | template copy is edited as inline markdown: Enter saves, and Shift+Enter |
| 71 | adds a line break (`<br>`). After saving, |
| 72 | the section shows a preview rendered with its `data-copy-style` preset. |
| 73 | Once the handler's follow-up work (for example regenerating pages) finishes, |
| 74 | the page's own rendering replaces the preview. If the handler reports a |
| 75 | structural change (`reload: true`), the page reloads instead. |
| 76 | |
| 77 | Content that isn't rendered through Vite (markdown files) can mark a single |
| 78 | element with a trailing `<!--copy:<scheme>:<id>-->` comment for inline |
| 79 | editing; handlers implement `edit` for those. |