| Age | Commit message (Collapse) | Author |
|
|
|
|
|
|
|
|
|
|
|
|
|
Add has_secure_password and bcrypt while retaining compatibility with
legacy SHA-1 password hashes. Existing users are upgraded to
password_digest on their next successful login.
Add regression tests covering both legacy and modern authentication
paths.
|
|
|
|
|
|
|
|
|
|
Weekdays appearing on a subset of the weeks of the month are now
aggregated into a simpler form.
|
|
Improve the humanizer to also understand weekly patters that
an editor can manually select, on top of the single week
rules.
Extend template and javascript controller to reflect these
changes.
|
|
Also clean up some instance variable assignments on the way.
|
|
Now there's a separate revisions history and a full action
log for a node. To reflect the extended scope, the section
has been renamed to history.
|
|
Several minor improvements to the action log presentation:
* Now a table with date and human readable presentation as
rows is displayed
* If no changes in a title were detected, the old version
is omitted
* The "inferred" flag is demoted to the end of the line
* You can zoom in on the node's history directly from a
log line
* byline for the first publish action is preserved
* Revisions are directly linked to when a new one i
published
|
|
|
|
NodeActionsController#index lists entries newest-first, filterable
by node_id or user_id -- the two zoom shapes the log was designed
around. Rendering goes through NodeActionsHelper.action_summary,
which builds one sentence per entry from metadata alone, so entries
referencing deleted users or nodes render from their snapshots;
live associations only upgrade names to links. Unknown verbs
degrade to a generic sentence rather than an error, since the log
outlives its vocabulary. The helper is the escaping boundary:
every metadata value passes through h() before assembly.
Actor names link to the log's own user zoom rather than the unused
users page -- inspecting a suspicious user's other actions is the
intended workflow. Publish entries with a translation_diff expose
a collapsed per-locale change table linking out to the revision
itself. Sentences live in en.yml/de.yml following the existing
widget-string convention. nodes#show links to its node's zoomed
log.
|
|
A contract comment above NodeAction.record! now specifies every
verb's metadata shape. NodeAction.head_diff computes the publish
diff between an outgoing head and its replacement -- default-locale
title pair always, author/tags pairs and template/assets/abstract/
body flags only when changed, and a per-locale translation_diff
with added/removed/changed status. It is a pure function of its two
pages, shared by publish, rollback, and the future backfill, and
reads translation rows directly so fallbacks never masquerade as
content.
publish entries carry via ("draft" or "revision"); restore_revision!
is now transactional, takes the acting user, and logs through the
same diff. Staged slug/parent changes applied at publish log a move
entry with the path pair. Node creation logs a create entry with
initial title and path. The draft-scoped translation_destroy writer
is retired -- locale removal is recorded by the publish diff, where
it becomes public fact.
|
|
|
|
|
|
node_id/page_id/user_id are lookup and ordering only -- all three
nullify on delete, so an entry outlives its actor and its subject.
Everything that must survive those deletions lives in a mandatory
metadata jsonb written once at creation: the actor's username, the
node's human-readable name (pinned to the default locale), and
action-specific extras such as publish's title from/to.
NodeAction.record! is the single constructor, so every entry gets
the same baseline metadata without each call site re-implementing
it. occurred_at is one field for live and backfilled entries alike;
inferred_from distinguishes them -- nil means witnessed at the
moment it happened, populated names how a backfilled entry was
estimated.
Instrumented so far: publish (crediting the actual publisher,
threaded through from the controller -- previously nobody had the
act of publishing recorded anywhere), revert's discard_autosave and
destroy_draft branches, and translation destroy. publish_draft! now
runs in a transaction so the promotion and its log entry land
together. The remaining verbs follow once this mechanism has proven
itself.
|
|
nodes/recent rendered the generic node-listing partial (id,
actions, current lock owner, revision number) -- nothing
resembling what 'See all recent changes' actually promised to
show more of. Both surfaces now render the same extracted
partial, so they can't tell two different stories again.
Also: head.editor correctly identifies who last wrote the content
sitting in head, but publish_draft! never touches editor at all
-- nobody has ever had the specific act of publishing recorded
anywhere. 'Published by X' was a claim the data never supported,
only incidentally true when the same person who drafted something
also happened to publish it. Relabeled to 'last edited by' -- same
data, accurately described. A real publish-actor is separate,
future work, once the action log exists.
Basic list styling added for the full page, which had never
rendered outside the widget's own compact five-line context
before.
|
|
|
|
|
|
RelatedAssetsController#create's JSON includes large_url,
original_url, and name alongside thumb_url/url; the related-asset
list items in both editors carry them as data-large-url,
data-original-url, and data-name.
ccc.css gets the public-facing .inline-image--full/--half/--left/
--right rules -- admin.css and TinyMCE's content_style cover the
cockpit and in-editor renderings respectively.
|
|
Custom TinyMCE toolbar button opens a picker scoped to a page's own
attached images. Selecting one and a placement (full width, left,
right) inserts <a class="glightbox" data-gallery="page-N">
wrapping an <img>, sharing the existing headline-image lightbox
gallery. No native TinyMCE image dialog or resize handles --
placement is fixed to the three options via CSS classes only.
extended_valid_elements defines a and img explicitly and
symmetrically; valid_classes allows the placement classes and
glightbox specifically. content_style renders placement inside the
editing iframe, since ccc.css doesn't load there.
A pre-save pass removes any a.glightbox left without an img,
covering in-editor deletion regardless of how it happened.
Alt text comes from the asset's name, escaped.
A badge marks whichever attached image sits at position 0, since
_headline_image.html.erb always renders that position as the
page's automatic headline. Shown in the sidebar Images list and in
the picker grid.
|
|
mtime_busted_path only ever covered the four admin-only static
files. The public layout has the identical exposure -- ccc.css,
glightbox's vendored CSS/JS, and public.js are all served straight
from public/ with no pipeline fingerprinting either -- just never
got the same treatment.
|
|
STYLES restructured from a single geometry: string per style to a
full args: array, since this fix needs -gravity and -extent
alongside -resize, which one geometry string can't express.
|
|
Sized generously (1600x1600>) rather than against today's cramped
490px content column specifically -- shrinking an oversized source
to fit a narrow container is free and looks fine; a source too
small for a future, wider redesign has no fix short of re-uploading
every image. Purely additive: nothing reads :large yet, and
generate_variants already loops over the whole STYLES hash
generically, so no other code needed to change for new uploads to
start getting it. Test suite's own hardcoded style lists updated in
the same commit so they don't quietly drift out of sync with what
the app actually defines.
|
|
Both had already lost their reason to exist as production API:
wipe_draft!'s one remaining callsite (nodes#show) was removed two
sessions ago, and find_or_create_draft had zero production callers
left at all -- confirmed by a fresh grep, not assumed -- every one of
its ~65 call sites was test setup, unrelated to what those tests
actually cover.
wipe_draft! is deleted outright, along with its two tests -- the
lock/draft/autosave cleanup it silently performed already has
explicit, always-visible manual equivalents (Unlock, Discard
Autosave, Destroy Draft), so nothing real is lost.
find_or_create_draft moves to test_helper.rb as a plain method on
ActiveSupport::TestCase, alongside the create_node_with_draft/
create_node_with_published_page helpers already living there --
extending the framework's own designated test-extension point
rather than reopening the Node model from test code. Its three
tests of real dispatch behavior (idempotency on repeat calls, and
raising when a second user contends for the lock) are kept, since
~65 other tests depend on this helper actually working correctly;
only the call syntax changed, from node.find_or_create_draft(user)
to find_or_create_draft(node, user).
|
|
_chapter.html.erb (the aggregator partial used by the erfas/
chaostreffs overview pages) never rendered an image at all --
editors attaching a hackerspace photo via the asset picker would see
zero visible effect on exactly the pages they'd check first.
chapter_detail.html.erb (the individual page template) had the same
gap. Both fixed by reusing existing infrastructure rather than
inventing anything: the overview list gets a small, aspect-preserving
:thumb image beside the heading; the detail page gets the app's
existing headline_image helper, the same one every other full-page
template already uses.
|
|
Hidden by default, shares horizontal space with the body editor once
opened rather than a separate section, and only requests the iframe's
src on first toggle -- no preview traffic at all until someone
actually wants to see it. Refreshes automatically on every successful
autosave; a separate force-render button re-attempts a save-and-
refresh cycle immediately, for when the normal 7-second cycle has
been interrupted (a lock-lost error, a transient failure) and waiting
for the next tick isn't good enough.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
Every promotion (autosave creation, draft creation, draft save) ran
every locale's translation through delete-all-then-recreate
unconditionally, giving every locale a fresh created_at/updated_at
regardless of whether its content actually changed. Confirmed via
Node.find(546).draft.translations -- both DE and EN shared one
identical timestamp despite only EN having been edited.
This silently defeated Page.find_with_outdated_translations' whole
staleness comparison: every save reset every locale back into
lockstep, so timestamps could never actually drift apart the way
the feature depends on. The existing test for it only ever passed
by manually backdating a timestamp with record_timestamps = false,
bypassing normal save flow entirely -- not something that happens
through ordinary editing.
Now updates a matching locale's translation only when its own
attributes actually differ, creates one genuinely new, and removes
one genuinely gone from the source -- same end state, but real
per-locale timestamps survive an unrelated save. search_vector is
excluded from both the comparison and the copied attributes: it's
DB-trigger-maintained from title/abstract, not real content, and
comparing a precomputed tsvector risked a false 'changed' from
representation noise alone.
Also reloads the source's translations association, not just
self's -- clone_attributes_from previously only guaranteed a fresh
self, leaving any caller holding a page reference across an earlier
mutation vulnerable to reading a stale cached association.
|
|
|
|
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.
|
|
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.
|
|
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.
|
|
Its other two branches both require a day's inactivity before
touching anything; this one didn't, so a lock acquired seconds ago
with nothing drafted yet looked identical to one abandoned for a
week -- confirmed with a throwaway script showing a lock cleared
inside a single method call. Guarded on autosave.nil? specifically,
since the method's first line already guarantees any autosave
reaching this branch is a day old by construction.
|