| Age | Commit message (Collapse) | Author |
|
Pagination had almost no styling, and its current-page selector never
matched will_paginate's actual markup. Added chip-style page links, a
solid current-page indicator, and distinct disabled states for
Previous/Next.
Previous/Next/gap rendered as empty, unlabeled elements --
will_paginate only ships English translations and this app defaults
to German. Added previous_label/next_label/page_gap to en.yml and
de.yml.
|
|
publish_draft! called move_to_child_of before validating the new
parent at all. Staging a node under one of its own descendants
created a real, if transient, cycle in parent_id the instant that
save landed -- and update_unique_names_of_children's after_save
callback, which recurses down through parent_id with no cycle check
and no depth limit, bounced between the two nodes forever, crashing
the whole process with a SystemStackError rather than just producing
bad data.
Now rejected up front with a normal ActiveRecord::RecordInvalid.
|
|
@mynodes used the bare @current_user ivar instead of the current_user
method (works today only because login_required already forces that
memoization; every other query and controller in this app uses the
method), and .uniq, which is plain Enumerable#uniq here, not
Relation#distinct -- it materializes every joined row into an Array
before deduplicating, rather than deduplicating in SQL.
@mypages had the same ivar pattern, no .order/.limit unlike every
sibling query in this action, and -- confirmed via a full grep -- is
referenced nowhere else in the codebase. Removed rather than bounded;
nothing was ever rendering it.
|
|
Both ran real code and checked nothing -- Minitest's "missing
assertions" warning has been firing on every run this session.
test_find_or_create_draft_if_draft_exists_and_is_owned_by_user called
the method twice but never checked what came back; now confirms the
second call returns the same draft rather than creating a new one,
and leaves the lock and page count untouched.
test_destroy_a_published_node destroyed a node and loaded the admin
index but never checked the response, unlike its two neighbors
covering the same destroy path (dangling pages, orphaned
occurrences). Added the assert_response :success its own shape
already implied.
No behavior changed -- both were passing before for the same reason
they're passing now, they just weren't proving anything.
|
|
Long RRULEs previously overflowed their column with no wrap point;
now escaped and rendered with a <wbr> after each semicolon, so a
long rule wraps at a clause boundary instead of running off the
table. Deliberately not truncated -- a cut-off RRULE's trailing
clause (BYDAY, BYMONTH, etc.) is usually the most specific part.
The url column is now a real link, truncated with an ellipsis at a
fixed width -- deliberately no tooltip, since hovering a real link
already shows the full address in the browser's own status bar.
rrule_with_break_opportunities splits on the raw string's own
semicolons before escaping each piece, not after -- escaping first
and searching the result for semicolons also matches the ones inside
</> entities, corrupting anything containing a literal < or >.
|
|
"#page_editor form input[...]" -- but form_for wraps around
inside, so the selector never matched anything. Every field without
its own dedicated per-id rule (i.e. everything outside nodes#edit)
fell through to the bare, unsized base rule instead. Fixed by
dropping the erroneous "form" and adding textarea, which the rule
never covered either even for its intended targets.
nodes#edit's own per-id rules (#page_title, #page_abstract, etc.) are
unaffected -- same specificity, later in the file, so they still win
where they already applied.
|
|
|
|
|
|
|
|
|
|
sdiff aligned an inserted paragraph break correctly, but the renderer
wrapped the raw </p>/<p> tokens in <ins> regardless -- invalid nesting
that browsers silently repair by dropping the unmatched closing tag,
leaving everything downstream rendered inside an <ins> with nothing
left to close it. Same failure mode as the retired cacycle_diff.js,
different cause.
Tag tokens now render as a plain-text pilcrow with a tooltip naming
the actual tag, never as literal markup inside <ins>/<del>. Applies to
both inline and side-by-side. Side-by-side's new panel flattening a
real paragraph break into a marker, rather than showing its own true
structure, is a known, accepted trade-off for now -- not the same
problem, and not fixed here.
|
|
Hiding the numeric revision picker for head/draft/autosave comparisons
accidentally took the view toggle down with it -- it was nested inside
the numeric-only branch instead of being its own control. Now renders
unconditionally as plain links, with the current mode shown as
non-interactive text. Switching which revisions to compare via the
numeric form no longer silently resets the view mode back to inline.
|
|
|
|
|
|
|
|
|
|
Node#resolve_page_reference and #available_layer_pairs let
Page#diff_against compare named layers (head/draft/autosave), not
just numbered revisions -- autosave was never part of Node#pages, so
this was the missing piece.
Wired into nodes#show's Status section, nodes#edit right after an
autosave gets resurrected ("What changed?"), and the admin wizard's
current-drafts table, which now also lists autosave-only nodes it
previously never showed.
revisions#diff hides the numbered-revision picker when comparing
named layers (it can't represent them), shows a plain label instead,
and offers buttons to switch between whichever other pairs make
sense for the node's current state. Destroying the topmost layer is
available directly from the diff view, reusing the existing revert!
path.
"Discard changes" is renamed "Discard Autosave" everywhere it
appears, to match "Destroy Draft".
|
|
|
|
The table-based markup revisions#show's own test was asserting
against (<strong>, <td>) was retired when this view moved to the
node_description/node_content pattern; updated the assertions to
match. Found in the same pass: @page.body had no raw(), so any real
markup in a revision's body rendered as escaped entities on this
page — same bug class as revisions#diff, just not yet fixed here.
|
|
Revisions#diff now computes an inline or side-by-side word diff
server-side (Page#diff_against, lib/html_word_diff.rb) instead of
shipping raw/escaped content to the browser for a 2008-era vendored
JS differ to mangle. View mode is picked via a `view` param, settable
either on the diff page itself or directly from the revisions#index
sticky bar next to "Diff revisions".
Also fixes a pre-existing bug in RevisionsController#diff's single-
revision branch, which set params[:start]/params[:end] instead of
params[:start_revision]/params[:end_revision].
|
|
|
|
|
|
Converts the <p>-per-field layout to node_description/node_content,
matching every other converted view. The RRULE builder's internal
markup and JS-driven show/hide are untouched -- only its containing
block moved.
Adds the same node picker and optional Title field events#new just
got, with one addition specific to edit: an already-linked event
shows its node as a plain link, with the picker itself gated behind
a <details> disclosure. Re-linking a live event to a different node
is rare and has real side effects (which page it appears under, which
tag-prefill applies), so it gets an extra click rather than sitting
as easy to trigger as the unlinked case, which stays unguarded.
Moves Destroy out of the subnav and into the page body, matching
events#show. Required extracting it (and Back) into their own block
outside form_for -- button_to generates its own nested <form>, which
the browser silently drops when nested inside another; this is the
same failure mode nodes#edit hit earlier in this branch. Reuses the
existing .node_action_bar class rather than the naked block that
would've resulted, adding a "standalone_action_bar" modifier for the
two things specific to living outside #page_editor: the -125px
margin every other line in the form gets for free, and link
underline visibility, since this is the first .node_action_bar
instance with a bare <a> rather than only buttons.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Editors' TinyMCE output can contain unclosed void elements like <br>,
which is valid HTML5 but invalid XML -- three different places assumed
the stricter rule and broke or silently misbehaved on the looser one.
The Atom feed's <content type="xhtml"> block required real, well-formed
XML structure but was handed a raw, unescaped body string; switched to
type="html" with CGI.escapeHTML, matching how title/summary already
handle the same content. rewrite_links_in_body used libxml's strict XML
parser to rewrite internal links to be locale-prefixed, which raised on
exactly this class of malformed markup -- silently, since the whole
method was wrapped in rescue; nil, meaning the link rewrite (not the
save) quietly failed with no error anywhere. Replaced with Nokogiri's
lenient HTML parser, which repairs malformed void elements rather than
rejecting them; also drops the bare rescue now that the actual failure
mode it was guarding against shouldn't occur, and fixes two adjacent
bugs found while in this method: a typo'd /sytem/uploads/ regex that
could never match, and a missing https:// exclusion alongside the
existing http:// one.
Also addresses stale flash messaging surfaced while testing the above:
update's save confirmation was being clobbered by edit's own "locked
and ready" notice on the very next request, since nothing distinguished
a fresh lock acquisition from a redirect back after saving. The save
confirmation now names the next step (publish from Status) and flags a
stale translation if one exists, using Page#outdated_translations?,
already present but previously unused by any controller.
|
|
Remove dead pages#index and occurrences resource; fix menu_items#new
menu_items#new called menu_item_params, which requires a submitted
menu_item key that can never be present on a fresh GET -- new only
ever needs a blank record to render against.
pages#index and the entire occurrences resource were unmodified Rails
scaffold generator output, unlinked from any nav and unreachable except
by direct URL. pages#index had no controller action at all (@pages was
never assigned); occurrences#index queried every row with no scoping or
pagination and its layout referenced a scaffold.css asset that no
longer exists in the pipeline. Neither is a real editorial surface --
occurrences are auto-generated from an event's RRULE and were never
meant to be browsed as a flat list. Removed rather than repaired.
pages#preview and pages#sort_images remain, now as explicit routes
rather than under a full resources :pages block.
|
|
Fix button height mismatch, extend table styling to assets/users/events
a.action_button (bordered) and the state_changing/destructive/computation
pills (borderless) rendered at different heights, since each pill variant
set its own padding at higher specificity than the shared rule meant to
equalize them. Padding and border-radius now live only in one shared
rule; also restores the #page_editor prefix on a.action_button, needed
to outrank the #page_editor a wavy-underline rule.
table.assets_table, table.user_table, and table.events_table now share
table.node_table's border-collapse/header/hover treatment. tr
min-height is dropped -- browsers don't honor height on <tr> -- in
favor of cell padding, kept at zero for node_table (spacing already
comes from its h4/p content) and set to 8px for the plain-text tables.
|
|
Adds a fourth button category, .computation -- teal, matching Preview's
existing color -- for actions that compute a view without changing state,
the first case in the app that didn't fit reversible/state-changing/
destructive. Diff revisions now starts disabled and only enables once two
distinct revisions are selected, matching the disabled-state pattern
already used for Unlock + Back.
The header row is wrapped in a real <thead> (previously flat inside the
table alongside data rows) so a sticky diff bar can sit above the fold on
long revision lists -- selecting a comparison pair no longer requires
scrolling back up to find the button, or back down to confirm what's
selected, both surfaced live via one shared JS function.
The two most recent revisions are pre-selected by default, covering the
most common comparison (current against previous) with no extra clicks.
Deliberately scoped to numbered revisions only -- comparing draft/head or
autosave/draft was raised and set aside, since neither exists as a row in
this table by design and extending the comparison to them is a separate
feature, not a default on this one.
Also drops the dead "Edit" subnav link, redundant with nodes#show's
Status section covering the same action with state-aware labeling this
static link could never have.
|
|
Retires content_for :subnavigation on nodes#show entirely -- Preview
was fully redundant with Links' own preview URLs in every state, and
Edit is now a permanent, always-rendered action inside the new Status
section rather than a link floating at the top, consistent with the
"action lives next to the info" pattern People already established
for Unlock. Status surfaces head/draft/autosave plainly and gates
Edit/Publish/Destroy/Discard on lock ownership specifically, not mere
lock presence -- @node.locked? alone would have blocked the lock
owner's own session, caught by the click-test and fixed here rather
than shipped.
nodes#edit's action bar is rebuilt to sit outside form_for (both
button_to calls render their own nested form, invalid HTML the
browser was silently stripping) with Save wired back in via
form="..." rather than needing to live inside the form tag at all.
Also brings the locked-and-ready-to-edit flash message, and the
visual polish from this session's click-test: consistent button
heights across the bordered and pill-shaped variants sharing a row,
spacing between Status's data and its actions, and error_messages
styling reusing the destructive-red vocabulary already established
elsewhere.
|
|
revert! requires the lock to already be held, correct for nodes#edit
where the editor holds it throughout -- but both exclusive-case
buttons on nodes#show fire from a node that starts out unlocked,
which raised LockedByAnotherUser incorrectly. lock_for_editing! first
makes both call sites safe: a harmless re-stamp when already the
owner, a real but momentary acquisition otherwise, released again by
revert! itself once nothing is left to protect.
|
|
A blank title failed presence and length validation simultaneously,
showing two redundant messages for one problem; length now skips
whenever slug is already blank, matching presence's own skip
condition. Separately, create's failure path never computed
@selected_kind/@parent_id/@parent_name the way new does, so
re-rendering after a validation error silently lost which kind and
parent had been chosen -- for the auto-tagging/auto-templating kinds,
that meant a corrected resubmission could silently produce a plain
generic node instead. required on the title field closes the common
case without a round trip; the server-side fix remains the actual
guarantee, since required is trivially bypassed.
|
|
|
|
|
|
revert! discards exactly the topmost non-empty layer -- autosave if
present, else draft -- and reveals whatever's beneath it, releasing the
lock only once nothing is left to protect. Guards against destroying a
brand-new, never-published node's only draft, which would violate the
(head | draft) invariant every other method here assumes holds; the
view's Destroy/Discard button is gated the same way.
nodes#edit now calls lock_for_editing! instead of find_or_create_draft,
and always displays autosave || draft || head, resurrecting an
abandoned session's unsaved content by default with an explicit flash
explaining what's shown and how to get back to the last saved version.
The view drops content_for :subnavigation entirely: Show becomes
"Unlock + Back", Preview stays a plain link, metadata's own <details>
already replaced the old toggle, and Publish moves off this page for
good, per the earlier decision to manage the publish lifecycle
entirely from nodes#show. Save Draft and Save + Unlock + Exit appear
both above and below the form, given the body field alone runs 600px
on desktop.
|
|
Two real bugs surfaced by the full test suite, not by the new tests
written for this feature -- both were latent the moment lock_for_editing!
and save_draft! replaced find_or_create_draft, and only visible once an
existing test exercised the exact path each one lived in.
lock_for_editing! never stamped user/editor onto a draft that already
existed when the lock was acquired. find_or_create_draft used to do this
as part of acquiring the lock; splitting lock acquisition from draft
creation dropped it entirely, since it looked like draft-creation logic
rather than locking logic. Restored as an explicit step: claim authorship
only if none is set yet, editorship unconditionally, matching the old
behavior exactly.
save_draft!'s "no draft yet" branch set user/editor before calling
clone_attributes_from, whose first line is an unconditional self.reload
-- silently discarding both, since neither had been persisted yet.
clone_attributes_from also always copies published_at from its source
without the ||= guard used for template_name, and the source here is
always the autosave, whose published_at is never anything but nil --
meaning every single promotion, not just the first, was quietly
resetting a published page's published_at, which publish_draft!'s own
||= Time.now would then treat as never-published and re-stamp. Fixed by
running clone_attributes_from first on both branches, then applying
user/editor/published_at afterward, exactly as the existing-draft branch
already happened to do by accident.
Four controller tests updated to insert a real put :update between
get :edit and assertions that used to be true immediately after
visiting edit -- deferred draft creation means edit alone no longer
produces one, which is the intended consequence of this whole
redesign, not something these tests were meant to catch.
|
|
|
|
Fallout from the earlier nodes#show heading change, which replaced the
Title row with an <h1> -- unrelated to the autosave work in the
preceding commits.
|
|
Previously piggybacked on the main form's action and relied on a
.js.erb response to update #flash; now posts to the dedicated
autosave route with an explicit _method=put override, and reacts to
a 423 by stopping the timer and showing a persistent banner rather
than letting the next Save discover the conflict on its own.
|
|
Route Save and autosave through the new Node methods
update now promotes the current autosave into the draft via
save_draft! rather than writing submitted params directly; autosave
gets its own PUT member route so PATCH update can mean "deliberate
save" specifically, matching every other custom action on this
resource. Both now require the lock to already be held rather than
acquiring it themselves, which is a deliberate narrowing: through the
real UI this is always true, since neither can fire before edit has
loaded. Two existing tests called update directly with no prior
edit, which the old code tolerated by acquiring the lock as a side
effect; updated to call edit first instead of loosening the
guarantee back open.
NodesController#edit itself is unchanged and still calls the old
find_or_create_draft, so drafts are still created eagerly on entering
edit for now -- switching that over is the next step, not this one.
|
|
Introduces autosave_id as a third, unversioned layer above draft/head,
with lock_for_editing!, autosave!, and save_draft! as the new entry
points. Also fixes a real bug in wipe_draft!: its "no draft" branch
unconditionally released the lock, which was safe when "no draft" only
ever meant "nothing is happening" — no longer true now that a lock can
exist with only an autosave beneath it. lock_for_editing! deliberately
does not call wipe_draft! at all, for the same reason: an intruder
calling it while a lock was genuinely held would otherwise silently
steal it via wipe_draft!'s own unlock side effect, caught by the new
two-user lock test.
|
|
|
|
|
|
|
|
|
|
|