History Source of this version Current version
| 1 | --- | |
| 2 | title: Writing components | |
| 3 | tags: [Building] | |
| 4 | --- | |
| 5 | | |
| 6 | A wiki can make its own components: infoboxes for its characters, navboxes linking a series, stat tables, banners. Trusted editors and up write them, on the wiki's **Components** page (**Write a component**). A component is used in articles exactly like the platform's own, and a change to it shows up on every page using it. | |
| 7 | | |
| 8 | Components are written in **FSX**: HTML-like markup (if you've seen JSX or Vue templates, it will look familiar) with values in `{…}`, a few **directives** for logic, and blocks for the component's props, styles and examples. It's deliberately small, and the editor checks every line as you write it. | |
| 9 | | |
| 10 | ## A first component | |
| 11 | | |
| 12 | A component is one file with a few blocks: | |
| 13 | | |
| 14 | ```fsx | |
| 15 | <props> | |
| 16 | class: text | |
| 17 | image: media | |
| 18 | </props> | |
| 19 | | |
| 20 | <component summary="Key facts about a legend."> | |
| 21 | <aside class="legend"> | |
| 22 | <img src={$props.image} alt="" $if={$props.image}> | |
| 23 | <p class="title">{$label ?? $context.page.title}</p> | |
| 24 | <p $if={$props.class}>Class: {$props.class}</p> | |
| 25 | </aside> | |
| 26 | </component> | |
| 27 | | |
| 28 | <style> | |
| 29 | .legend { | |
| 30 | padding: var(--forge-space-4); | |
| 31 | border-radius: var(--forge-radius-md); | |
| 32 | background: var(--theme-bg-subtle); | |
| 33 | } | |
| 34 | </style> | |
| 35 | | |
| 36 | <example> | |
| 37 | ::legend[Rampart]{class=Controller} | |
| 38 | </example> | |
| 39 | ``` | |
| 40 | | |
| 41 | - **`<props>`** lists what an article can pass in, each with a type: text, markdown, number, flag, page, media, lists of those, and lists of records (rows with named fields). | |
| 42 | - **`<component>`** is the markup: a safe set of HTML tags, values in `{…}`, and directives like `$if` for logic. | |
| 43 | - **`<style>`** is the component's own CSS, kept to the component, using the theme's colours and sizes so it looks right in every theme, light or dark. | |
| 44 | - **`<example>`** shows how it's used; the editor checks every example renders. | |
| 45 | | |
| 46 | An article then writes `::legend[Rampart]{class=Controller}`, or for many props, a props block: | |
| 47 | | |
| 48 | ```forge | |
| 49 | :::legend[Rampart] | |
| 50 | --- | |
| 51 | class: Controller | |
| 52 | image: rampart-portrait | |
| 53 | --- | |
| 54 | ::: | |
| 55 | ``` | |
| 56 | | |
| 57 | ## Values | |
| 58 | | |
| 59 | Anything in `{…}` is a value, shown in the page or used in an attribute: | |
| 60 | | |
| 61 | - `$props.name`: what the article passed in. | |
| 62 | - `$label`: the text in brackets after the component's name, like `[Rampart]`. | |
| 63 | - `$children`: the text between the component's fences. | |
| 64 | - `$context.page.title` and friends: the page the component is on, and the wiki. | |
| 65 | - `$data.name`: one of the wiki's data sets (see [[Wiki Data]]). | |
| 66 | - Constants from a `<constants>` block, for fixed lists and names. | |
| 67 | | |
| 68 | **Filters** change a value: `{$props.name | upper}`, `{$props.list | join: " · "}`, `| replace`, `| sort`, `| slice`, `| contains` and more. A `page` value is a link, and `{legend.exists}` says whether that page has been written yet. | |
| 69 | | |
| 70 | ## Directives | |
| 71 | | |
| 72 | Directives are attributes starting with `$` that decide whether, and how many times, an element appears. They go on any tag, or on `<>…</>` to wrap several things without adding a tag. | |
| 73 | | |
| 74 | | Directive | What it does | | |
| 75 | | --- | --- | | |
| 76 | | `$if={…}` | Shows the element only when the value is true (empty values are false). | | |
| 77 | | `$else` | On the next element: shows it when the `$if` before was false. With its own `$if`, it's an "else if". | | |
| 78 | | `$for={item in list}` | Repeats the element for each item; `$index` counts from 0. | | |
| 79 | | `$switch={…}` with `$case` and `$default` | Shows the first child whose `$case` matches, or the `$default` one. | | |
| 80 | | |
| 81 | ```fsx | |
| 82 | <p $if={$props.image}>…</p> | |
| 83 | <p $else>No picture yet.</p> | |
| 84 | | |
| 85 | <ul> | |
| 86 | <li $for={legend in $props.legends}>{legend}</li> | |
| 87 | </ul> | |
| 88 | | |
| 89 | <$switch={$props.tier}> | |
| 90 | <span $case={4}>Legendary</span> | |
| 91 | <span $case={3}>Epic</span> | |
| 92 | <span $default>Common</span> | |
| 93 | </> | |
| 94 | ``` | |
| 95 | | |
| 96 | ## Tags and to-dos | |
| 97 | | |
| 98 | Every tag and to-do written in a component is added to **every page that uses it**. They show nothing where the component appears; they're how a component files its pages and flags what they're missing. | |
| 99 | | |
| 100 | ```fsx | |
| 101 | <tag>Weapons</tag> | |
| 102 | <tag $if={$props.class}>{$props.class}s</tag> | |
| 103 | <todo $if={!$props.image}>Add an image</todo> | |
| 104 | ``` | |
| 105 | | |
| 106 | With that in a `weapon` component, every page using it is tagged Weapons, and Shotguns if it says `class=Shotgun`. Any of them without an image appears on the wiki's to-do list under "Add an image" until someone adds one. Nobody has to tag those pages by hand, and when you change the component, every page using it is updated at once. See [[Tags and to-dos]]. | |
| 107 | | |
| 108 | ## Shared data | |
| 109 | | |
| 110 | Components can read the wiki's data sets as `$data.name`. See [[Wiki Data]]. | |
| 111 | | |
| 112 | ## The editor does the remembering | |
| 113 | | |
| 114 | Writing a component should feel like filling in a form that knows what you mean, not like debugging. The component editor understands the language as you type: | |
| 115 | | |
| 116 | :::screenshot[The component editor: its details over the source on the left, the live preview on the right]{url="apex.fanforge.wiki/c/s0ph1r/edit"} | |
| 117 |  | |
| 118 | ::: | |
| 119 | | |
| 120 | - **The same workbench as the article editor:** its own bar, **Source**, **Split** and **Preview**, and on a phone, **Write**, **Preview** and a **Details** sheet. | |
| 121 | - **Details as fields:** the name (a new component's), and its **Summary**, which edits `summary="…"` on `<component>` in the source; with how many pages use it. | |
| 122 | - **A toolbar** that writes the language's pieces for you: **Insert** (a prop, a prop's value, `$if`, `$for`, `{$children}`, a style rule, an example), and **Format**. | |
| 123 | - **Highlighting** for each part of the file: props, markup, values, logic and styles (selectors, properties, values and `var()` each in their own colour). | |
| 124 | - **Typing comforts:** finishing an opening tag writes its closing tag after the cursor, `</` finishes the tag that's open, brackets and quotes come in pairs, and Enter between a pair (`<p>` and `</p>`, `{` and `}`) puts the closing one on its own line with you indented in between. In `<style>`, `{` after a selector opens the whole rule, ready to type into. Each block of the file is tinted in turn, and the toolbar offers what fits where the cursor is. | |
| 125 | - **Completions for everything:** your props and their types, tags (and the wiki's other components), attributes, `$if` and the other logic, filters with what each does, `$context` fields, the wiki's data sets and their columns, the theme's colours and sizes, and your own classes. | |
| 126 | - **Explanations on hover:** point at a prop, a filter, a component or a theme colour to see what it is. | |
| 127 | - **Problems as you type,** with the exact place: an undeclared prop, an unknown tag, a colour that isn't from the theme, a component that would use itself, an example that doesn't render. The note for the cursor's line shows under it (an undeclared prop gets a **Declare** button), and one count in the bar opens the list. Saving waits until there are none. | |
| 128 | - **A live preview** in its own frame: each example, a playground to try props, your own Markdown, or a real page of the wiki that uses the component, in light or dark, wide or phone width. | |
| 129 | - **Warnings about pages:** before you save, the notes say which pages pass props your change no longer accepts, and **Save…** says how many pages will update. | |
| 130 | - **Format** tidies the file in one click. | |
| 131 | | |
| 132 | :::screenshot[Completions: props, filters and theme colours offered as you type]{url="apex.fanforge.wiki/c/s0ph1r/edit"} | |
| 133 |  | |
| 134 | ::: | |
| 135 | | |
| 136 | :::screenshot[The preview showing a real page that uses the component, in dark mode at phone width]{url="apex.fanforge.wiki/c/s0ph1r/edit"} | |
| 137 |  | |
| 138 | ::: | |
| 139 | | |
| 140 | :::screenshot[A problem pointed out the moment it's typed]{url="apex.fanforge.wiki/c/s0ph1r/edit"} | |
| 141 |  | |
| 142 | ::: | |
| 143 | | |
| 144 | The full language reference, with every type, tag and filter, lives with the code; it's what the editor's checks follow. |