The SEO a page or a layout stores, as the builder's settings modal edits it: a Gervaz form.
A resource with no table behind it
An SEO setting is not a row anywhere. It is a Page's description, seo_tags and schema, or
a Layout's seo_tags. Gervaz builds a form from an Ash RESOURCE, so this is one with no data
layer — Ash.DataLayer.Simple, as MishkaCmsCore.Runtime.PageBuilder.DesignToken — whose :read
reads the document as the actor and whose :update writes it back through the document's own
action, so every policy and validation stays the document's.
Its id names the document: "page:<id>" or "layout:<id>" (id/2), and "page:draft:<id>" or
"layout:draft:<id>" for a draft of one, which the builder edits while it writes that draft.
The record is the draft
What the document stores, the add-a-tag row, whether the JSON box is open, and the head the page
would render (preview/1). change/3 is what one edit writes and typed/2 what Save reads
from the fields as drawn; MishkaCmsCoreWeb.Templates.SeoForm draws the draft and keeps the
edited one on the form's component until Save sends params/1.
What a page sets
title— a storedtitletag; empty, the record's title on a page that serves records, else the page's owntitle;description— the page's owndescription;- share image, canonical address, "hide from search engines" — stored
og:image,canonicalandrobotstags; - structured data — one object of a type
MishkaCmsCore.Runtime.Seo.schema_types/0offers, written in place among the page's others, or the whole of it as JSON.
Every one of them may name a variable, {{post.title}}; a plugin on
MishkaCmsCore.Runtime.Seo.event/0 fills what the page leaves empty. A layout stores tags only.
What the page shows
A page that serves one record per address names the kind in its extra, "record" => "blog:post"
— one of MishkaCmsCore.Runtime.Seo.record_types/1, which the installed extensions answer — and
the plugin that owns the kind fills its SEO, whatever its path. The page's General tab picks it
(MishkaCmsCore.Runtime.Resources.Changes.SetRecordKind); this reads it, for the variables and
for a preview drawn with one of the kind's samples instead of placeholders, and Save leaves it as
it is.
Summary
Functions
seo with one more, empty, question.
seo with the add-a-tag row written as a stored tag, when the row is complete.
seo after one edit, its head drawn again. params are the form's fields as drawn and target
the one that changed (["seo", "add"]), which decides what else the edit writes — picking a tag
to add starts its row, picking a structured-data type writes its object, typing JSON replaces the
schema.
seo without a share image of its own: each record's, or none, again.
The id of a document's SEO: "page:<id>", or "page:draft:<id>" for a draft.
Validates that the keys in the provided input are valid for at least one action on the resource.
Same as input/1, except restricts the keys to values accepted by the action provided.
Which form a section's SEO is: :page where its resource stores schema and seo_tags, :tags
where it stores seo_tags alone (a layout), nil where it stores neither.
What Save sends the :update action for seo.
seo with the head its page would render, asked of MishkaCmsCore.Runtime.Seo.resolve/2 with
the draft in place of the stored fields; a dynamic page's plugin answers with its own variables
there too. A layout has no head of its own and is returned as it is.
seo previewed in view: "google", "x" or "facebook".
The :read action: the document the id names, read as the actor, as its draft — none for an
unknown section, a document the actor may not read, or a query that names no id.
The kind of record seo shows, as its record_types describe it; nil for none.
seo without the question at index.
seo without the stored tag at index.
The :update action: the draft's description, seo_tags and schema written through the
document's own update action, as the actor — MishkaCmsCore.Runtime.PageBuilder.Helpers.save_section/5,
the write every save in the editor takes. The saved document comes back as the record's document.
seo with the Advanced section opened, or closed.
seo with the share image's address field shown, or hidden.
seo with the fields every edit sends as they are drawn: the description, and the title, share
image and canonical address, each of which is one stored tag — a value writes it, a blank removes
it, an absent field leaves it.
Types
@type t() :: struct()
Functions
seo with one more, empty, question.
seo with the add-a-tag row written as a stored tag, when the row is complete.
seo after one edit, its head drawn again. params are the form's fields as drawn and target
the one that changed (["seo", "add"]), which decides what else the edit writes — picking a tag
to add starts its row, picking a structured-data type writes its object, typing JSON replaces the
schema.
seo without a share image of its own: each record's, or none, again.
The id of a document's SEO: "page:<id>", or "page:draft:<id>" for a draft.
Validates that the keys in the provided input are valid for at least one action on the resource.
Raises a KeyError error at compile time if not. This exists because generally a struct should only ever
be created by Ash as a result of a successful action. You should not be creating records manually in code,
e.g %MyResource{value: 1, value: 2}. Generally that is fine, but often with embedded resources it is nice
to be able to validate the keys that are being provided, e.g
Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> Ash.create()
Same as input/1, except restricts the keys to values accepted by the action provided.
@spec mode(String.t()) :: :page | :tags | nil
Which form a section's SEO is: :page where its resource stores schema and seo_tags, :tags
where it stores seo_tags alone (a layout), nil where it stores neither.
What Save sends the :update action for seo.
seo with the head its page would render, asked of MishkaCmsCore.Runtime.Seo.resolve/2 with
the draft in place of the stored fields; a dynamic page's plugin answers with its own variables
there too. A layout has no head of its own and is returned as it is.
seo previewed in view: "google", "x" or "facebook".
@spec read(Ash.Query.t(), map()) :: {:ok, [t()]}
The :read action: the document the id names, read as the actor, as its draft — none for an
unknown section, a document the actor may not read, or a query that names no id.
The kind of record seo shows, as its record_types describe it; nil for none.
seo without the question at index.
seo without the stored tag at index.
@spec save(Ash.Changeset.t(), map()) :: {:ok, t()} | {:error, term()}
The :update action: the draft's description, seo_tags and schema written through the
document's own update action, as the actor — MishkaCmsCore.Runtime.PageBuilder.Helpers.save_section/5,
the write every save in the editor takes. The saved document comes back as the record's document.
seo with the Advanced section opened, or closed.
seo with the share image's address field shown, or hidden.
seo with the fields every edit sends as they are drawn: the description, and the title, share
image and canonical address, each of which is one stored tag — a value writes it, a blank removes
it, an absent field leaves it.