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

Copy Markdown View Source

What the builder needs from the review thread: the counter, and getting to an element.

The thread itself is drawn by Gervaz — MishkaCmsCoreWeb.Templates.ReviewComments reads MishkaCmsCore.Runtime.PageComment through the :for_editor action and owns everything about listing, filtering and paging. Nothing here duplicates that. This is the two things the EDITOR needs that a table cannot answer.

The counter

The red number beside Review is unresolved BUGS, and it is a COUNT — not length/1 over a list the sheet happens to have loaded. The sheet may be closed, may be showing "All", may have loaded one page of fifty; none of that can be allowed to change the number. Asking the database is both cheaper and the only answer that is always right.

Getting to an element

A note's node_id is the editor's own n<8> handle, minted fresh by every Tree.parse/1. It is a soft reference by construction: the element can be deleted, or the document re-parsed under it, and the id will point at nothing. locate/2 is the whole of what to do about that — {:ok, id} when the element is still there, :gone when it is not — so that the caller shows a message instead of selecting nil and every caller behaves the same way.

Summary

Functions

The defaults the composer is seeded with — everything the editor already knows.

One note by id, with its author and resolver loaded, or nil.

Every note on this document, newest first.

Where a note points, if it still points anywhere.

How many unresolved bugs this document has.

The path_params the Gervaz thread is scoped by.

Functions

compose_defaults(scope)

@spec compose_defaults(map()) :: map()

The defaults the composer is seeded with — everything the editor already knows.

This is what makes the form two fields instead of seven, and what makes an element note and a page note the same code path: with nothing selected, node_id is simply nil.

node_label is captured HERE, at compose time, and stored with the note, because the element it names can be deleted afterwards.

fetch(id, scope)

@spec fetch(String.t(), map()) :: MishkaCmsCore.Runtime.PageComment.t() | nil

One note by id, with its author and resolver loaded, or nil.

Scoped to WHO IS ASKING, not to the id: :get_any bypasses multitenancy, so the id alone reached every site's notes — and the id travels in a phx-value-id that anyone can type. A master reads any note; a tenant reads its own site's; nobody else reads one at all. MishkaCmsCore.Runtime.PageBuilder.Snippets.delete/2 scopes its own by-id path the same way.

list(scope, opts \\ [])

Every note on this document, newest first.

Used by the counter's siblings and by tests; the sheet itself reads through the Gervaz table.

locate(comment, tree)

@spec locate(
  MishkaCmsCore.Runtime.PageComment.t() | map() | nil,
  MishkaCmsCore.Runtime.PageBuilder.Tree.t()
) :: {:ok, String.t()} | :page | :gone

Where a note points, if it still points anywhere.

  • {:ok, node_id} — the element is in the document and can be selected.
  • :page — the note was never anchored to an element; it is about the whole page.
  • :gone — it was anchored, and that element is no longer here.

Three answers rather than two, because "about the page" and "the element was deleted" deserve different words in front of the reader, and a caller that cannot tell them apart will pick one and be wrong half the time.

open_bugs(scope)

@spec open_bugs(map()) :: non_neg_integer()

How many unresolved bugs this document has.

Derived, always: it counts rows, so creating a bug, resolving one, reopening one and deleting one all move it without anything having to remember to.

Returns 0 rather than raising when the read fails. A counter is chrome; a builder that will not open because a badge could not be computed is a worse outcome than a badge that reads zero.

thread_params(scope)

@spec thread_params(map()) :: map()

The path_params the Gervaz thread is scoped by.

PREFIXED keys, and keyed by ATOM. Gervaz turns any path param whose name matches a resource attribute into an equality filter and tests membership against Ash.Resource.Info.attributes/1, whose names are atoms — so site_id would drop the global rows this read exists to include, and a string key would match nothing at all.