MishkaDocument.Plugins.PageSeo (Mishka Document v0.0.1)

Copy Markdown View Source

Gives each document and each page of one its own head on the documentation page that serves it, through the runtime:page:seo mishka_installer hook MishkaCmsWeb.RuntimeLive fires in handle_params/3 (see MishkaCmsCore.Runtime.Seo for the map it is handed and what the head builds from it).

A page is matched by the kind of record it says it shows (record, its extra's "record"): "docs:document", a document's front page, or "docs:page", one page of a document — the kinds MishkaDocument.Plugins.PageRecords names. Its path is a document's own address, /chelekom/docs and /chelekom/docs/*section (MishkaDocument.Address), which names that document; or anything that captures workspace, and *section for a page, whose slug names a document with no address of its own. Every other page is handed back untouched before anything is read. A kind has one owner, so the answer for one of these halts the chain: no later plugin is asked.

What is read

Nothing, on a page a visitor reads: MishkaCmsWeb.RuntimeLive hands it the rows the page read once for its head, its frame and its blocks, as the visitor (source, MishkaDocument.PageSource): the active document whose own address the page is at, else the one the workspace capture names, in the page's language; its published versions; the approved pages of the one shown — the version shown is the one ?v= names, else the one marked latest, else the newest published — and the page at the address the section capture holds, headless/switch, with its own text and lock. A slug names only a document with no address of its own: one that has an address is read there and nowhere else, so /docs/<its slug> names nothing. A version's front page is at the document's own address and nowhere under it (MishkaDocument.Address), so a section naming its slug names nothing; a group is only a heading in the contents, and nothing is named at its slug either — unless it has a page of its own (group_page), which is named there as any page is.

A document or a page the visitor's own read left out is answered with its lock alone, read as the system, so the address answers them 404 and nothing of it reaches them.

An editor's preview and a share card hand no rows, and the same document, versions and page are read here, once, through :public_list as the system (MishkaDocument.PageSource.workspace/3).

What it returns

Every value is a default: what the page sets in its own SEO — a title, a description, a tag, structured data — wins, and may name the variables below, {{doc.title}} · {{workspace.name}}.

  • title, description — the document's name and description on its front page, whether it draws the page its version marks as its front page there or the landing; the page's title and the start of its text on one of its pages;
  • type — "article";
  • tags — the document's or the page's own seo_tags; a page that holds no robots meta of its own takes its document's, as a CMS page takes its layout's;
  • schema — its own schema when it holds one, else a TechArticle; then a BreadcrumbList, Documentation → the document → the page. The TechArticle's description is the description meta the record stores, else the one above; its image is {{page.image}}; its author is the Person the page's author meta names, else its document's, and left out when neither names one; its publisher is the site, the Organization at <site>/#organization, with the site.logo setting as its logo. A record whose robots meta (tags) says noindex or none has no TechArticle: a stored robots meta of noindex, follow keeps a page that is not ready yet out of an index, and out of the sitemap (MishkaDocument.Plugins.SitemapUrls);
  • url — the canonical address: the document's own address where it has one, with the page's under it, so the head names the one address a reader and a crawler are sent to; else the address asked;
  • vars — doc.title, doc.description, doc.url, doc.version, doc.group, doc.published_at, doc.modified_at (the front page or the page of it this address shows; a page's, like its TechArticle's dateModified, is when it last changed — MishkaDocument.Resources.Calculations.LastModified), and workspace.name, workspace.description, workspace.url (the document);
  • card — what its share card (MishkaCmsCore.Runtime.ShareCard) shows besides the title and description: "Documentation" above a document's front page, the document's name above one of its pages — Guide · Navigation under a group — and the version, on the front page the one a reader lands on;
  • var_docs — those variables in the current locale, whether or not this request filled them. Called with no params — an editor asking which variables a page has — that is all it changes.

permissions is not a default: the document's on its front page; on one of its pages, the page's own, else its nearest parent page's, else its document's (MishkaDocument.Resources.Calculations.EffectivePermissions). MishkaCmsWeb.RuntimeLive opens the address only for whoever holds one of them, after the page's own, and the head keeps it out of an index. That is why a record the visitor's read left out has its lock read as the system: a read as the visitor leaves a locked record out for exactly the visitor the page has to refuse.

What plugins add to it

The answer is handed to the document:page:seo mishka_installer event (event/0) through MishkaInstaller.Event.Hook.call/3, and what it replies is the answer; a plugin may give the page a share card of its own — image with card: false — or change any other default. A plugin that opts in with call/2 is handed, as meta:

%{
  site_id: "…",
  document: %{
    name: "Guide",
    slug: "guide",
    path: "/guide/docs",
    language: "en",
    description: "How to use it.",
    url: "https://example.com/guide/docs"
  },
  version: "2.0.0",
  page: %{
    slug: "breadcrumb",
    title: "Breadcrumb",
    description: "The start of its text.",
    path: "navigation/breadcrumb",
    parents: [%{slug: "navigation", title: "Navigation", group: true}]
  },
  url: "https://example.com/guide/docs/breadcrumb",
  contents: [
    %{title: "Breadcrumb", slug: "breadcrumb", group: false, current: true},
    %{title: "Menu", slug: "menu", group: false, current: false}
  ]
}

The answer keeps redraw as core handed it (MishkaCmsCore.Runtime.Seo.given/2): true when a forced look at the share card asks for the head (MishkaCmsCore.Runtime.ShareCard.specs/3). A plugin that draws a card of its own draws it again over its old file then.

page is nil on a document's front page, and version when it has no published version. A description is the description meta the document or the page stores, else the one the head is given here. parents are the pages and groups the page is filed under, nearest last. contents are up to seven entries of the page's level of the contents, in their order, the page among them marked current; on a front page, the document's top level, its version's front page marked. A plugin replies with the answer:

use MishkaInstaller.Event.Hook, event: "document:page:seo"

def call(seo, %{page: %{title: title}}), do: {:reply, %{seo | title: title <> " · Docs"}}
def call(seo, _meta), do: {:reply, seo}

A capture that names nothing a reader may open changes nothing else but card, false: the page answers 404 as it does without this, and has no share card to draw. A note is drawn inside its page and has no address of its own, so its own SEO is not read here.

A module because a mishka_installer hook is one. Registered in MishkaDocument.Application.

Summary

Functions

Returns a specification to start this module under a supervisor.

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

The mishka_installer event a documentation page's answer is handed to: "document:page:seo".

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.

event()

@spec event() :: String.t()

The mishka_installer event a documentation page's answer is handed to: "document:page:seo".

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.