| Age | Commit message (Collapse) | Author |
|
The column moved to page.rb and was now written only by publish_draft!
and by the chapter seed, and read by nothing.
lib/tasks/pages.rake goes with it. backfill_external_url reads the dropped
column. backfill_address is spent and no longer safe to re-run.
|
|
diff_against gained external_url, published_at and user. The view renders
each metadata section only when that attribute changed, and says so once
when none did.
Metadata now precedes the locale pointer, which speaks only of translations:
above the metadata it read as "nothing changed" on a revision that had moved
the page.
|
|
|
|
Node#attach_asset! writes to the node's draft alone, creating one from the
head when none is pending. It refuses when another user holds the lock, and
when an autosave already exists
The asset_attach verb goes with it. create_new_draft names the editor on the
draft it creates and the publish entry's asset delta reports the attachment.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nodes#new marks the kinds that place under /updates, derived from each kind's
own path_prefix. The three parent pickers mark candidates the current user
cannot publish into; menu_search returns needs_redaktion per result, computed
per user rather than as a bare restricted flag, and the menu-item picker opts
out.
restore_from_trash! checks its destination: the restore is the move, applied
rather than staged, so no later publish would have caught it. The node
returns as a draft either way.
|
|
nodes#show renders publish and trash as disabled_action spans with a hint
naming what an editor can still do, matching how locked_by_other already
reads. Editing and reverting stay live: drafting is free everywhere.
|
|
CccConventions::TAG_SCOPES maps "update" and "pressemitteilung" to /updates
and "disclosure" to /disclosure. Page.aggregate applies the mapping, so an
aggregate over one of those tags is bounded by its subtree regardless of what
the shortcode says. Tags stay unrestricted; positions are publish-gated.
Both RSS actions call Page.aggregate rather than repeating its tag join.
Empty feeds no longer crash: Atom falls back to the current time for the
required <updated>, and the optional dc:date is omitted.
|
|
publish_draft!, trash!, destroy_from_trash!, attach_asset! and
Asset#destroy_witnessed! now refuse unless the acting user holds redaktion,
and only when the subject is on a restricted surface: the front page, the
updates tree that feeds ~100k subscribers, or disclosure. Drafting,
autosaving, tagging and creating stay free everywhere for everyone.
Enforcement is in the models rather than the controllers, since attach_asset!
and the rest are reachable from rake tasks and internal paths. It follows the
errors.add-plus-bare-raise pattern the rest of Node already uses, so every
existing RecordInvalid rescue reports it with a localised message; only
assets_controller#destroy needed a rescue added.
A nil user is treated as a system context and bypasses the gate. The default
nil on three of those verbs is what makes that reachable, and removing those
defaults once every call site passes a user is the next tightening.
|
|
Every path through Permission raised or lied: validates_presence_of on a
boolean made granted = false unsaveable, since false.blank? is true;
get_permission_for read a nonexistent identifier attribute; has_permission?
called an undefined plural method and returned a truthy relation in every
case; and set_permission mutated granted without saving. Nothing outside the
model referenced it and the table was empty.
|
|
|
|
A lock with no draft or autosave behind it was invisible to
everyone including its holder, until the next editor collided
with it. work_in_progress now admits those nodes, and the
widget names the holder rather than the last editor on any
locked row.
Also flips the ordering to stalest-first, so a backlog reads as a queue
rather than a feed, and gives the heading a real total: the count came
from the already-limited relation and could never exceed five.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
head ? head.title : draft.title never accounted for a node with only
an autosave, reuses editable_page
|
|
A starred PDF previously ran through the same crop-and-lightbox path
a photo does -- exactly the awkward treatment explicit headline
designation was meant to avoid. _headline_image.html.erb now branches
on @headline_asset.pdf?: a PDF renders as a linked card (a :medium
thumbnail, a file icon, its name), no gallery participation at all.
The existing image-headline and gallery-fallback logic is otherwise
unchanged, now scoped to an image-specific headline only.
Other attached PDFs -- headlined or not -- list below as plain links,
same reasoning as why non-headline photos still get a gallery
trigger: an attached document shouldn't go invisible just because
nothing's been starred yet.
headline_image's own render guard needed widening to cover a page
with PDFs attached but no images and no headline at all -- the one
case none of the existing conditions accounted for.
|
|
|
|
|
|
|
|
Only caller left was the sort_images action removed last commit.
Kept alive only as a test-setup convenience for diff_against
coverage, which is what it actually was -- an implicit API with no
deliberate contract, sitting there for anyone to call. Rewrote the
one test to set up its fixture state directly via related_assets
rather than through a method whose real job was already done.
|
|
- related_assets gains a `headline` boolean (DB-enforced: at most one
per page), replacing "first image by position" as the headline
rule. A rake task backfills the current first image on every live
head/draft, so nothing changes visually until an editor changes it.
- The image picker sidebar gets a star toggle reflecting the flag;
the TinyMCE inline-image picker's badge now reads it too, instead
of assuming position 0.
- No headline chosen (or none attached) now falls back to the
gallery-count caption itself becoming the lightbox trigger, instead
of the gallery being unreachable.
- Assets gain creator, source_url, and license_key (against a new
config/asset_licenses.yml dictionary). asset_credit renders a
degrading attribution line, reused as a hidden per-image glightbox
caption so credit is one click away for every image, not only the
headline's always-visible one.
- Fixed: asset thumbnails rendered unconditionally regardless of
whether a real variant exists on disk. Asset#has_variant? checks
file existence, not content type -- some legacy PDFs have real
pre-rewrite thumbnails a content-type check would have hidden.
- assets#new/edit rebuilt onto the same node_description/node_content
layout as assets#show, picking up the three new fields in the
process.
|
|
|
|
A muted glyph before each sentence makes both log surfaces
scannable by verb: create, publish, move, trash, restore,
destroy, and the discard pair. Rollback publishes get their own
icon (history) distinct from ordinary promotion. Unknown verbs
fall back to a dashed circle.
|
|
Each row in nodes#show's History section now carries terse badges
from the action log: created, published, and restored (rollback
re-promotion), with date and actor, rendered from entry metadata
alone. Backfilled entries wear the inferred marker, so
reconstructed provenance stays distinguishable from witnessed
history. A revision that was published and later restored shows
both badges chronologically -- its true biography.
Only create and publish entries carry page_id; trash, restore, and
destroy annotate the node's own log zoom instead of any single
revision, by design.
|
|
|
|
|
|
Sentences for trash (with the pre-trash path), restore_from_trash
(with the destination), and destroy (with the final path, read
from its flat create-symmetric key).
|
|
nodes#show gains a Trash section on trashed nodes: provenance from
the trash entry, a restore form whose parent picker pre-fills the
old parent while it still lives, and permanent deletion. A
Move-to-Trash button joins the status actions on living nodes.
nodes#trashed lists trashed subtree roots with weight, provenance,
and deletion; the dashboard housekeeping row links to it, and
trash/destroy redirect there.
Deletion from Trash now removes the whole subtree, deepest first,
each node through a real destroy! so every per-node cascade runs --
amending the never-recursive rule for this one sanctioned path
(both confirms state the count; the root entry carries
destroyed_descendants). Bare Node#destroy still refuses children.
|
|
Also update the node action contract to include the trash related verbs.
|
|
|
|
NestedTree's before_destroy silently delete_all'd the whole
subtree, bypassing every per-node cleanup. Nodes are never
destroyed recursively; descendants must be removed individually.
|
|
Page.aggregate interpolated order_by into SQL unchecked while
already allowlisting order_direction; the column is now normalized
and checked against the sortable columns, falling back to pages.id.
Its values arrive from editor-authored aggregate shortcodes, so
this was editor-gated, but the asymmetry was wrong regardless.
template_name and default_template_name now validate inclusion in
Page.custom_templates -- names render as filesystem paths, so only
names actually present in the template directory are acceptable.
Validated only on change: legacy rows whose template file has since
vanished stay saveable, and valid_template's render-time fallback
to standard_template continues to cover them. Two tests that wrote
fabricated template names through the front door now arrange their
state correctly (update_column for the stale-name fallback test, a
real template for the update-persists test).
|