MishkaCmsCore.Runtime.PageBuilder.Conditions (Mishka CMS Core v0.0.1)

Copy Markdown View Source

When an element is on the page at all, without anyone writing Elixir.

What it writes

HEEx already has the feature: :if={…} is how a developer says it, and it is the one attribute every template in this project uses for the job. So there is nothing to invent — the panel offers a short list of questions a page can answer about itself, and each one writes the expression a developer would have written:

<div :if={assigns[:current_user]}>

Every kit builder in the field has this under some name; Elementor calls it Display Conditions. The difference here is that the answer is ordinary markup, so an author who later learns Elixir finds their own condition in Advanced rather than a setting stored somewhere else.

Why assigns[:key] and not @key

Because the same document is rendered on surfaces that do not all carry the same assigns. @flash raises KeyError where nothing assigned one, and a layout, an error page, a component preview and the canvas are each rendered by a different caller. assigns[:key] answers nil instead, which is the same answer as "no", and is the form the compiled templates in this project already use.

It is also why a flash is read as assigns[:flash]["info"] rather than through Phoenix.Flash: nil[key] is nil in Elixir, so the whole chain is safe on a surface that assigned no flash at all, and put_flash/3 stores under exactly those two string keys.

Reading it back

By matching the expression against what this module writes, never by matching text. Anything else — a condition somebody wrote by hand, one piped into something — reads as :custom and the panel shows it rather than offering to replace something it does not understand. Same contract MishkaCmsCore.Runtime.PageBuilder.Interactions keeps for a click.

Summary

Functions

The attribute a condition is written into.

Reads an attribute value back into {:ok, rule}, :none, or :custom.

Every condition offered, core's first and a plugin's after them.

The conditions offered, as {value, label} in panel order.

Core's own conditions, in panel order — the seed every plugin appends to.

The expression source for one condition, or nil when there is nothing to write.

Functions

attribute()

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

The attribute a condition is written into.

read(arg1)

@spec read(term()) :: {:ok, String.t()} | :none | :custom

Reads an attribute value back into {:ok, rule}, :none, or :custom.

:custom is the important one: the element carries a condition somebody wrote, and the panel must show it rather than replace it.

registry()

@spec registry() :: [map()]

Every condition offered, core's first and a plugin's after them.

Why a plain map and not a behaviour

Because a condition IS an expression. An action had to grow a module — it decides where it runs, what boxes to draw, what to write and how to read it back — and none of that has an answer here: a rule is a name and a piece of HEEx. So the entry is what it describes,

%{key: "is_admin", label: "Visitor is an admin", source: "assigns[:current_user] && …"}

and read/1 recognises a contributed rule for free, because it matches the quoted form of whatever is in this list rather than a table of its own.

Registered through builder:conditions exactly as every other registry — core seeded first, so a plugin adds and never replaces. See MishkaCmsCore.Builder.Events.

rules()

@spec rules() :: [{String.t(), String.t()}]

The conditions offered, as {value, label} in panel order.

seed()

@spec seed() :: [map()]

Core's own conditions, in panel order — the seed every plugin appends to.

source(rule)

@spec source(String.t() | nil) :: String.t() | nil

The expression source for one condition, or nil when there is nothing to write.

nil for the empty rule — "Always", which is the absence of the attribute rather than an expression that is always true — and nil for a rule this module does not offer.