WFHB needs to edit certain events as they flow from the community-calendar database to the iframed viewer. The approach: add an eventOverrides object to the existing excluded.json file that WFHB already manages in GitHub. The community-calendar viewer already fetches this file via the ?exclude= query param; it just needs to apply overrides in addition to exclusions.
Two repos are involved:
community-calendar(Jon) — the shared viewer that any city can iframewfhb(Chuck) — the WFHB-specific moderator UI
File: xmlui/helpers.js
The function _filterExternalExclusions (line ~1419) currently reads window.externalExclusions and filters events by excluded sources, categories, and individual UIDs. After filtering, add a rewrite step that applies per-event overrides.
Add a new function right after _filterExternalExclusions:
var _applyExternalOverrides = function(events) {
var exc = window.externalExclusions;
if (!exc || !exc.eventOverrides) return events;
var overrides = exc.eventOverrides; // { "source_uid": { "title": "...", "location": "...", ... } }
return events.map(function(e) {
if (e.source_uid && overrides[e.source_uid]) {
return Object.assign({}, e, overrides[e.source_uid]);
}
return e;
});
};
window.applyExternalOverrides = function(events) {
return window.xsTraceWith
? window.xsTraceWith("applyExternalOverrides", function() { return _applyExternalOverrides(events); },
function(result) { return { inputCount: (events || []).length, overrideCount: Object.keys((window.externalExclusions || {}).eventOverrides || {}).length }; })
: _applyExternalOverrides(events);
};File: xmlui/Main.xmlui
Insert applyExternalOverrides into the processing pipeline. Currently (line ~140):
window.filterExternalExclusions(combinedEvents)
Change to:
window.applyExternalOverrides(window.filterExternalExclusions(combinedEvents))
Allow overrides for any display field: title, start_time, end_time, location, description, url, image_url, all_day, category. The override object is a partial — only specified fields are replaced.
{
"excludedSources": ["..."],
"excludedCategories": ["..."],
"excludedEvents": ["..."],
"reviewedEvents": ["..."],
"lastReviewed": "...",
"requireApproval": true,
"eventOverrides": {
"source-uid-abc": {
"title": "Corrected Event Title",
"location": "Updated Venue"
},
"source-uid-xyz": {
"start_time": "2026-04-20T19:00:00"
}
}
}The moderator app lives in the wfhb repo. It has two files: index.html (state management + helper functions) and Main.xmlui (UI). Both need changes.
1. Add eventOverrides to the state model.
In the _state declaration (line ~26), add eventOverrides:
var _state = {
excludedSources: [], excludedCategories: [], excludedEvents: {},
reviewedEvents: {}, lastReviewed: null, requireApproval: true,
eventOverrides: {} // <-- add this
};2. Load overrides from excluded.json on startup.
In the block that parses the loaded JSON (around line ~38), add:
_state.eventOverrides = loaded.eventOverrides || {};3. Add helper functions for managing overrides:
window.getEventOverrides = function() {
return getState().eventOverrides;
};
window.getEventOverride = function(sourceUid) {
return getState().eventOverrides[sourceUid] || null;
};
window.setEventOverride = function(sourceUid, field, value) {
var s = getState();
if (!s.eventOverrides[sourceUid]) s.eventOverrides[sourceUid] = {};
s.eventOverrides[sourceUid][field] = value;
};
window.removeEventOverride = function(sourceUid) {
delete getState().eventOverrides[sourceUid];
};
window.hasOverride = function(sourceUid) {
return !!getState().eventOverrides[sourceUid];
};4. Include overrides in buildExcludedContent.
In buildExcludedContent (line ~390), add eventOverrides to the data object:
var data = {
excludedSources: s.excludedSources.sort(),
excludedCategories: s.excludedCategories.sort(),
excludedEvents: Object.keys(s.excludedEvents).sort(),
reviewedEvents: Object.keys(s.reviewedEvents).sort(),
lastReviewed: s.lastReviewed || null,
requireApproval: s.requireApproval,
eventOverrides: s.eventOverrides // <-- add this
};5. Include overrides in hasUnsavedChanges / _savedSnapshot.
Add JSON.stringify(_state.eventOverrides) as an element in both the snapshot array (line ~121) and the comparison array in hasUnsavedChanges (line ~131). Do the same in getChangeSummary and markSaved.
For getChangeSummary, add a diff for overrides:
var savedOverrides = JSON.parse(saved[6] || '{}');
var curOverrides = _state.eventOverrides;
var overrideChanges = JSON.stringify(curOverrides) !== JSON.stringify(savedOverrides);
if (overrideChanges) parts.push('event edits changed');1. Add an edit dialog.
Add a ModalDialog for editing events, similar to the existing detailDialog. It should have TextBox fields for title, location, start_time, end_time, and a TextArea for description. Pre-populate from the event's current values (which may already have an override applied). Example:
<ModalDialog id="editDialog" title="Edit Event" var.editTitle="{''}" var.editLocation="{''}" var.editStartTime="{''}" var.editEndTime="{''}" var.editDescription="{''}" var.editUid="{''}">
<VStack gap="$space-2">
<Text fontSize="$fontSize-sm" value="Title" />
<TextBox initialValue="{editTitle}" onDidChange="(val) => editTitle = val" />
<Text fontSize="$fontSize-sm" value="Location" />
<TextBox initialValue="{editLocation}" onDidChange="(val) => editLocation = val" />
<Text fontSize="$fontSize-sm" value="Start time (ISO)" />
<TextBox initialValue="{editStartTime}" onDidChange="(val) => editStartTime = val" />
<Text fontSize="$fontSize-sm" value="End time (ISO)" />
<TextBox initialValue="{editEndTime}" onDidChange="(val) => editEndTime = val" />
<Text fontSize="$fontSize-sm" value="Description" />
<TextArea initialValue="{editDescription}" onDidChange="(val) => editDescription = val" rows="{4}" />
<HStack gap="$space-2" marginTop="$space-2">
<Button label="Save edits" onClick="() => {
if (editTitle !== $param.title) window.setEventOverride(editUid, 'title', editTitle);
if (editLocation !== $param.location) window.setEventOverride(editUid, 'location', editLocation);
if (editStartTime !== $param.start_time) window.setEventOverride(editUid, 'start_time', editStartTime);
if (editEndTime !== $param.end_time) window.setEventOverride(editUid, 'end_time', editEndTime);
if (editDescription !== $param.description) window.setEventOverride(editUid, 'description', editDescription);
refreshKey = refreshKey + 1;
editDialog.close()
}" />
<Button when="{window.hasOverride(editUid)}" label="Remove edits" variant="outlined" onClick="() => { window.removeEventOverride(editUid); refreshKey = refreshKey + 1; editDialog.close() }" />
<Button label="Cancel" variant="outlined" onClick="editDialog.close()" />
</HStack>
</VStack>
</ModalDialog>2. Add an Edit button to event cards.
In the Included tab's event cards (and optionally the New tab), add an Edit button next to the existing Exclude button:
<Button label="Edit" variant="outlined" size="sm" onClick="() => {
editTitle = $item.title;
editLocation = $item.location || '';
editStartTime = $item.start_time || '';
editEndTime = $item.end_time || '';
editDescription = $item.description || '';
editUid = $item.source_uid;
editDialog.open($item)
}" />3. Visual indicator for edited events.
Show that an event has been edited, e.g. add to each card:
<Text when="{window.hasOverride($item.source_uid)}" fontSize="$fontSize-xs" color="$color-primary-500" value="(edited)" />4. Apply overrides to the display in the moderator itself.
So the moderator shows the edited versions, add a function that applies overrides to the event list before display. In getVisibleEvents and related functions, or as a wrapper when passing events to the UI, map events through eventOverrides the same way the viewer will.
- Edit an event in the moderator, save to GitHub
- Load the community-calendar viewer with
?exclude=pointing to the wfhbexcluded.json - Verify the event displays with the overridden field values
- Verify the "Remove edits" button in the moderator restores original values
- Verify
hasUnsavedChangescorrectly detects override changes