MishkaCmsMigration.Targets.CmsLayout (Mishka CMS Migration v0.0.1)

Copy Markdown View Source

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.

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).

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.

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.

Summary

Functions

Returns a specification to start this module under a supervisor.

Callback implementation for MishkaInstaller.Event.Hook.enable/0.

Callback implementation for MishkaInstaller.Event.Hook.get/0.

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

Callback implementation for MishkaInstaller.Event.Hook.register/0.

Callback implementation for MishkaInstaller.Event.Hook.restart/0.

Callback implementation for MishkaInstaller.Event.Hook.start/0.

Callback implementation for MishkaInstaller.Event.Hook.stop/0.

Types

error_return()

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

okey_return()

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

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

config()

@spec config() :: keyword()

config(key)

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

enable()

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

Callback implementation for MishkaInstaller.Event.Hook.enable/0.

get()

@spec get() :: keyword()

Callback implementation for MishkaInstaller.Event.Hook.get/0.

health_check()

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

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

on_dependency_error(error)

@spec on_dependency_error(term()) :: term()

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

register()

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

Callback implementation for MishkaInstaller.Event.Hook.register/0.

restart()

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

Callback implementation for MishkaInstaller.Event.Hook.restart/0.

start()

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

Callback implementation for MishkaInstaller.Event.Hook.start/0.

start_link(args \\ [])

stop()

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

Callback implementation for MishkaInstaller.Event.Hook.stop/0.

unregister()

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

Callback implementation for MishkaInstaller.Event.Hook.unregister/0.