summaryrefslogtreecommitdiff
path: root/app/controllers
AgeCommit message (Collapse)Author
14 hoursMake revisions locale awareerdgeist
15 hoursStore return_to before resetting session in loging. Fixes returning to an admin page before logging inerdgeist
21 hoursRemove redundant default translation from the translations subsectionerdgeist
21 hoursMake revision diffs locale-aware, defaulting to whichever locale changederdgeist
diff_against compared title/abstract/body under whatever I18n.locale happened to be ambient, with no concept of 'diff this translation specifically' -- so a change confined to one locale was invisible to Diff Head vs. Draft regardless of which locale you were looking at when you clicked it, exactly the 'yields nothing' complaint from earlier this session. Page#diff_against gains a locale: keyword, additive only -- nil preserves the exact original ambient-locale behavior every existing caller and test already depends on; passing a locale switches to reading each side's actual PageTranslation row directly, same fallback-free reasoning as Page#translation_summary. Page#locale_diff_summary reports one entry per locale present on either side, so an added or removed translation counts as a change even where content matches everywhere it exists on both. RevisionsController#diff now resolves a real locale before diffing -- defaulting to whichever locale actually changed, falling back to the default locale only when nothing did -- and the view carries that locale through every existing control (view toggle, layer-pair buttons, the revision-select form) so it and the view/layer-pair axis stay independently selectable rather than resetting each other.
23 hoursFix shared preview redirecting to admin for an ordinary new drafterdgeist
superseded/currently_public used published_at presence as a proxy for page-row relevance, but published_at is carried forward onto every new draft descended from previously-published content (Page#clone_attributes_from, Node#save_draft!) -- so it read true for any draft on an already-published node, whether or not that specific draft had ever gone live itself. Compare draft_id/head_id identity directly instead; no timestamp involved. A stale (superseded) link now redirects to the live public page rather than an admin URL an anonymous holder can't reach anyway.
23 hoursWire real autosave into translation editingerdgeist
The shared TinyMCE setup initializes cccms.setup_autosave() on any page with a textarea.with_editor, unconditionally starting a 7-second interval that submits to the form's data-autosave-url -- which the translation edit form never set, so the interval PUT the current page URL itself and 404'd on a nonexistent route. Fixed by actually giving it something to talk to, rather than suppressing it: a real autosave endpoint, and update now goes through Node#autosave!/#save_draft! -- the same pipeline the primary editor uses, fixed earlier this session for exactly this kind of cross-locale carryover. This was the autosave-buffer parity already flagged as due after the proof of concept; the shared JS just forced the timing.
24 hoursAdd per-node translation management: list, edit, destroy, compareerdgeist
Replaces the old locale-switch-and-edit-the-same-screen workflow, which conflated presentation locale with content locale and let an editor silently drift into editing the wrong language with no persistent signal that anything had changed. Non-default-locale content now has its own explicit routes and screens, never sharing a route param with the ambient chrome locale. - PageTranslationsController: index/show/edit/update/destroy, scoped to Page.non_default_locales; update handles first-time creation too, so there's no separate new/create step. - Reads go through the actual PageTranslation row (Page#translation_summary), never through the Globalize fallback-bearing accessor -- fallback is correct for public rendering but wrong for editing, where a missing translation needs to look empty, not borrowed from another locale. - Translations ride on the page's own draft/head cycle; no independent publish state. - nodes#show gains a Translations section (per-locale Lock+Edit / Create+Lock, Destroy, a link into the read-only Compare view) and a locale indicator on its own default-locale content; nodes#edit, nodes#update, and nodes#autosave are pinned to the default locale via Globalize.with_locale regardless of the ambient route locale. - nodes#show no longer double-loads the node or calls wipe_draft! on every view (see previous commit for why that's now safe). - .preview_link_row is renamed .aligned_action_row now that it has a second real consumer.
35 hoursAsset picker: attach/detach/reorder UI, read-only view, autosave fixerdgeist
Replaces nodes#edit's old Images section -- a hidden panel dumping every image asset in the system unfiltered (#image_browser) plus a raw drag-and-drop box (#image_box) -- with a small search-and-click picker built on the endpoint from the last two commits. Attaching posts immediately and appends the new thumbnail via a cloned <template> -- icons only render correctly through the Rails helper server-side, so the template holds real, pre-rendered markup for JS to clone rather than duplicating raw SVG in a JS string. Reordering is jQuery UI sortable on the small attached list only, with a dedicated drag handle rather than the whole thumbnail. Two bugs caught while click-testing, fixed here rather than shipped and patched after: the search panel never closed after attaching an image, since the success handler re-triggered focus to keep it open for attaching several in a row -- which meant it just re-populated itself forever instead of signaling "done." Fixed to close explicitly; a click-outside-closes handler was added alongside it, matching the affordance the top-bar search already has. A real, independent, pre-existing data bug surfaced during the same testing: Node#autosave!'s first-time-creation branch never carried related assets forward from whatever page was previously current -- attach an image, let autosave fire once, and it silently landed on a fresh, assetless Page row. Long-dormant, not introduced by this work, just finally exercised by something that made it visible. Fixed inside the `unless self.autosave` guard specifically -- running this on every call, not just creation, would overwrite anything attached directly to an existing autosave in between, a worse bug than the one being fixed. nodes#show gains a read-only Images section, rendered only when a page actually has attached images, so an attachment can be confirmed present without entering the edit/lock cycle -- useful on its own, and specifically useful the next time an asset bug needs investigating. Its thumbnail CSS is shared with the edit view's picker via a class (.thumbnail_list) rather than duplicated under a second name.
37 hoursAdd loadOnFocus to initSearchPicker; asset search shows recent by defaulterdgeist
initSearchPicker's input handler has always short-circuited on an empty term -- correct for every existing picker, none of which want results appearing before anything's typed. The asset picker does want exactly that (show the last few uploaded images without requiring a search first), so this adds an opt-in loadOnFocus option instead of changing the shared default: it fires once when the input gains focus with no term yet, reusing the same request/render path a real search uses rather than a separate code path. The AJAX call itself was pulled out into a named runSearch function so both triggers could share it without duplicating the success/render logic. RelatedAssetsController#search now treats a blank term as "show the most recently created, unattached images" (limit 5) rather than returning nothing, matching the new client behavior. A real search term still returns up to 10 name-matched results, unchanged.
37 hoursAdd RelatedAssetsController: search, attach, detach, reordererdgeist
Backend for the asset-picker rebuild -- replaces the plan to dump every image asset in the system into a hidden, unfiltered browse panel on every node edit (the actual current behavior, confirmed by reading nodes/edit.html.erb directly) with a small, name-scoped search endpoint plus create/destroy/update for attach, detach, and reorder. No schema change needed -- RelatedAsset already had everything this requires (asset_id, page_id, an acts_as_list position). search excludes assets already attached to the page, keeping results meaningfully small given hundreds of assets total but only a handful per node in practice. create is find_or_create_by! rather than a bare create!, guarding against the same asset being attached twice from two separate search results. update leans on acts_as_list's own insert_at rather than custom position-shifting logic. Node#editable_page (autosave || draft || head) is extracted since this is now its third call site with identical logic -- deliberately not touching nodes#show, which resolves draft || head without autosave on purpose, a different and correct semantic for "current committed state" versus "what's actively being edited."
41 hoursRebuild the admin dashboard: icon nav, search, signposts, widgetserdgeist
Replaces the old admin#index wizard -- accreted over years, never designed as a whole -- with the dashboard settled on this session: a three-icon nav (dashboard/search/log out, no locale selector), a nodes-first search bar, four task signposts, and two symmetric widgets (drafts/autosaves, recent changes) with a quiet housekeeping row beneath them. Node.recently_changed now filters and orders by the head page's own updated_at instead of the node's blanket timestamp, so a lock/unlock cycle with no actual publish no longer surfaces here, and the original publisher is no longer misattributed to someone else's housekeeping action. This also restores the "published" qualifier on each entry, which the query previously couldn't guarantee was true. @mynodes and its dedicated "My Work" table are retired along with the old wizard -- "Continue my work" is a link to the existing, already- correct NodesController#mine instead of a second, duplicate query. Its one dedicated test (dedup across multiple revisions by the same user) is ported to nodes_controller_test.rb, since mine already carries the same .distinct protection the old query did; it just had no test of its own until now.
48 hoursAdd a grouped tag+node search endpoint for the upcoming dashboarderdgeist
AdminController#dashboard_search returns tags and nodes as separate, labeled groups (via ActsAsTaggableOn::Tag.named_like and Node.editor_search) rather than the flat per-node list every existing picker returns. initSearchPicker gains a renderResults callback option that, when given, replaces the default per-node rendering entirely -- lets the dashboard render its two labeled groups without a second, parallel picker implementation. resultsHeaderHtml (the "Press Enter to see all results" hint) is now threaded through as a third argument to renderResults, so a picker with custom rendering can still show it -- previously only the default per-node branch ever did. Tags link straight to the existing /admin/nodes/tags/:tag view rather than becoming an in-place filter chip.
2 daysExtract drafts/recent query logic from controller into Nodeerdgeist
Node.drafts_and_autosaves and Node.recently_changed replace inline query logic in NodesController#drafts/#recent -- pure refactor, no behavior change for either action. The real reason: the dashboard's upcoming abridged widgets need the exact same queries the full pages already use, just limited and (for drafts) sorted with the current user's own locked nodes first. Better to share one method than let a widget and a full page quietly drift onto two versions of "what counts as a current draft." current_user_id: is an explicit, optional argument rather than a separate method -- ordering by lock ownership only applies when a user is given; omitted entirely, it's pure recency, exactly today's behavior. Needed Arel.sql wrapping a sanitized CASE expression before .order would accept it -- sanitize_sql_array only vouches for the values inside the string, a separate Rails safety check still refuses any string shaped like more than a plain column reference unless it's explicitly marked as already-vetted.
2 daysUnify the four field-level pickers, fix search visibilityerdgeist
menu_items/parent_search/move_to_search/event_search each duplicated their own debounce-free AJAX call and result rendering. Replaced with one shared initSearchPicker, taking each picker's distinctive behavior (parent_search's live path preview, event_search's title hint) as a callback rather than a whole reimplementation. Adds a real debounce with an out-of-order-response guard -- none of the four had either before. admin_search (the Alt+F top-bar search) now shares the same function via its own url/isActive/header options, gaining the same guard and fixing an inconsistency of its own (it previously always slid its panel open, even on zero results). Each picker's results container gets its own id instead of sharing was ever supposed to be on screen at once, an assumption with no actual enforcement behind it. Styling moved from an id-pair (#menu_search_results, #search_results) to a shared .search_results class so a future picker never needs this file touched again. menu_search and the admin top-bar search now call Node.editor_search instead of the public, head-only Node.search -- both are admin-only, authenticated views, and had no reason to inherit the public search's "can't find a draft" limitation. The always-ignored :per_page => 1000 on the latter is gone too; Node.search's second argument was never read by the method at all. Also removed a stale #metadata a { text-transform: lowercase } rule, found while verifying the above -- written for the pre-subnav-removal expand-toggle, which no longer exists; it had been silently lowercasing nodes#edit's own, unrelated #metadata div (including move_to_search's results) by id coincidence ever since. #main_navigation and #overview_toggle intentionally left capitalized rather than special-cased -- both belong to the nav bar already slated to shrink to three icons, not worth polishing on the way out.
3 daysGive the sitemap its own view, with collapse and descendant countserdgeist
Extracted from admin#index's inline table into NodesController#sitemap. Nested <details>/<summary> per branch, one linear pass over the existing flat [node, level] list (no added queries) -- each node's own descendant count computed the same way, via a small stack rather than re-walking the tree per node. Branches under updates/, club/erfas, club/chaostreffs, and disclosure start collapsed by default (CccConventions::SITEMAP_COLLAPSED_PATHS); any branch currently collapsed, whether by that default or because someone just closed it, is highlighted via a plain :not([open]) selector -- no state tracked outside the DOM itself. Dropped the update?-post exclusion this view used to rely on -- no longer needed now that updates/ collapses instead of being filtered out, so its real children (previously silently absent) now show up correctly. admin#index's own, separate @sitemap query is unchanged; that view has no collapse mechanism to compensate and wasn't part of this.
3 daysAdd drafts/recent/mine/chapters admin node viewserdgeist
Four NodesController actions -- drafts, recent, mine, chapters -- each building its own base scope, sharing one private method (index_matching) for search narrowing and pagination. Wizard rewrite to link into these instead of rendering its own tables is a separate, later step. Node.editor_search backs the shared "q" narrowing: an ILIKE substring match against title/abstract on whichever of head or draft is present, splitting the term on whitespace and requiring every word to match somewhere independently, not as one phrase, since real words can end up separated by markup in the underlying HTML. Deliberately separate from Node.search, the public content search, which stays tsvector-based and head-only. chapters generalizes into /admin/nodes/tags/:tags for an arbitrary tag list (OR'd, not AND'd), sharing the controller action but rendering its own template rather than branching inside one view.
4 daysFix current_user/uniq inconsistencies in admin#index, remove dead queryerdgeist
@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.
5 daysDestroying Draft or Discarding Autosave drops you where you lefterdgeist
5 daysAdd head/draft/autosave layer comparison at three UI entry pointserdgeist
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".
5 daysRetire vendored cacycle_diff.js for a diff-lcs-based diff viewerdgeist
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].
5 daysAdd an events view for events without node, part 2erdgeist
5 daysPaginate Events indexerdgeist
5 daysFix malformed-HTML fallout across RSS, link rewriting, and flash messagingerdgeist
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.
5 daysMajor resource view cleanuperdgeist
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.
6 daysAdd the Status section to nodes#show and rebuild nodes#edit's action barerdgeist
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.
6 daysFix revert being unusable from an unlocked nodes#showerdgeist
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.
6 daysFix blank-title validation and its lost form state on re-rendererdgeist
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.
6 daysAdd revert!/discard and rebuild nodes#edit around the new hierarchyerdgeist
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.
6 daysFix tests for the new autosave routingerdgeist
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.
8 daysAdd context-aware child-creation shortcuts to nodes#showerdgeist
parent_match Procs on CccConventions::NODE_KINDS, matched against unique_path, decide which "add child" kinds show on a given node. Fixes nodes#new not honoring a pre-selected kind (radio group and parent-field visibility both defaulted to "generic" unconditionally).
8 daysReplace awesome_nested_set with a plain parent_id-based NestedTree concernerdgeist
Root-caused this session: appending a child to any node never widened that parent's own rgt boundary, on the pinned revision (Gemfile tracked main directly, chasing a too-conservative gemspec constraint - not, as first assumed, a deliberate pin to avoid a known bug). Reproduced cleanly on a single ordinary create with no concurrency and no bulk operation involved, confirmed via the gem's own SetValidator, then confirmed as the root cause of nodes_controller_test.rb's 3 long-standing "pre-existing" failures - not three separate mysteries, one bug. admin_controller's sitemap needed its own real conversion, not just a drop-in: awesome_nested_set's lft column implicitly provided correct depth-first tree order for free, which the old code combined with a separate class-level each_with_level iterator. Both replaced by one method, self_and_descendants_ordered_with_level, computing an ordered [node, level] list in a single query-then-walk pass - checked against the actual view template first (admin/index.html.erb) rather than assumed, since it relies on list order alone to render correct visual nesting. lft/rgt/depth columns intentionally left in schema, unused - dropping them is a separate, deliberately deferred migration once this is proven running for a while, not bundled with the behavior change.
8 daysFix shared preview links breaking after a second publisherdgeist
A token's page could stop being node.head_id (superseded by a newer draft) while still being published_at.present? - the old check only compared against the current head, so a superseded page fell through to direct rendering instead of redirecting, serving a stale frozen snapshot indefinitely instead of the current live content. Also handles scheduled publishing correctly: a page can be head_id but not yet public? (published_at in the future) - that case must still render directly, not redirect into a 404 on the not-yet-live public URL.
8 daysAdd public preview links for draftserdgeist
Lets editors share a draft with people outside the admin system, via a random per-page token rather than an enumerable id - /preview/<token>. shared_previews#show is intentionally unauthenticated. Redirects to the real public URL once the page is published. Surfaced on nodes#show (Admin Preview + Public Preview, next to Public Link) as generate/revoke buttons.
9 daysWhitespace fixeserdgeist
9 daysAdd erfa/chaostreff node-kind creation conventionserdgeist
lib/ccc_conventions.rb: NODE_KINDS registry replaces the kind-specific branches previously scattered across nodes_controller#create (tags), unique_path-position check). Each kind declares its parent lookup (a Proc - Update's "update"/"press_release" continue to genuinely find-or-create their year-folder via Update.find_or_create_parent; erfa/chaostreff use a plain find_by_unique_name!, since their parent nodes are fixed and a missing one should fail loudly, not be silently created), its tags, and (new) its default template. Node gains a default_template_name column (migration, with backfill for existing update-tree nodes via node.update? - reusing that method rather than re-deriving its unique_path check in raw SQL). Page#set_template now inherits from node.default_template_name, falling back to the old update?-based check only when the column is blank, and only fills in template_name when nothing's already been explicitly chosen - a deliberate change from the previous behavior, which unconditionally overwrote template_name on every save regardless of manual selection. Node#update? itself is unchanged and still used as-is by admin_controller's sitemap filtering - a genuinely different, still valid use of that check. "generic" stays special-cased in the controller, parametrized by params[:parent_id] at request time - doesn't fit "kind implies a fixed lookup" and isn't in the registry. nodes#new's four hardcoded radio buttons and nodes_controller's kind-specific case/when branches are both replaced by iterating/looking up this one registry - adding a new kind now means one new hash entry, not four scattered edits.
9 daysFix admin search results colliding with other search widgetserdgeist
layouts/admin.html.erb's top-bar search results div shared id "search_results" with nodes#new/nodes#edit's parent-search widget - since the layout renders on every admin page, whichever widget's JS ran last silently won the shared ID, putting top-bar results into the parent-search box on affected pages. Renamed to "menu_search_results" and updated admin_search.js to match. Also modernizes admin_search.js's event bindings from keyup/keydown/ keypress/paste/cut + .attr("value") to input + .val() throughout (menu_items, parent_search, move_to_search) - the multi-event binding was working around old IE compatibility that .val() + "input" already handles correctly. parent_search's result rendering now matches menu_search's convention (real <p>/<a> markup with a .result_path span) instead of a bare <a> in a throwaway wrapper div, so the same CSS rule now correctly applies to both. menu_search's JSON response gains node_path per result, matching what admin_search's own results already provide - not yet consumed by parent_search/move_to_search, which still render click-to-select links rather than navigable ones (correct for their purpose - selecting a value, not leaving the page). Known remaining gap, not fixed here: menu_items, parent_search, and move_to_search still all target the literal id "search_results" between themselves. No live collision today since none of the three currently share a page, but it's the same fragility as the bug above - tracked alongside the existing menu_items/parent_search/move_to_search consolidation backlog item rather than treated as resolved.
11 daysFix safe_return_to being uncallable from controllerserdgeist
Defined only in ApplicationHelper, which Rails auto-mixes into views but not controllers - so events#create and events#update, which call it directly, have been broken since introduction. Likely unexercised until now because every existing event was created via node.events.create! in the seed script, never through a real POST. Moved to ApplicationController as a protected method + helper_method declaration, so both controllers and views can call it (form_error_ messages stays in ApplicationHelper - it's genuinely view-only, content_tag isn't available in a controller either). Logic unchanged, caught by the new EventsController tests in the previous commit.
11 daysAdd self-service event creation from nodes#showerdgeist
nodes#show's events table now renders unconditionally (previously hidden entirely for a zero-event node) and gains an "add event" link. The suggested tag_list is derived from the page's own category tags via NodesHelper::DEFAULT_EVENT_TAG_BY_PAGE_TAG (erfa-detail/ chaostreff-detail -> open-day) rather than hardcoded, and degrades to a blank field for any node whose tags don't match - deliberately universal, not chapter-specific, since Updates have historically carried event dates the same way. events#new surfaces *why* the tag was pre-filled via flash.now (not flash - this is a same-request render, not a redirect), using an explicit auto_tag_source param passed alongside tag_list rather than having the controller re-derive the reason from node_id. No destroy link added to nodes#show's events list - deliberate, per existing subnav-semantics convention (destructive actions live on the resource's own views). Covered directly by test. Adds real coverage for EventsController, previously 100% commented-out scaffold, plus unit tests for the new tag-mapping helper and the weekday-abbreviation helpers from the prior commit.
13 daysClean up events views: return_to, subnav, remove custom_rruleerdgeist
- events_controller: wire return_to through show and new actions; create respects return_to with fallback to node/event path; new pre-populates node_id and tag_list from params - events/edit: remove custom_rrule checkbox; node link removed from subnav (use show for node navigation); destroy button added - events/new: remove custom_rrule checkbox; add return_to hidden field and back link; tag_list field added - events/show: fix back link via safe_return_to; add node link to subnav; add destroy button; remove custom_rrule display; show humanized rrule below raw string - events/index: destructive class on destroy button; node_id column replaced with node link; show/edit links carry return_to - nodes/show: add show/edit links to event entries with return_to
14 daysFix events CRUD for standalone events and add events to admin menuerdgeist
- event_params now permits title, description, is_primary - event_information helper lists all node.events, not just the first - Occurrence.generate handles nil node (standalone events) - Page.aggregate order_by title uses correlated subquery to avoid GROUP BY conflict with tag-filter path; order_direction whitelisted to ASC/DESC to prevent SQL injection - Events link added to admin menu bar - events/index shows title, is_primary; drops latitude/longitude columns
14 daysPermit :admin in params only for adminserdgeist
14 daysRemove recent_changes RSS feed. It leaks unpublished drafts and the basic auth was only symbolicerdgeist
14 daysPrevent xss by not allowing return_to to point to javascript resourceserdgeist
2026-06-30Improve admin search overlay layout and behaviourerdgeist
- Widen overlay (300px -> min(520px, 90vw)), centre instead of hardcoded left:400px, so it scales from mobile to desktop - Split title and unique_name into separate JSON fields and DOM elements; two-line result layout (bold title, small grey monospace path) instead of "Title (path)" wrapping awkwardly - Add small margin between title and path line - Fix event handler stacking: keyup/escape/outside-click handlers were being rebound on every display_toggle call. Moved all bindings to initialize(), display_toggle() now only shows/hides - Switch search input from keyup to input event, catching paste and cut via mouse which keyup misses - Add Escape key and outside-click to dismiss the overlay - Stop clearing search box and results on close; reopening now preserves prior search, matching standard search UI behaviour - Link search results to node_path instead of edit_node_path, since opening edit auto-locks the node - Add "press Enter to see all results" hint in dropdown - Disable browser autocomplete on search input
2026-06-29Restore and improve admin node searcherdgeist
- Admin search broke when routing-filter was removed: hardcoded /admin/search and /admin/menu_search URLs in admin_search.js no longer matched the locale-scoped routes. Fix by emitting locale-aware URLs from the layout as JS variables. - Also fixes form submission (POST -> GET, missing = on form_tag), jQuery .attr("value") -> .val() for typeahead input reading, and template name for Rails 8 compatibility. - Adds a visible "search" link to the admin menu so editors can discover the feature without knowing the Alt+F shortcut. Search results now show node path alongside title and link directly to the edit view. Named route admin_menu_search added to routes.rb.
2026-06-27rss: add per-tag Atom feed at /rss/tags/:tag/updates.xmlerdgeist
- rss#tag_updates action: filters Page.heads by tag name, default locale, 20 items, same caching as updates feed - tag_updates.xml.builder: Atom feed with CGI.escapeHTML on title and summary, consistent with updates.xml.builder - tags/show.html.erb: add subscription link above article list - routes: two routes per existing pattern (format-less + .:format constrained to /xml/)
2026-06-27Stage 7 click-testing fixes (2)erdgeist
- nodes_controller: permit staged_slug and staged_parent_id in node params; these were silently dropped since strong parameters migration, breaking the two-phase slug/parent change workflow - file_attachment: add SVG support; vector files are copied to all style directories without rasterisation, preserving scalability in the browser - assets index/show: constrain image display with max-width/max-height via admin.css td img rule; fixes oversized SVG thumbnails while leaving raster variants unaffected
2026-06-27Stage 7: Rails 7.2 → 8.1 on Ruby 3.2.11erdgeist
- Bump Rails to 8.1.3 (Ruby unchanged at 3.2.11, new gemset rails8-upgrade) - config.load_defaults 8.1; merge app:update diffs for all environment files - Remove routing-filter 0.7.0; replace with native scope '(:locale)' in routes.rb and default_url_options in ApplicationController - Delete config/initializers/routing_filter_rails71_patch.rb - Replace vendored TinyMCE 3.x (~200 files) with tinymce-rails ~> 8.3; migrate admin_interface.js from jQuery .tinymce()/advanced theme to tinymce.init(); add config/tinymce.yml; note: TinyMCE 7+ is GPL - rails-i18n ~> 8.0 added explicitly (previously indirect dependency) - awesome_nested_set, acts-as-taggable-on pinned to git main/master (gemspec activerecord < 8.1 ceiling; no functional incompatibility; repin to version once upstream releases updated gemspecs) - globalize ~> 7.0, libxml-ruby ~> 5.0, nokogiri ~> 1.18, pg ~> 1.5 - sass-rails, coffee-rails, uglifier moved from :assets group to main (Sprockets 4 convention; :assets group no longer meaningful) - Node: head, draft, lock_owner marked belongs_to optional: true - Page: node, user, editor marked belongs_to optional: true - Static assets in public/images/ and public/javascripts/ referenced via plain HTML tags; Rails 8 load_defaults raises on pipeline helpers for undeclared assets - sessions_controller_test.rb: remove stale require and dead rescue_action - users_controller_test.rb: assert button[type=submit] not input[type=submit] (Rails 8 button_to renders <button> not <input>) - test_helper.rb: node.reload after children.create! (awesome_nested_set 3.9.0 does not refresh parent in memory after callback) - 129 runs, 339 assertions, 3 failures, 0 errors — identical baseline to 7.2
2026-06-26Fix RSS feeds, xmlparser initializererdgeist
2026-06-26Stage 4: Rails 5.2 -> 6.1 on Ruby 2.7.2erdgeist
- routing-filter 0.6.3 -> 0.7.0 (Rails 6.1 compatibility) - RSS named routes rss_xml/rss_rdf added - RouteWithParams workarounds: will_paginate_patch, content_path shim, safe_path helper - Paperclip removed, replaced with FileAttachment concern (preserves URL scheme) - Assets resource moved to /admin/assets (Sprockets middleware conflict) - ApplicationRecord base class added, all models migrated - Strong parameters added to Assets, Occurrences, Events, MenuItems controllers - update_attributes -> update throughout - render :nothing -> head :ok/:not_found throughout - language_selector rewritten (removes :overwrite_params) - Environment files updated for Rails 6.1 (eager_load, public_file_server, ActionMailer) - Arel::Visitors::DepthFirst and Integer/Float duration patches removed from test_helper - AssetsController tests added (10 tests covering upload, variants, destroy, auth) - ImageMagick geometry: 460x250! for headline crop (not # which is invalid in IM6) 129 runs, 311 assertions, 5 failures (all pre-existing), 0 errors