mechanica 2.0.0-alpha.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +288 -48
  2. package/bin/mechanica.js +7 -3
  3. package/dist/chunks/{ImagePickerDialog-CY-mn2UP.js → ImagePickerDialog-6skSAzJh.js} +44 -0
  4. package/dist/chunks/{create-mechanica-CnBFiyfJ.js → create-mechanica-CYaKXuQd.js} +243 -25
  5. package/dist/chunks/{editor-9tquvDcS.js → editor-B0r4EOIM.js} +84 -9
  6. package/dist/chunks/{page-assets-BCUAuOlo.js → page-assets-Bp6mxPSy.js} +14 -2
  7. package/dist/chunks/{preview-G5_AFI5P.js → preview-CbUxh2gL.js} +4 -2
  8. package/dist/cli.js +59 -8
  9. package/dist/composer.js +603 -180
  10. package/dist/editor.css +365 -97
  11. package/dist/editor.js +458 -152
  12. package/dist/index.js +48 -85
  13. package/dist/plugin.js +107 -22
  14. package/dist/types/cli/export.d.ts +16 -1
  15. package/dist/types/cli/page-assets.d.ts +7 -0
  16. package/dist/types/core/create-mechanica.d.ts +3 -1
  17. package/dist/types/core/data-registry.d.ts +2 -0
  18. package/dist/types/core/define-block.d.ts +17 -0
  19. package/dist/types/core/define-data.d.ts +8 -0
  20. package/dist/types/core/define-mechanica-app.d.ts +7 -0
  21. package/dist/types/core/index.d.ts +1 -0
  22. package/dist/types/core/layout.d.ts +27 -0
  23. package/dist/types/core/render-blocks.d.ts +2 -1
  24. package/dist/types/core/state.d.ts +5 -0
  25. package/dist/types/editor/composer/lib/canvas.d.ts +15 -2
  26. package/dist/types/editor/composer/lib/composer-store.d.ts +18 -2
  27. package/dist/types/editor/lib/block-meta.d.ts +16 -0
  28. package/dist/types/editor/lib/bridge.d.ts +8 -0
  29. package/dist/types/editor/lib/data-meta.d.ts +9 -0
  30. package/dist/types/editor/lib/page-list.d.ts +2 -0
  31. package/dist/types/editor/lib/page-setup.d.ts +15 -0
  32. package/dist/types/editor/lib/store.d.ts +9 -0
  33. package/dist/types/editor/lib/types.d.ts +2 -0
  34. package/dist/types/editor/lib/use-block-frames.d.ts +9 -0
  35. package/dist/types/elements/emit-css.d.ts +2 -1
  36. package/dist/types/elements/style-vars.d.ts +10 -8
  37. package/dist/types/vite/dev/pages-store.d.ts +6 -0
  38. package/dist/types/vite/entries.d.ts +3 -0
  39. package/dist/types/vite/plugin.d.ts +26 -1
  40. package/dist/widgets.js +1 -1
  41. package/package.json +2 -8
  42. package/src/cli/args.ts +0 -33
  43. package/src/cli/build.ts +0 -40
  44. package/src/cli/cli.ts +0 -70
  45. package/src/cli/export.ts +0 -771
  46. package/src/cli/headless.ts +0 -361
  47. package/src/cli/images.ts +0 -67
  48. package/src/cli/page-assets.ts +0 -114
  49. package/src/cli/push.ts +0 -64
  50. package/src/cli/shot.ts +0 -245
  51. package/src/cli/thumbs.ts +0 -252
  52. package/src/compiler/compile-block.ts +0 -212
  53. package/src/core/composed.ts +0 -45
  54. package/src/core/composer-components.ts +0 -31
  55. package/src/core/content.ts +0 -24
  56. package/src/core/create-mechanica.ts +0 -126
  57. package/src/core/data-registry.ts +0 -37
  58. package/src/core/define-block.ts +0 -51
  59. package/src/core/define-data.ts +0 -65
  60. package/src/core/define-mechanica-app.ts +0 -56
  61. package/src/core/field-types.ts +0 -37
  62. package/src/core/image.ts +0 -107
  63. package/src/core/index.ts +0 -57
  64. package/src/core/link.ts +0 -87
  65. package/src/core/load-blocks.ts +0 -59
  66. package/src/core/preview.ts +0 -230
  67. package/src/core/query.ts +0 -39
  68. package/src/core/render-blocks.ts +0 -66
  69. package/src/core/router.ts +0 -137
  70. package/src/core/state.ts +0 -37
  71. package/src/core/use-fetch.ts +0 -14
  72. package/src/core/use-locale.ts +0 -40
  73. package/src/core/use-page-data.ts +0 -10
  74. package/src/core/use-pages.ts +0 -39
  75. package/src/core/use-pagination.ts +0 -75
  76. package/src/editor/EditorApp.vue +0 -396
  77. package/src/editor/components/BlockFrame.vue +0 -18
  78. package/src/editor/components/BlockPalette.vue +0 -338
  79. package/src/editor/components/BlockPreview.vue +0 -127
  80. package/src/editor/components/BlockSettings.vue +0 -63
  81. package/src/editor/components/DataSettings.vue +0 -240
  82. package/src/editor/components/HierarchyTree.vue +0 -568
  83. package/src/editor/components/IconField.vue +0 -201
  84. package/src/editor/components/LocaleSwitcher.vue +0 -239
  85. package/src/editor/components/PageBar.vue +0 -109
  86. package/src/editor/components/PanelToggle.vue +0 -161
  87. package/src/editor/components/TypographyInput.vue +0 -131
  88. package/src/editor/components/VContextMenu.vue +0 -187
  89. package/src/editor/components/VIcon.vue +0 -20
  90. package/src/editor/components/VPopover.vue +0 -135
  91. package/src/editor/components/VSegmented.vue +0 -93
  92. package/src/editor/components/VSelect.vue +0 -222
  93. package/src/editor/composer/ComposerApp.vue +0 -220
  94. package/src/editor/composer/components/AlignGrid.vue +0 -74
  95. package/src/editor/composer/components/AnchorGrid.vue +0 -36
  96. package/src/editor/composer/components/BindField.vue +0 -42
  97. package/src/editor/composer/components/BoxSideInput.vue +0 -141
  98. package/src/editor/composer/components/CanvasOverlay.vue +0 -488
  99. package/src/editor/composer/components/ClassStylePreview.vue +0 -82
  100. package/src/editor/composer/components/ColorField.vue +0 -36
  101. package/src/editor/composer/components/ComponentFields.vue +0 -39
  102. package/src/editor/composer/components/ComposerCanvas.vue +0 -376
  103. package/src/editor/composer/components/ComposerInspector.vue +0 -506
  104. package/src/editor/composer/components/ComposerLayers.vue +0 -28
  105. package/src/editor/composer/components/ComposerSettings.vue +0 -52
  106. package/src/editor/composer/components/InsertToolbar.vue +0 -96
  107. package/src/editor/composer/components/LayerNode.vue +0 -69
  108. package/src/editor/composer/components/MultiSelectPanel.vue +0 -57
  109. package/src/editor/composer/components/NumInput.vue +0 -121
  110. package/src/editor/composer/components/OverrideLabel.vue +0 -21
  111. package/src/editor/composer/components/PropToggle.vue +0 -42
  112. package/src/editor/composer/components/PropsSection.vue +0 -51
  113. package/src/editor/composer/components/SegControl.vue +0 -30
  114. package/src/editor/composer/components/SizeInput.vue +0 -62
  115. package/src/editor/composer/composer.ts +0 -220
  116. package/src/editor/composer/lib/abs.ts +0 -78
  117. package/src/editor/composer/lib/canvas-drop-target.ts +0 -103
  118. package/src/editor/composer/lib/canvas-drop.ts +0 -78
  119. package/src/editor/composer/lib/canvas-overlay.ts +0 -168
  120. package/src/editor/composer/lib/canvas-view.ts +0 -75
  121. package/src/editor/composer/lib/canvas.ts +0 -65
  122. package/src/editor/composer/lib/class-css.ts +0 -103
  123. package/src/editor/composer/lib/class-groups.ts +0 -86
  124. package/src/editor/composer/lib/clipboard.ts +0 -25
  125. package/src/editor/composer/lib/composer-history.ts +0 -81
  126. package/src/editor/composer/lib/composer-store.ts +0 -546
  127. package/src/editor/composer/lib/design-tokens.ts +0 -46
  128. package/src/editor/composer/lib/elements-meta.ts +0 -123
  129. package/src/editor/composer/lib/inspector-props.ts +0 -76
  130. package/src/editor/composer/lib/keys.ts +0 -14
  131. package/src/editor/composer/lib/marquee.ts +0 -26
  132. package/src/editor/composer/lib/normalize-template.ts +0 -51
  133. package/src/editor/composer/lib/padding.ts +0 -71
  134. package/src/editor/composer/lib/scrub.ts +0 -9
  135. package/src/editor/composer/lib/use-insert-dnd.ts +0 -131
  136. package/src/editor/composer/lib/use-layer-dnd.ts +0 -99
  137. package/src/editor/composer/styles/composer.scss +0 -1657
  138. package/src/editor/dialogs/ConfirmDialog.vue +0 -48
  139. package/src/editor/dialogs/DataDialog.vue +0 -16
  140. package/src/editor/dialogs/ImageCropDialog.vue +0 -360
  141. package/src/editor/dialogs/ImagePickerDialog.vue +0 -219
  142. package/src/editor/dialogs/PageFormDialog.vue +0 -127
  143. package/src/editor/dialogs/PagesDialog.vue +0 -800
  144. package/src/editor/dialogs/QuickSwitcher.vue +0 -208
  145. package/src/editor/dialogs/RichTextDialog.vue +0 -38
  146. package/src/editor/editor.ts +0 -247
  147. package/src/editor/fields/FieldControl.vue +0 -31
  148. package/src/editor/fields/builtin.ts +0 -29
  149. package/src/editor/fields/editors/BooleanField.vue +0 -12
  150. package/src/editor/fields/editors/ColorField.vue +0 -12
  151. package/src/editor/fields/editors/EnumField.vue +0 -33
  152. package/src/editor/fields/editors/ImageField.vue +0 -178
  153. package/src/editor/fields/editors/MultiselectField.vue +0 -35
  154. package/src/editor/fields/editors/NumberField.vue +0 -12
  155. package/src/editor/fields/editors/RichTextField.vue +0 -59
  156. package/src/editor/fields/editors/SmartLinkField.vue +0 -457
  157. package/src/editor/fields/editors/StringField.vue +0 -13
  158. package/src/editor/fields/editors/TextField.vue +0 -15
  159. package/src/editor/fields/registry.ts +0 -52
  160. package/src/editor/fields/richtext/RichCodeWidget.vue +0 -120
  161. package/src/editor/fields/richtext/RichImageWidget.vue +0 -129
  162. package/src/editor/fields/richtext/RichTableWidget.vue +0 -96
  163. package/src/editor/fields/richtext/RichTextEditor.vue +0 -792
  164. package/src/editor/fields/richtext/WidgetBoundary.vue +0 -45
  165. package/src/editor/fields/richtext/config.ts +0 -83
  166. package/src/editor/fields/richtext/keys.ts +0 -12
  167. package/src/editor/fields/richtext/widgets.ts +0 -117
  168. package/src/editor/icons/align-center.svg +0 -1
  169. package/src/editor/icons/align-left.svg +0 -1
  170. package/src/editor/icons/align-right.svg +0 -1
  171. package/src/editor/icons/arrow-down.svg +0 -1
  172. package/src/editor/icons/arrow-up.svg +0 -1
  173. package/src/editor/icons/bold.svg +0 -1
  174. package/src/editor/icons/bolt.svg +0 -1
  175. package/src/editor/icons/book.svg +0 -1
  176. package/src/editor/icons/button.svg +0 -1
  177. package/src/editor/icons/check.svg +0 -1
  178. package/src/editor/icons/chevron-down.svg +0 -1
  179. package/src/editor/icons/close.svg +0 -1
  180. package/src/editor/icons/code.svg +0 -1
  181. package/src/editor/icons/column.svg +0 -1
  182. package/src/editor/icons/component.svg +0 -1
  183. package/src/editor/icons/copy.svg +0 -1
  184. package/src/editor/icons/corner.svg +0 -1
  185. package/src/editor/icons/desktop.svg +0 -1
  186. package/src/editor/icons/ellipsis.svg +0 -1
  187. package/src/editor/icons/fill.svg +0 -1
  188. package/src/editor/icons/folder.svg +0 -1
  189. package/src/editor/icons/frame.svg +0 -1
  190. package/src/editor/icons/gap.svg +0 -1
  191. package/src/editor/icons/globe.svg +0 -1
  192. package/src/editor/icons/grip.svg +0 -1
  193. package/src/editor/icons/height.svg +0 -1
  194. package/src/editor/icons/image.svg +0 -1
  195. package/src/editor/icons/info.svg +0 -1
  196. package/src/editor/icons/italic.svg +0 -1
  197. package/src/editor/icons/limits.svg +0 -1
  198. package/src/editor/icons/margin.svg +0 -1
  199. package/src/editor/icons/menu.svg +0 -1
  200. package/src/editor/icons/mobile.svg +0 -1
  201. package/src/editor/icons/named-slots.svg +0 -1
  202. package/src/editor/icons/pencil.svg +0 -1
  203. package/src/editor/icons/plus.svg +0 -1
  204. package/src/editor/icons/position.svg +0 -1
  205. package/src/editor/icons/redo.svg +0 -1
  206. package/src/editor/icons/row.svg +0 -1
  207. package/src/editor/icons/search.svg +0 -1
  208. package/src/editor/icons/sides.svg +0 -1
  209. package/src/editor/icons/sliders.svg +0 -1
  210. package/src/editor/icons/slot.svg +0 -1
  211. package/src/editor/icons/strikethrough.svg +0 -1
  212. package/src/editor/icons/table.svg +0 -4
  213. package/src/editor/icons/tablet.svg +0 -1
  214. package/src/editor/icons/text.svg +0 -1
  215. package/src/editor/icons/trash.svg +0 -1
  216. package/src/editor/icons/underline.svg +0 -1
  217. package/src/editor/icons/undo.svg +0 -1
  218. package/src/editor/icons/upload.svg +0 -1
  219. package/src/editor/icons/width.svg +0 -1
  220. package/src/editor/icons/wrap.svg +0 -1
  221. package/src/editor/lib/block-meta.ts +0 -98
  222. package/src/editor/lib/block-preview.ts +0 -84
  223. package/src/editor/lib/bridge.ts +0 -90
  224. package/src/editor/lib/content-tree.ts +0 -186
  225. package/src/editor/lib/context-menu.ts +0 -52
  226. package/src/editor/lib/drag-controller.ts +0 -217
  227. package/src/editor/lib/history.ts +0 -91
  228. package/src/editor/lib/image-crop.ts +0 -174
  229. package/src/editor/lib/image-size.ts +0 -154
  230. package/src/editor/lib/keyboard.ts +0 -29
  231. package/src/editor/lib/navigation.ts +0 -31
  232. package/src/editor/lib/page-list.ts +0 -89
  233. package/src/editor/lib/panel-dock.ts +0 -97
  234. package/src/editor/lib/recents.ts +0 -31
  235. package/src/editor/lib/save-queue.ts +0 -156
  236. package/src/editor/lib/shortcuts.ts +0 -54
  237. package/src/editor/lib/store.ts +0 -331
  238. package/src/editor/lib/tree-ui.ts +0 -33
  239. package/src/editor/lib/types.ts +0 -24
  240. package/src/editor/lib/typography.ts +0 -80
  241. package/src/editor/lib/use-block-frames.ts +0 -125
  242. package/src/editor/props-panel/ArrayField.vue +0 -106
  243. package/src/editor/props-panel/SchemaField.vue +0 -45
  244. package/src/editor/props-panel/SchemaForm.vue +0 -120
  245. package/src/editor/props-panel/VCollapse.vue +0 -28
  246. package/src/editor/props-panel/humanize.ts +0 -8
  247. package/src/editor/props-panel/use-reorder.ts +0 -90
  248. package/src/editor/styles/_variables.scss +0 -54
  249. package/src/editor/styles/editor.scss +0 -915
  250. package/src/editor/ui/VDialog.vue +0 -95
  251. package/src/editor/ui/VDialogHost.vue +0 -116
  252. package/src/editor/ui/dialog.ts +0 -47
  253. package/src/editor/widget-api.ts +0 -49
  254. package/src/elements/elements.scss +0 -69
  255. package/src/elements/emit-css.ts +0 -153
  256. package/src/elements/index.ts +0 -135
  257. package/src/elements/style-vars.ts +0 -230
  258. package/src/index.ts +0 -1
  259. package/src/svg-plugin.ts +0 -108
  260. package/src/virtual-modules.d.ts +0 -23
  261. package/src/vite/collect-blocks.ts +0 -99
  262. package/src/vite/collect-components.ts +0 -58
  263. package/src/vite/collect-composed.ts +0 -49
  264. package/src/vite/collect-widgets.ts +0 -54
  265. package/src/vite/dev/assets-store.ts +0 -217
  266. package/src/vite/dev/composed-store.ts +0 -99
  267. package/src/vite/dev/composer.ts +0 -46
  268. package/src/vite/dev/data-store.ts +0 -147
  269. package/src/vite/dev/fs-utils.ts +0 -41
  270. package/src/vite/dev/image-preview.ts +0 -102
  271. package/src/vite/dev/middleware.ts +0 -395
  272. package/src/vite/dev/page-state.ts +0 -167
  273. package/src/vite/dev/pages-store.ts +0 -583
  274. package/src/vite/dev/preview.ts +0 -69
  275. package/src/vite/dev/query-dev.ts +0 -33
  276. package/src/vite/entries.ts +0 -196
  277. package/src/vite/generate-pages.ts +0 -51
  278. package/src/vite/index.ts +0 -17
  279. package/src/vite/plugin.ts +0 -681
  280. package/src/vite/read-breakpoints.ts +0 -47
  281. package/src/vite/rich-text-codec.ts +0 -48
  282. package/src/vue-shim.d.ts +0 -14
