# `MishkaCmsMigration.Targets.CmsLayout`
[🔗](https://github.com/mishka-group/mishka_cms_migration/blob/v0.0.1/lib/mishka_cms_migration/targets/cms_layout.ex#L5)

The `"menu"` items of a source: every menu of the site, classic and block
(`MishkaCmsMigration.Sources.WordPress.Navigation`), written where an admin says it goes. A menu
becomes the links of the site's layout, the core's own navigation, through the `:update` an
admin's layout form runs (`MishkaCmsCore.Runtime.Layout`), on the run's site and as the admin who
pressed Import: the admin needs the core's `admin:dashboard:runtime:layout`, and the core's own
rules decide the rest, such as that the layout's template must compile.

## Where a menu goes

Every site has the `site-main` layout (`MishkaCmsCore.Runtime.SiteLayout`), and its header is the
`site-header` component, which draws the links the layout hands it before its own
(`MishkaCmsCore.Install.StarterContent`, the `:nav` it seeds a site with). A top-level item is a
link in the bar, and one with items under it is a group, a dropdown from `lg` up and a labelled
list in the phone's menu panel, in the core's markup and classes. The core's group holds links,
not groups, so an item's own link comes first in its group and every item under it follows,
however deep, in menu order. An item with no address of its own is left out of the group.

Each menu goes where its `"menu_goes_to"` decision says (`MishkaCmsMigration.MigrationItem`'s
Decide…, kept in `decisions` across runs): Header links, which show in the top bar; Footer
links, which show beside the footer's links; A component, which can be placed on any page or
layout; or Leave out. The site's main menu (`MishkaCmsMigration.Sources.WordPress`'s `main`, the one at the
theme's primary location) is pre-selected for Header links and writes at once. Every other menu
waits for the admin's decision as `"menu_goes_to"`, as `"menu_goes_to: <menu>"` when it is a
block menu with the same links as the classic menu `<menu>` (`"same_as"`), so both are shown and
the admin leaves one out. A menu's name is never a rule.

The footer is the `site-footer` component, whose free slot ("Anything to put beside the links",
`render_slot(@inner_block)`) takes the links. The starter layout calls it self-closed, so the
write turns the call into an opening and a closing tag in the layout's tree
(`MishkaCmsCore.Runtime.Authoring.Tree`) and puts the links between them. Footer links are flat:
a menu with items under items is listed in menu order, each item's link followed by the links
under it.

A component is a `MishkaCmsCore.Runtime.Component` of its own for the menu (the target's
`writes_to/2`), named `nav-` and the menu's slug, as `nav-footer`, with the header's markup
(`StarterContent.nav_links/1`, groups included) as its template. The admin places it with
`<.component component_name="nav-footer" />` on any page or layout. The layout is not touched. A
site that already has a component of that name, or a menu of the same run that is about to
create it, makes the menu wait for the admin's decision as `"component_name_taken"`: Use the
existing component connects the menu to it and writes nothing (when the other menu has not
written it yet, the menu is skipped as `"component_written_first"` and connects on the next
run), or the menu is left out.

The layout is read before anything is written, in the dry run too (`existing/2`), with one query
for every menu of the page, and read again by the one write of each menu (`prepare/3`), so a
menu never writes over what another menu of the same run just put in the layout; the menus the
layout holds from earlier writes get the layout's new `updated_at`, so a write of one is never
taken for an edit in the CMS:

  * a site with no `site-main` layout, or one whose layout draws no `site-header` or
    `site-footer`, skips a menu placed there as `"no_layout"`;
  * a header or footer that already has links of its own, or that another menu of the same run is
    about to write, waits for an admin's decision as `"header_has_links"` or
    `"footer_has_links"` (`MishkaCmsMigration.MigrationItem`'s Decide…): Keep the site's own skips the menu as
    `"site_own_kept"`, and Replace with the imported menu writes it over the layout in place of
    those links; the site's own navigation is never written over on the import's own say. The
    main menu comes first, so a second menu is the one that asks;
  * otherwise the menu is written over the layout (`MishkaCmsMigration.Target`'s
    `{:updated, record}`), and the layout is its record.

The template is changed as the core's editor changes one: read into its tree
(`MishkaCmsCore.Runtime.Authoring.Tree`), the header's links put in, and written back the way the
editor saves it (`MishkaCmsCore.Runtime.Authoring.Tree.Serializer.to_heex/1`), so the layout comes
back formatted as a save in the builder formats it.

## Where each link points

An item that points at a page, a post, a category or a tag points at the CMS record the same run,
or an earlier one, wrote for it, at the address the site serves it at, live or not yet
(`MishkaCmsMigration.MigrationRun.record_address/2`), as a live navigation. One whose record was
never written, or that an admin left out, keeps its address at the source. A custom link to the
source's own home is the site's home, `/`; any other link keeps its address and opens as WordPress
said it does (`new_tab`). Only `http`, `https`, `mailto` and `tel` addresses, and a path on the
site, are links; an item with any other, such as `#`, has none.

The pages, posts, categories and tags a menu's items point at are what it needs (`needs/1`): Import
this item writes a menu only once each of them has a CMS record, or was left out.

The import's report names the items that still point at the source's host, after `"old_address:
"`, so an admin can see which links leave the site; a dry run, which writes nothing, names none.
The items' records are found with one query for the items (`note/2`) and one more, then one for
each kind of record they point at, when the menu is written (`prepare/3`).

## Links to pages that are not live

A link to a page the site does not serve would lead nowhere: pages come in as drafts by default
(`MishkaCmsMigration.Targets.CmsPage`). So a menu waits for an admin's decision as
`"draft_pages"` (`MishkaCmsMigration.MigrationItem`'s Decide…) while it links a page that is not
live, its labels after `": "`, as in `"draft_pages: Team, Leadership"`, unless another live page
of the site answers that page's address, as the Blog's `/:lang/blog` answers `/blog`
(`MishkaCmsCore.Runtime.PageEditions.urls/2`, read with one query of the live pages):

  * Publish the linked pages (`"import"`) makes those pages live through the core's own page
    `:update`, as the admin, in the menu's write: a page the core refuses to put live fails the
    menu, and nothing of it is written. A page published so is the admin's from then on, as one
    published in the CMS is: a later run keeps it as it is (`"edited_in_cms"`);
  * Leave those links out of the menu (`"keep"`) writes the menu without them, and the pages stay
    as they are.

An import tells from the pages' records, once its pages are written (`prepare/3`). A dry run,
which writes none, tells from what it plans for them (`waiting/2`): a page it would create or
update is not live under the run's `pages` choice `"draft"`, nor with a note that keeps it a
draft (`"draft_from_*"`), and any other from its record. It asks two queries more, for the
pages' items and their records. A menu a run does not write, one WordPress did not change, is
not asked about.

## Links and the site's language addresses

The links are written as fixed addresses in the way the site names the language in its addresses
now, while a source that runs a language plugin (Polylang or WPML) has addresses with a language
prefix, and a site that gives every language a prefix may later be switched back. On such a run
a menu waits for an admin's decision as `"language_urls"`, before anything else
(`MishkaCmsMigration.Targets.Helpers.LanguageUrls`): Write links for the site's current mode
(`"import"`) goes on, Wait (`"keep"`) skips it as `"waits_for_language_urls"`. When the site's
`Language in addresses` changes later, a Run again plans the menu for a new write, so its links
follow the new mode (`MishkaCmsMigration.Targets.Helpers.LanguageUrls.hash/2`).

## A second run

A re-run is held to the rules every target is: a menu the source did not change is
`"unchanged"`, and a layout or component an admin changed since the import wrote it is kept as the
admin left it (`"edited_in_cms"`). A menu the source changed is written again, its links in place of the ones
the last import wrote.

# `error_return`

```elixir
@type error_return() ::
  {:error, [%{action: atom(), field: atom(), message: String.t()}]}
```

# `okey_return`

```elixir
@type okey_return() :: {:ok, struct() | map() | module() | [any()]}
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `config`

```elixir
@spec config() :: keyword()
```

# `config`

```elixir
@spec config(atom()) :: any()
```

# `enable`

```elixir
@spec enable() :: okey_return() | error_return()
```

# `get`

```elixir
@spec get() :: keyword()
```

# `health_check`

```elixir
@spec health_check() :: :ok | {:degraded, term()} | {:error, term()}
```

Optional. See `c:MishkaInstaller.Event.Hook.health_check/0`. Override to report health.

# `on_dependency_error`

```elixir
@spec on_dependency_error(term()) :: term()
```

Optional. See `c:MishkaInstaller.Event.Hook.on_dependency_error/1`. Override to react.

# `register`

```elixir
@spec register() :: okey_return() | error_return()
```

# `restart`

```elixir
@spec restart() :: okey_return() | error_return()
```

# `start`

```elixir
@spec start() :: okey_return() | error_return()
```

# `start_link`

# `stop`

```elixir
@spec stop() :: okey_return() | error_return()
```

# `unregister`

```elixir
@spec unregister() :: okey_return() | error_return()
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