package/README.md CHANGED
@@ -1,28 +1,61 @@
1
1
  # mechanica
2
2
 
3
- Build Vue 3 websites with a visual block editor. Authors write Vue SFC **blocks**, a visual editor arranges them into pages, and the result renders server-side and ships statically or via a backend.
3
+ Build Vue 3 websites with a visual block editor. You write **blocks** — ordinary Vue single-file components with a typed props schema — and arrange them into **pages** in an editor that runs on top of your live site. Pages are stored as readable Markdown files in your project, and `mechanica export` renders every page to static HTML.
4
4
 
5
- Built for **Vite 8 / Vue 3.5 / Bun**.
5
+ - **Blocks are just Vue.** One compile-time macro, `defineBlock`, declares a block's props; everything else is plain Vue 3.
6
+ - **The editor is your site.** In development the editor overlays the real page: add blocks from a palette, edit props in forms, drag to reorder, undo/redo, manage pages.
7
+ - **Pages are files.** `.mech/pages/about.page.md` is Markdown with YAML props — editable in the editor, by hand, or by an AI assistant, with clean git diffs.
8
+ - **Static output, SEO included.** Per-page HTML and code splitting, sitemap, canonical URLs, hreflang and JSON-LD.
6
9
 
7
- ## Install
10
+ Built on Vite 8 and Vue 3.5. Runs on Node.js 20.19+ or 22.12+ (npm, pnpm, yarn) or [Bun](https://bun.sh).
8
11
 
9
- Works with plain Node ≥ 20.19 (npm/pnpm/yarn) or [Bun](https://bun.sh) — Bun installs run the TypeScript source directly, Node uses the compiled build. The fastest start is the scaffolder:
12
+ ## Quick start
10
13
 
11
14
  ```bash
12
15
  npm create mechanica@latest my-site
13
- # or: bun create mechanica my-site
16
+ cd my-site
17
+ npm install
18
+ npm run dev
14
19
  ```
15
20
 
16
- Or add it to an existing Vite + Vue project:
21
+ Open the URL Vite prints (`http://127.0.0.1:5173/`). The starter page appears with the editor on top of it: click a block to edit its props, add blocks from the palette, and open the page browser to create more pages. Every edit is saved into `.mech/` as you go.
22
+
23
+ When you're ready to ship:
17
24
 
18
25
  ```bash
19
- npm install mechanica
26
+ npm run export
20
27
  ```
21
28
 
22
- ## Quick start
29
+ `export/` now holds a static site: an `index.html` per page, hashed JS and CSS in `assets/`, your uploads in `media/`. It's built to be served from the root of a domain; upload it to any static host, or look at it locally with `npx vite preview --outDir export`.
30
+
31
+ Prefer Bun? `bun create mechanica my-site`, then `bun install` and `bun run dev`.
32
+
33
+ ## A Mechanica project
34
+
35
+ ```text
36
+ index.html the document shell; {{ }} placeholders fill <head> per page
37
+ vite.config.ts plugins: [mechanica(), vue()]
38
+ src/main.ts export default defineMechanicaApp({ root: App })
39
+ src/App.vue site-wide chrome around <Content />
40
+ src/blocks/*.vue blocks: every file appears in the editor's palette
41
+ src/data/*.ts shared data entries (defineData)
42
+ src/composer.ts optional: your design system for the Block Composer
43
+ .mech/pages/ the pages, one .page.md file each
44
+ .mech/assets/ uploaded files
45
+ .mech/data.json site-wide data values
46
+ ```
47
+
48
+ `.mech/` is content — commit it. Scaffolded projects also include a `CLAUDE.md` that teaches AI coding assistants to author blocks and pages in this format.
49
+
50
+ ### Adding Mechanica to an existing Vite + Vue project
51
+
52
+ ```bash
53
+ npm install mechanica vue
54
+ npm install -D vite @vitejs/plugin-vue
55
+ ```
23
56
 
24
- **`vite.config.ts`**
25
57
  ```ts
58
+ // vite.config.ts
26
59
  import { defineConfig } from 'vite'
27
60
  import vue from '@vitejs/plugin-vue'
28
61
  import { mechanica } from 'mechanica/plugin'
@@ -32,92 +65,299 @@ export default defineConfig({
32
65
  })
33
66
  ```
34
67
 
35
- **`index.html`** — just a mount point; the plugin injects the entry and editor:
36
- ```html
37
- <body><div id="app"></div></body>
38
- ```
39
-
40
- **`src/main.ts`** — export an app definition (no `createApp`/`mount` yourself):
41
68
  ```ts
69
+ // src/main.ts — export an app definition; don't call createApp()/mount() yourself
42
70
  import { defineMechanicaApp } from 'mechanica'
43
71
  import App from './App.vue'
44
72
 
45
73
  export default defineMechanicaApp({ root: App })
46
74
  ```
47
75
 
48
- **`src/App.vue`** — render the page content somewhere:
49
76
  ```vue
77
+ <!-- src/App.vue -->
50
78
  <template>
51
79
  <Content />
52
80
  </template>
81
+
53
82
  <script setup lang="ts">
54
83
  import { Content } from 'mechanica'
55
84
  </script>
56
85
  ```
57
86
 
58
- **`src/blocks/Hero.vue`** — a block. `defineBlock` is a global macro (no import):
87
+ `index.html` needs only a mount point, `<div id="app"></div>`. Leave out the usual `<script type="module" src="/src/main.ts">`: the plugin injects the entry (and, in dev, the editor) itself.
88
+
89
+ ## Blocks
90
+
91
+ A block is a `.vue` file in `src/blocks/`. Its `<script setup>` calls the global `defineBlock` macro (no import needed), which declares the editable props and returns them typed:
92
+
59
93
  ```vue
94
+ <!-- src/blocks/Hero.vue -->
60
95
  <template>
61
- <section>
96
+ <section class="hero">
62
97
  <h1>{{ props.title }}</h1>
63
- <p>{{ props.subtitle }}</p>
98
+ <p v-if="props.subtitle">{{ props.subtitle }}</p>
99
+ <Image :image="props.photo" eager />
100
+ <Link v-if="props.cta?.url" :to="props.cta">{{ props.cta.title || 'Read more' }}</Link>
64
101
  </section>
65
102
  </template>
103
+
66
104
  <script setup lang="ts">
105
+ import { Image, Link } from 'mechanica'
106
+
67
107
  const props = defineBlock({
68
108
  name: 'Hero',
69
109
  category: 'Content',
70
110
  props: {
71
- title: { type: 'string', default: 'Hello' },
111
+ title: { type: 'string', label: 'Headline', default: 'Hello' },
72
112
  subtitle: 'text',
113
+ photo: 'image',
114
+ cta: 'smartLink',
73
115
  },
116
+ // Example values for the palette preview and `mechanica shot`
117
+ previewData: { title: 'Build sites visually', subtitle: 'Blocks are plain Vue components.' },
74
118
  })
75
119
  </script>
76
120
  ```
77
121
 
78
- Run `vite` and open the page — the editor overlay lets you add Hero blocks and edit their props live.
122
+ The block's id is its kebab-cased file name (`Hero.vue` → `hero`). Props use [compact-json-schema](https://www.npmjs.com/package/compact-json-schema): the JSON types (`string`, `number`, `boolean`, `object`, `array`) plus Mechanica's field types `text`, `richText`, `image`, `color`, `smartLink` and `multiselect`. The object form takes `default`, `label`, `description`, `placeholder` and `enum` (rendered as a select).
79
123
 
80
- ## Authoring API
124
+ Other `defineBlock` options: `icon`, `description` and `order` for the palette; `hidden` and `devOnly`; `standalone`, `folders` and `layouts` to control where the palette offers the block; `chunk` for code splitting; `slots` for named slots. A block whose template contains `<slot />` becomes a container, and other blocks nest inside it in the editor.
125
+
126
+ Give every block meaningful `previewData`: it drives the palette's hover preview, the standalone preview route and `mechanica shot`. A `$slots` key fills slots with child blocks, e.g. `$slots: { default: [{ blockId: 'card', data: { title: 'One' } }] }`.
127
+
128
+ ## Pages
129
+
130
+ Each page is a `.page.md` file under `.mech/pages/`, and its path is the URL: `about.page.md` → `/about`, `blog/index.page.md` → `/blog`, `index.page.md` → `/`.
131
+
132
+ ```markdown
133
+ ---
134
+ name: About
135
+ data:
136
+ head:
137
+ title: About us
138
+ ---
139
+
140
+ ::: hero
141
+ title: About us
142
+ @subtitle
143
+ We build fast websites
144
+ for people who care about the details.
145
+ :::
146
+ ```
81
147
 
82
- ### `defineBlock(descriptor)` — the one macro
83
- No import; used inside a block's `<script setup>`. Fields use [compact-json-schema](https://www.npmjs.com/package/compact-json-schema). Built-in field types: `string`, `text`, `number`, `boolean`, `color`, `image`, `file`, `smartLink`, `multiselect`, `richText`, plus `array`/`object`. Object-form fields take editor metadata (`label`, `description`, `default`, `placeholder`).
148
+ - The YAML frontmatter holds the editor label (`name`), page-scoped data (`data`) and optional `layout`, `draft: true` (hidden from listings and the export) and `meta` (`noindex: true`, `lastmod`).
149
+ - Each block is a fence: `::: <blockId>`, then its props as YAML, then `@field` regions for long text, closed by `:::`. A region fills a `text` prop with its text as written, and a `richText` prop with its Markdown turned into rich text. Blocks nest by placing fences inside a container's fence.
150
+ - Props left out take their schema defaults, so a hand-written page renders exactly like one made in the editor.
151
+
152
+ With `npm run dev` running, edits you make to these files show up in the editor live, and the editor won't overwrite a file that changed on disk underneath it.
153
+
154
+ ## Shared data and `<head>`
155
+
156
+ Data that isn't a block prop (a site header, page titles, a post date) is declared with `defineData`, an ordinary imported function that returns a hook:
84
157
 
85
158
  ```ts
86
- defineBlock({
87
- name, category, icon, description, order, hidden,
88
- props: { /* schema */ },
89
- slots: ['default'], // auto-detected from <slot> if omitted
159
+ // src/data/head.ts
160
+ import { defineData } from 'mechanica'
161
+
162
+ export const useHead = defineData({
163
+ id: 'head',
164
+ title: 'Page head',
165
+ props: {
166
+ title: { type: 'string', default: 'My site' },
167
+ description: 'string',
168
+ },
90
169
  })
91
170
  ```
92
171
 
93
- ### `defineData({ id, scope, props })` — shared data
94
- An **imported function** (not a macro), used in a standalone module; returns a hook:
172
+ Call `useHead()` in any component to read the values. An entry registers when its module is imported, usually by the block or component that uses it. Data that only `index.html` reads, like `head`, still needs importing somewhere (the starter's `App.vue` imports `./data/head`): unregistered entries are left out of the Page data window, page state and `{{ }}` placeholders.
173
+
174
+ Values are edited in the editor's **Page data** window, where each entry can be set for one page, for a folder of pages, or site-wide; a page value overrides its folder's, which overrides the site's. They're stored in the page file, `.mech/folders.json` and `.mech/data.json` respectively. Pass `folder: 'blog'` to offer an entry only on pages under that folder, and `localized: true` to translate its site/folder value per language.
175
+
176
+ `index.html` is templated with the same data, in dev and in the export:
177
+
178
+ ```html
179
+ <title>{{ head.title }}</title>
180
+ <meta name="description" content="{{ head.description }}" />
181
+ ```
182
+
183
+ `{{ page.path }}`, `{{ site.url }}` and `{{ site.name }}` are available too, and the triple-brace form `{{{ expr }}}` emits raw JSON for `<script type="application/ld+json">` blocks.
184
+
185
+ ## Layouts and whole-page blocks
186
+
187
+ Give pages different shells by declaring named layouts. Each layout component renders `<Content />` inside its own chrome, and the root renders `<Layout />`:
188
+
95
189
  ```ts
96
- // data/header.ts
97
- import { defineData } from 'mechanica'
98
- export const useHeader = defineData({ id: 'header', scope: 'site', props: { logo: 'image' } })
99
- // in a component: const header = useHeader()
190
+ // src/main.ts
191
+ export default defineMechanicaApp({
192
+ root: App, // its template: <Layout />
193
+ layouts: { site: SiteLayout, bare: BareLayout }, // the first entry is the default
194
+ })
100
195
  ```
101
196
 
102
- ### Components & composables
103
- `<Content>`, `<Link :to="..." />` (string path or `smartLink` object), `useRouter`, `useRoute`, `usePages`, `useFetch`, `usePageData`.
197
+ A page picks its layout with `layout: bare` in its frontmatter, or in the editor's **Page setup** pane. Pages without one get the first layout, and `useLayout().name` tells a component which layout is active.
198
+
199
+ Blocks with `standalone: true` are whole pages in one block, such as a 404 or a contact form. The palette offers them only on empty pages ("Start this page"), so they don't clutter it elsewhere.
200
+
201
+ ## Images
202
+
203
+ Render an `image` prop with the `<Image>` component. It outputs `alt`, intrinsic `width`/`height` (no layout shift), native lazy loading, and a blurred inline preview until the real image arrives. Add `eager` to above-the-fold images.
204
+
205
+ The editor captures dimensions and the blur-up preview on upload. For images added by hand, `mechanica images` generates them headlessly; it needs `sharp` in your project (`npm install -D sharp`), and the export fills in anything missing when `sharp` is installed.
206
+
207
+ Editors can set a focal point and crop from the image field's **Position / Crop** action. Cropping is opt-in per field, via `crop: { width: 1200, height: 630 }` (fixed output size), `crop: { aspect: 16 / 9 }` or `crop: true` (free) on its schema. Crops are non-destructive: the original stays, and a derived file is rendered in its place. For images painted as a CSS background, `imagePosition(image)` turns the focal point into a `background-position` value.
208
+
209
+ ## Links and navigation
210
+
211
+ `<Link :to="…">` takes a path string or a `smartLink` value. Internal links navigate client-side without a full reload, get an `is-active` class on the current page, and add the language prefix on multi-language sites. `useRouter().push(path)` navigates from code, and `useRoute().path` is the current path.
212
+
213
+ ## Listing pages and pagination
214
+
215
+ Blocks can query the site's pages. Queries run at build time and their results are baked into the page, so there's no runtime API to host:
104
216
 
105
- ### Custom field types
106
217
  ```ts
107
- import { defineFieldType, registerFields } from 'mechanica/editor'
108
- registerFields([
109
- defineFieldType({ name: 'video', schema: { type: 'object', properties: { src: 'string' } }, editor: VideoField }),
110
- ])
218
+ import { usePages, usePagination } from 'mechanica'
219
+ import { usePostMeta } from '../data/post-meta' // defineData({ id: 'postMeta', props: { date: 'string' } })
220
+
221
+ const latest = usePages({ folderName: 'blog', data: [usePostMeta], sort: { by: 'postMeta.date', dir: 'desc' }, limit: 3 })
222
+
223
+ const blog = usePagination({ folderName: 'blog', data: [usePostMeta], pageSize: 10 })
224
+ // blog.items, blog.page, blog.pageCount, blog.prevPath, blog.nextPath, blog.pathFor(n)
111
225
  ```
112
226
 
113
- ## CLI
227
+ A paginated page exports as real pages, `/blog`, `/blog/2` and so on, each with its own slice. `useFetch({ url })` fetches external JSON the same way: on the server, baked into the output.
228
+
229
+ ## Generated pages
230
+
231
+ To produce many pages from an external source (a product catalog, API docs) without a `.page.md` file each, give the plugin page providers:
232
+
233
+ ```ts
234
+ // vite.config.ts
235
+ import { mechanica, type PageProvider } from 'mechanica/plugin'
236
+
237
+ const products: PageProvider = async () => {
238
+ const items = await fetch('https://api.example.com/products').then((res) => res.json())
239
+ return items.map((item) => ({
240
+ path: `/shop/${item.slug}`,
241
+ content: [{ id: item.slug, blockId: 'product', data: { title: item.title } }],
242
+ data: { head: { title: item.title } },
243
+ }))
244
+ }
245
+
246
+ // plugins: [mechanica({ generatePages: [products] }), vue()]
247
+ ```
248
+
249
+ Providers run when the dev server starts and at build. Generated pages render through the normal pipeline, appear in the sitemap and are read-only in the editor.
250
+
251
+ ## Multi-language sites
252
+
253
+ ```ts
254
+ mechanica({ locales: ['en', 'de'] }) // the first locale is the default
255
+ ```
256
+
257
+ The default language is served at unprefixed URLs, the others under `/de/…`. A translation is a sibling file, `about@de.page.md`, created from the editor's language switcher. It stores only the fields that differ, so images, links and anything else left untranslated keep following the default-language page.
258
+
259
+ `<Link>` keeps visitors in their language automatically. For a language switcher, `useLocale()` returns `locale`, `locales` and `localePath()`, and `<Link :to="path" :locale="code">` targets a specific language. The export writes each translation to its own URL (pages without a translation are skipped for that language), and with `siteUrl` set it adds `hreflang` alternates to the page head and the sitemap.
260
+
261
+ ## Rich text and widgets
262
+
263
+ A `richText` prop is edited with a WYSIWYG editor for headings, lists, bold/italic/underline, images, code, callouts and tables. It switches to a Markdown view, where you can also write links as `[text](url)`, and it's stored as Markdown inside the page file. Its value is a [vuewrite](https://www.npmjs.com/package/vuewrite) document; render it with vuewrite's `TextViewer`, as the starter's `RichTextView.vue` does.
264
+
265
+ You can add your own content widgets to the editor's Insert menu: put a module in `src/widgets/` that default-exports `defineWidget({ type, title, icon, create, editor })` from `mechanica/widgets`, then render the widget through a `#<type>` slot on your `TextViewer`.
266
+
267
+ ## Block Composer
268
+
269
+ Designers can build new blocks without code. The palette's **New block** button opens the composer: frames with auto layout, text, images and your own components, per-breakpoint styles, and repeated items. Any value can be bound to a prop, so each placed copy stays editable. Composed blocks are saved as `.mech/blocks/<id>.block.yml`, appear in the palette under "Site blocks", and render, preview and export like any other block.
270
+
271
+ `src/composer.ts` hands the composer your design system:
272
+
273
+ ```ts
274
+ import { defineComposer } from 'mechanica'
275
+ import UiButton from './components/UiButton.vue'
276
+
277
+ export default defineComposer({
278
+ components: {
279
+ button: { component: UiButton, name: 'Button', props: { label: { type: 'string', default: 'Button' } } },
280
+ },
281
+ classes: { container: { title: 'Container', on: 'frame' } }, // your CSS classes, offered as element styles
282
+ breakpoints: { md: 1024, sm: 640 },
283
+ })
284
+ ```
285
+
286
+ ## SEO
287
+
288
+ Set `siteUrl` on the plugin, or pass `--site-url` to `mechanica export`, and the export adds what nobody should write by hand: canonical and `og:url` tags, absolute social-image URLs, `rel=prev/next` on paginated pages, BreadcrumbList JSON-LD from the folder structure, `sitemap.xml` and `robots.txt` (a `public/robots.txt` of your own takes precedence). Add `siteName` for WebSite JSON-LD on the home page. Tags already in your `index.html` are never duplicated. `meta: { noindex: true }` in a page's frontmatter keeps it out of search engines and the sitemap.
289
+
290
+ Every export also warns about missing titles and descriptions, pages without exactly one `<h1>`, images without `alt`, duplicate titles, and internal links to pages that don't exist. A page at `/404` is also written as `404.html`, which most static hosts serve for unknown URLs.
291
+
292
+ ## Code splitting
293
+
294
+ By default all blocks share one `blocks` chunk, cached across pages. Mark heavy, rarely used blocks with `chunk: 'charts'` in `defineBlock` to split them into their own chunk, or set the plugin's `blockChunks: 'per-block'`. The export adds per-page `<link rel="modulepreload">` and stylesheet tags, so each page loads exactly the block code it uses.
295
+
296
+ ## Checking your work visually
114
297
 
115
298
  ```bash
116
- mechanica build # client + SSR bundles → dist/
117
- mechanica export # statically render every .mech page → export/
118
- mechanica shot <blockId | /page/path> # headless screenshot of a block or page
119
- mechanica thumbs [--blocks] # regenerate page / block thumbnails
120
- mechanica push --key <apiKey> --host <url> # upload dist/ to a backend
299
+ npx mechanica shot hero # screenshot one block, from its previewData
300
+ npx mechanica shot /about --width 1440,390 # a whole page, at two widths
301
+ npx mechanica thumbs # page thumbnails for the editor's page browser
302
+ npx mechanica thumbs --blocks # block thumbnails for the palette
121
303
  ```
122
304
 
123
- Page content lives in `.mech/pages/**.page.md` — a human-readable, Markdown-centric format equally editable by the visual editor, by hand, and by AI tools (rich text persists as plain Markdown).
305
+ These drive a locally installed Chrome or Edge (or `--browser <path>`) and need Node.js 22+ or Bun. They reuse a dev server running on port 5173 and start one if there isn't. Screenshots go to `.mech/shots/`, which is handy for reviewing a block or letting an AI assistant check its own work.
306
+
307
+ ## CLI
308
+
309
+ | Command | What it does |
310
+ | --- | --- |
311
+ | `mechanica export` | Build, then render every page to static HTML in `export/`. Flags: `--site-url`, `--site-name`, `--assets-url` (serve `/assets` and `/media` from a CDN) |
312
+ | `mechanica build` | Build the client and server bundles into `dist/` (`export` runs this for you) |
313
+ | `mechanica shot <blockId \| /path>` | Screenshot a block or a page. Flags: `--width`, `--data`, `--out`, `--full`, `--server`, `--browser` |
314
+ | `mechanica thumbs [--blocks]` | Regenerate page or block thumbnails for the editor |
315
+ | `mechanica images` | Generate image dimensions and blur-up previews for `.mech/assets` (needs `sharp`; `--force` redoes all) |
316
+ | `mechanica push` | Experimental: upload `dist/` to a Mechanica hosting backend |
317
+
318
+ ## Plugin options
319
+
320
+ `mechanica(options)` from `mechanica/plugin`; every option is optional.
321
+
322
+ | Option | Default | |
323
+ | --- | --- | --- |
324
+ | `siteUrl`, `siteName` | none | Public origin and name: `{{ site.* }}` templating and SEO output at export |
325
+ | `locales` | none | `['en', 'de']` or `{ default, all, labels }` turns on multi-language pages |
326
+ | `generatePages` | none | Page providers (see above) |
327
+ | `blockChunks` | `'bundled'` | `'per-block'` gives each block its own chunk |
328
+ | `assetsUrl` | none | CDN origin for exported assets. Set Vite's `base` to the same URL |
329
+ | `entry` | `'src/main.ts'` | Module exporting `defineMechanicaApp(…)` |
330
+ | `mount` | `'#app'` | Mount selector |
331
+ | `blocksDir` | `'src/blocks'` | Where blocks are collected from |
332
+ | `widgetsDir` | `'src/widgets'` | Where rich-text widgets are collected from |
333
+ | `composerFile` | `'src/composer.ts'` | The Block Composer manifest |
334
+ | `mechDir` | `'.mech'` | Content directory |
335
+
336
+ ## API
337
+
338
+ From `mechanica`:
339
+
340
+ - **App and data:** `defineMechanicaApp`, `defineData`, `defineComposer`
341
+ - **Components:** `<Content>`, `<Layout>`, `<Link>`, `<Image>`
342
+ - **Composables:** `usePages`, `usePagination`, `useFetch`, `useLocale`, `useLayout`, `usePageData` (the current page's path, `meta`, layout and locale), `useRouter`, `useRoute`
343
+ - **Helpers:** `imagePosition`
344
+
345
+ From `mechanica/plugin`: `mechanica` and the `PageProvider` type. From `mechanica/widgets`: `defineWidget` and `useWidgetServices` (uploads and the image picker inside widget editors). `mechanica/editor` and `mechanica/composer` are loaded by the plugin in development; you don't import them.
346
+
347
+ ## Upgrading from 1.x
348
+
349
+ Mechanica 2 is a rewrite, and a 1.x project has to be ported by hand; there is no automatic migration. What changes:
350
+
351
+ - Vite 8 and `@vitejs/plugin-vue` 6 are required.
352
+ - `src/main.ts` exports `defineMechanicaApp({ root: App })` instead of calling `createSSRApp(App).use(createMechanica()).mount('#app')`, and `index.html` no longer loads it with a `<script>` tag.
353
+ - The plugin is a named import, `import { mechanica } from 'mechanica/plugin'`, and its `entrySsr` option is now `entry`.
354
+ - In `defineBlock`, `group` is now `category` and a field's label is `label` (it was `name`). Block ids default to the kebab-cased file name (`Headline.vue` → `headline`); set `id` to keep an old one.
355
+ - Pages are `.page.md` files instead of `.mech/pages/**.json`, and JSON pages are ignored. Uploaded files in `.mech/assets/` keep working.
356
+ - `RouterLink` and `SmartLink` are replaced by `<Link>` (whose active class is now `is-active`), and `useQuery` by `usePages`, `usePagination` and `useFetch`.
357
+ - `{{ }}` placeholders in `index.html` are now HTML-escaped.
358
+
359
+ 1.x remains installable as `mechanica@1`.
360
+
361
+ ## License
362
+
363
+ MIT
package/bin/mechanica.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // Under Bun, run the TS source directly (monorepo dev, Bun-native installs —
3
- // always live, no build needed). Under Node, use the compiled CLI from dist/.
4
- const entry = process.versions.bun ? '../src/cli/cli.ts' : '../dist/cli.js'
2
+ // In the repo checkout (src/ present) under Bun, run the TS source directly —
3
+ // monorepo dev stays live with no build. Published tarballs ship dist/ only,
4
+ // so installed packages always run the compiled CLI, under Node and Bun alike.
5
+ import { existsSync } from 'node:fs'
6
+
7
+ const srcEntry = new URL('../src/cli/cli.ts', import.meta.url)
8
+ const entry = process.versions.bun && existsSync(srcEntry) ? '../src/cli/cli.ts' : '../dist/cli.js'
5
9
 
6
10
  const { run } = await import(entry)
7
11
 
@@ -27,6 +27,8 @@ var contents = {
27
27
  "corner": `<path d="M5 20v-9a6 6 0 0 1 6-6h9"/>`,
28
28
  "desktop": `<rect x="3" y="4.5" width="18" height="12.5" rx="2"/><path d="M12 17v3"/><path d="M8.5 20.5h7"/>`,
29
29
  "ellipsis": `<circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/>`,
30
+ "eye-off": `<path d="M4 4l16 16"/><path d="M10.6 6c.46-.06.93-.1 1.4-.1 6 0 9.5 6.1 9.5 6.1a17.3 17.3 0 0 1-2.6 3.3"/><path d="M6.4 7.5A16.6 16.6 0 0 0 2.5 12s3.5 6.1 9.5 6.1c1.2 0 2.3-.24 3.3-.63"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/>`,
31
+ "eye": `<path d="M2.5 12s3.5-6.5 9.5-6.5S21.5 12 21.5 12s-3.5 6.5-9.5 6.5S2.5 12 2.5 12z"/><circle cx="12" cy="12" r="3"/>`,
30
32
  "fill": `<path d="M12 3.5c3.2 3.7 5.5 6.9 5.5 9.7a5.5 5.5 0 0 1-11 0c0-2.8 2.3-6 5.5-9.7z"/>`,
31
33
  "folder": `<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>`,
32
34
  "frame": `<rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 9.5h16M9.5 9.5V20"/>`,
@@ -38,6 +40,7 @@ var contents = {
38
40
  "info": `<circle cx="12" cy="12" r="9"/><line x1="12" y1="11" x2="12" y2="16"/><line x1="12" y1="8" x2="12.01" y2="8"/>`,
39
41
  "italic": `<line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>`,
40
42
  "limits": `<rect x="3" y="3" width="18" height="18" rx="2"/><rect x="8" y="8" width="8" height="8" rx="1.5" stroke-dasharray="2.5 2.5"/>`,
43
+ "link": `<path d="M10 14a5 5 0 0 0 7.1.4l3-3a5 5 0 0 0-7.1-7.1l-1.7 1.7"/><path d="M14 10a5 5 0 0 0-7.1-.4l-3 3a5 5 0 0 0 7.1 7.1l1.7-1.7"/>`,
41
44
  "margin": `<rect x="8" y="8" width="8" height="8" rx="1"/><path d="M12 2.5v3"/><path d="M12 18.5v3"/><path d="M2.5 12h3"/><path d="M18.5 12h3"/>`,
42
45
  "menu": `<path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/>`,
43
46
  "mobile": `<rect x="7" y="2.5" width="10" height="19" rx="2.2"/><path d="M10.75 18.25h2.5"/>`,
@@ -46,6 +49,7 @@ var contents = {
46
49
  "plus": `<path d="M12 5v14"/><path d="M5 12h14"/>`,
47
50
  "position": `<circle cx="12" cy="12" r="5"/><path d="M12 2.5V5"/><path d="M12 19v2.5"/><path d="M2.5 12H5"/><path d="M19 12h2.5"/>`,
48
51
  "redo": `<path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13"/>`,
52
+ "repeat": `<rect x="3" y="6" width="7" height="12" rx="1.5"/><rect x="14" y="6" width="7" height="12" rx="1.5" stroke-dasharray="2.5 2.5"/>`,
49
53
  "row": `<rect x="4" y="5" width="6.5" height="14" rx="1.6"/><rect x="13.5" y="5" width="6.5" height="14" rx="1.6"/>`,
50
54
  "search": `<circle cx="11" cy="11" r="7"/><path d="m21 21-4.35-4.35"/>`,
51
55
  "sides": `<rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6" rx="1"/>`,
@@ -230,6 +234,26 @@ var attrs = {
230
234
  "viewBox": "0 0 24 24",
231
235
  "fill": "currentColor"
232
236
  },
237
+ "eye-off": {
238
+ "width": "24",
239
+ "height": "24",
240
+ "viewBox": "0 0 24 24",
241
+ "fill": "none",
242
+ "stroke": "currentColor",
243
+ "stroke-width": "1.6",
244
+ "stroke-linecap": "round",
245
+ "stroke-linejoin": "round"
246
+ },
247
+ "eye": {
248
+ "width": "24",
249
+ "height": "24",
250
+ "viewBox": "0 0 24 24",
251
+ "fill": "none",
252
+ "stroke": "currentColor",
253
+ "stroke-width": "1.6",
254
+ "stroke-linecap": "round",
255
+ "stroke-linejoin": "round"
256
+ },
233
257
  "fill": {
234
258
  "width": "24",
235
259
  "height": "24",
@@ -329,6 +353,16 @@ var attrs = {
329
353
  "stroke-linecap": "round",
330
354
  "stroke-linejoin": "round"
331
355
  },
356
+ "link": {
357
+ "width": "24",
358
+ "height": "24",
359
+ "viewBox": "0 0 24 24",
360
+ "fill": "none",
361
+ "stroke": "currentColor",
362
+ "stroke-width": "1.6",
363
+ "stroke-linecap": "round",
364
+ "stroke-linejoin": "round"
365
+ },
332
366
  "margin": {
333
367
  "width": "24",
334
368
  "height": "24",
@@ -399,6 +433,16 @@ var attrs = {
399
433
  "stroke-linecap": "round",
400
434
  "stroke-linejoin": "round"
401
435
  },
436
+ "repeat": {
437
+ "width": "24",
438
+ "height": "24",
439
+ "viewBox": "0 0 24 24",
440
+ "fill": "none",
441
+ "stroke": "currentColor",
442
+ "stroke-width": "1.6",
443
+ "stroke-linecap": "round",
444
+ "stroke-linejoin": "round"
445
+ },
402
446
  "row": {
403
447
  "width": "24",
404
448
  "height": "24",