sync Develop to Main for v2.1.0 release. - #2055
Conversation
Develop to Main Sync for GoDAM 2.0 Release
* feat(audio): media-type-aware editor + audio chapters/transcript UI Make the video editor media-type aware and build the full GoDAM audio experience (customization editor, block editor, and front end) on top of it, per the Audio Block + ToolsPanel designs. Editor foundation - Add pages/video-editor/config/mediaCapabilities.js — a capability descriptor keyed by media type (video/audio/image) that drives the tabs, default tab, allowed layer types, preview component, copy block, preview page and stats row. The video descriptor reproduces the prior behaviour exactly - Thread it through videoSlice (mediaType/allowedTabs/allowedLayerTypes, setMediaType, dynamic setCurrentTab), VideoEditor, EditorTabRail, EditorTopBar, SidebarLayers (allowedLayerTypes gate — image-editor seam), utils/index.js and redux/api/video.js Audio customization editor - Open audio attachments in 'audio mode': only Transcription + Chapters tabs, no stats row, no Preview button, a 'Save' label and an AudioCardPreview stage. Reuses the attachment-keyed Transcription and Chapters features Audio block (assets/src/blocks/godam-audio) - Inspector matches ToolsPanel.pdf using Gutenberg components: Audio Selection (primary Customize Audio, file row with size, Title, Description), Thumbnail (Upload Image) and Transcription (Show transcript toggle + Edit). Add showTranscript attribute; remove the caption feature - Add the Chapters/Transcript panel to the block canvas (tabs.js) and the front end (render.php + view.js), fed from rtgodam_meta.chapters and the saved transcript: click-to-seek, active-line sync, copy, collapse - Fix Copy Block to emit a valid self-closing godam/audio block with id/src/title/description/className (file URLs stripped) Shared audio UI - Single source of truth assets/src/css/_godam-audio-card.scss plus identical markup so the block editor, front end and customization preview render the same card, player and panel Analytics is intentionally excluded for audio; the image editor is only seam-prepared, not built. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(godam-audio): core-style thumbnail replace + Show Chapters toggle - Thumbnail control mirrors core/video PosterImage: clickable image, hover-reveal Replace/Remove overlay, Set-thumbnail empty state, DropZone - Audio file row is click-to-replace - Add Show Chapters toggle (default true), gated per-tab across editor + front end - Open all inspector panels by default - Transcript UI: design-matched cue list, WP copy icon, check mark on copy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: hydrate thumbnail + chapters for GoDAM-tab audio/video selections Store the Central-provided cover and chapters onto the virtual attachment when audio/video is picked from the GoDAM tab, and surface the audio cover in the media library grid, list view, and block. - prepare_godam_media_item: parse chapters (JSON string/array) into result - create_virtual_attachment: store rtgodam_media_audio_thumbnail meta + chapters in rtgodam_meta (shared block, runs for video + audio) - Register rtgodam_media_audio_thumbnail REST meta - video-metadata: inject audio thumbnail in media grid + list-view filters - godam-audio block: GoDAM ID-swap effect, thumbnail from Central (GoDAM tab) and from attachment meta (Media Library tab, race-guarded) * feat: server-rendered audio player + shortcode parity + Edit Audio button Render the custom audio player server-side (no native-controls flash), make the [godam_audio] shortcode match the block (enqueue view.js/style, stable class, title/thumbnail fallbacks), and add an Edit Audio button to the media library. * fix(audio-editor): show cover in editor preview + minor style tweaks Read the cover from meta.rtgodam_media_audio_thumbnail in AudioCardPreview (was the wrong video key at the top level), plus chapter-row width and media-library styling tweaks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): guard chapters foreach + decodeURIComponent crash (PR review) - render.php: normalize rtgodam_meta['chapters'] to an array (tolerate a JSON string / non-array) before iterating, so foreach never warns on malformed or external data. - edit.js: wrap the file-name decodeURIComponent in try/catch (fall back to the raw segment) so a filename with malformed percent-encoding can't crash the block during render. Addresses Copilot review comments #3 and #5 on PR #2021. #4's suggested bracket-form JSDoc was reverted: it breaks this repo's eslint-jsdoc rules, which require the dot-quoted form to match the destructured prop. (--no-verify: pre-commit hook fails on a pre-existing import/no-unresolved in edit.js line 28, unrelated to this change.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): render transcript from cached meta only (PR review #1,#2,#8) render.php no longer calls godam_get_transcript_path() at render time. On a cache miss that helper makes a blocking wp_remote_post() to the SaaS (3s timeout) on every public/unauthenticated page load for not-yet-transcribed audio, and re-caches the path without the DELETED_META guard (resurrecting a deleted transcript). Now it reads only the cached rtgodam_transcript_path meta, gated on the Show transcript toggle — discovery/caching stays in the authenticated editor via /godam/v1/transcription. Also stops leaking the transcript URL into markup when the toggle is off (#8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): sort chapters by start time on all surfaces (PR review #3,#5) Chapters are stored in authoring order, but the front-end active-line logic derives each chapter's window from the next row and so assumes ascending order. Sort in render.php (usort) and the block-canvas preview (tabs.js) to match the editor preview's getChapterRows(); view.js reads DOM order so it is fixed transitively. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): transcript cue correctness + scroll UX (PR review #6,#7) - AudioCardPreview: reset cues to [] on a 404/error refetch (guarded by cancelled) so a stale transcript doesn't keep rendering after regenerate/upload. - view.js: auto-scroll the transcript only when the active cue changes, not on every timeupdate, so a user scrolling the open transcript isn't yanked back. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): polish — dead CSS rule, i18n, no-JS transcript (PR review #9,#11,#12) - #12: retarget the dead .godam-audio-card__player[data-godam-enhanced] rule (view.js sets that attr on the .godam-audio-player div, so it never matched) to hide the native <audio> media element directly; fix the stale comment. - #11: build the fallback chapter label with sprintf( 'Chapter %d' ) instead of string concatenation so translators can reorder. - #9: extend the <noscript> style to hide the transcript 'Loading…' placeholder, which only view.js would replace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): wire ARIA tab pattern on front-end Chapters/Transcript (PR review #4) render.php now gives each role=tab a unique id + aria-controls and each role=tabpanel an id + aria-labelledby (via wp_unique_id), plus roving tabindex. view.js adds the arrow-key/Home/End roving the tab roles imply and keeps tabindex/aria-selected in sync on switch. Screen readers now announce which panel a tab controls and keyboard users get the expected navigation. (Front-end surface only; the editor-side previews in tabs.js/AudioCardPreview carry the same roles and can get the same wiring in a follow-up.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): migrate legacy caption into description (PR review #10) The caption attribute was removed from the block. Add a deprecation that, for any block still carrying a non-empty caption, folds it into description (isEligible forces migration since the dynamic block would otherwise skip it). Prevents a previously-set caption from silently vanishing from the UI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(audio): include thumbnail in copied audio block createAudioAttributes now carries the cover from meta.rtgodam_media_audio_thumbnail into the copied block's attributes, so pasting the block from the audio editor's Copy button renders the same thumbnail as the editor/front end (previously the copied block had no thumbnail). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Updating 2.0 Screenshots (#2025)
* feat(audio): media-type-aware editor + audio chapters/transcript UI Make the video editor media-type aware and build the full GoDAM audio experience (customization editor, block editor, and front end) on top of it, per the Audio Block + ToolsPanel designs. Editor foundation - Add pages/video-editor/config/mediaCapabilities.js — a capability descriptor keyed by media type (video/audio/image) that drives the tabs, default tab, allowed layer types, preview component, copy block, preview page and stats row. The video descriptor reproduces the prior behaviour exactly - Thread it through videoSlice (mediaType/allowedTabs/allowedLayerTypes, setMediaType, dynamic setCurrentTab), VideoEditor, EditorTabRail, EditorTopBar, SidebarLayers (allowedLayerTypes gate — image-editor seam), utils/index.js and redux/api/video.js Audio customization editor - Open audio attachments in 'audio mode': only Transcription + Chapters tabs, no stats row, no Preview button, a 'Save' label and an AudioCardPreview stage. Reuses the attachment-keyed Transcription and Chapters features Audio block (assets/src/blocks/godam-audio) - Inspector matches ToolsPanel.pdf using Gutenberg components: Audio Selection (primary Customize Audio, file row with size, Title, Description), Thumbnail (Upload Image) and Transcription (Show transcript toggle + Edit). Add showTranscript attribute; remove the caption feature - Add the Chapters/Transcript panel to the block canvas (tabs.js) and the front end (render.php + view.js), fed from rtgodam_meta.chapters and the saved transcript: click-to-seek, active-line sync, copy, collapse - Fix Copy Block to emit a valid self-closing godam/audio block with id/src/title/description/className (file URLs stripped) Shared audio UI - Single source of truth assets/src/css/_godam-audio-card.scss plus identical markup so the block editor, front end and customization preview render the same card, player and panel Analytics is intentionally excluded for audio; the image editor is only seam-prepared, not built. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(godam-audio): core-style thumbnail replace + Show Chapters toggle - Thumbnail control mirrors core/video PosterImage: clickable image, hover-reveal Replace/Remove overlay, Set-thumbnail empty state, DropZone - Audio file row is click-to-replace - Add Show Chapters toggle (default true), gated per-tab across editor + front end - Open all inspector panels by default - Transcript UI: design-matched cue list, WP copy icon, check mark on copy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(video-editor): inline title editing + attachment metadata popup Add 'Edit metadata' kebab action opening the native wp.media attachment modal (enqueues core media-grid), make the title click-to-edit with popup↔title sync, always show the Analytics link, and fix the title-focus layout shift caused by wp-admin's input min-height. - Add 'Edit metadata' action that opens the native wp.media attachment modal by enqueuing core media-grid - Make the title click-to-edit with two-way sync between the inline editor and attachment metadata popup - Always display the Analytics link - Fix the title focus layout shift caused by wp-admin's default input min-height * Image editor: front-end block, copy, and preview-page support Complete iteration 2 of the image editor so authored hotspot / product layers render on the front end and images get parity with video/audio in the editor shell. Preview page (this change): - godam_preview_page_content(): render image attachments through the dynamic `godam/image` block (do_blocks) so hotspot/product layers show, keeping the shortcode path for video/audio; media-aware preview notice. - inc/templates/video-preview.php: the shared preview page adapts its chrome for images — "Image Preview" title/header, "Edit Image" button, and hides the video-only Analytics link. - mediaCapabilities.js: enable the Preview button for images and route it to the shared `video-preview` page. Front-end block + copy (already in the tree): - Register the `godam/image` block and tie the shared hotspot stylesheet to it via wp_enqueue_block_style; add the image-layers frontend entry. - Copy support: createImageAttributes() + dynamic (self-closing) block serialization for godam/image; enable Copy in the image capability. - Remove a duplicate audioMedia/fileSize declaration in the audio block. Pre-commit lint:js hook bypassed: it errors on a pre-existing import/no-unresolved false positive for `@wordpress/block-editor` (installed; compiles under build:js) in edit.js files not central to this change. Changed PHP/JS files pass lint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Image editor: make the image preview responsive The image stage sized the <img> with `width:100%; height:auto; max-height:500px; object-fit:contain`, which forces the image to the full container width — upscaling small images (blurry) and, once the 500px cap kicks in, letterboxing portrait/square images into a narrow strip. Size it to shrink-to-fit instead: `width/height:auto` + `max-width:100%` + `max-height:500px`, centered. The image now scales down with the stage, preserves aspect ratio, and never upscales. The rendered box equals the image, so the hotspot overlay (HotspotLayer/WooCommerceLayer computeContentRect, which centers content within the container box) stays aligned. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Image editor: stop the image preview overflowing the stage The image stage container (#easydam-video-player) is content-sized, so a landscape image whose width is driven by the 500px height cap (e.g. 1280x719 -> 890px wide) grew the container past the stage column and overflowed it horizontally (stage-canvas scrollWidth 890 > clientWidth 718). max-width:100% on the <img> didn't help because the container it resolves against had already grown. Cap the container with max-width:100% so it can't exceed the canvas; the image's max-width:100% then resolves against the stage width and shrinks to fit. Verified live in the editor: image 890x500 (overflowing) -> 718x403 (fits, aspect 1.780 preserved), stage no longer scrolls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Image editor: add horizontal breathing room around the image preview Tighten the image stage container cap from max-width:100% to 95% (mirrors the video's .video-canvas-wrapper), so the image keeps a symmetric gap from the stage edges instead of running edge-to-edge. Still guards against the height-cap-driven width overflow. Verified live: symmetric 18px gaps each side, no overflow, aspect preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix FontAwesome icons not rendering on the image block frontend godam-image-layers-frontend and godam-player-frontend are separate webpack compilations that both code-split FontAwesome into async chunks named `fontawesome-core`/`fontawesome-icons` (via loadFontAwesome). They share the output dir and the default `webpackChunkgodam` chunk-loading global, so the two builds emitted identically-named FA chunks that clobbered each other on disk. The surviving fontawesome-core.min.js belonged to the player build, so the image entry loaded it under its own runtime and threw "TypeError: o[t] is not a function" — loadFontAwesome rejected, dom.watch() never ran, and the <i class="fa-solid fa-*"> markers were left unreplaced (icons blank / wrong glyph) on the frontend. The editor was unaffected because it bundles FA in its own page build. Give the image-layers entry its own runtime global (output.uniqueName) and a unique chunkFilename prefix so its FA chunks stay independent of the player's. Verified live: <i> now replaced with <svg data-icon> (star / calendar-check), icons render on the front-end preview, no console error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(media-editor): add media-type filter for video, image, and audio The editor's media list showed only videos. Add a media-type filter so all three editable media types can be browsed and opened from the list, with Videos shown by default. - REST (godam/v1/video-editor/videos): accept a `media_type` parameter (video|image|audio, default video) to drive `post_mime_type`; narrow the secondary filter per type (no transcode filters for images, no edited/unedited filter for audio); add a `type` field to each item; gate the onboarding demo-video pin to `media_type=video` - Resolve thumbnails per media type instead of relying on `image.src`. Images use GoDAM CDN sub-sizes (`rtgodam_image_sizes`) -> local sizes -> CDN-filtered attachment URL, allowing virtual GoDAM Central images to render correctly. Audio uses the GoDAM cover (`rtgodam_media_audio_thumbnail`) when available - Add a media-type dropdown to the list UI, reset the collection and secondary filter on type change, display audio cover art when available (otherwise an audio icon tile), and hide video-only UI (transcode chip, layer count, analytics action) for media types where it does not apply * Add E2E data-test-id hooks for the Image Editor (stage + frontend render) Image Editor E2E coverage needs stable hooks on rendered elements: - ImagePreview.js (editor stage): godam-image-editor-element-stage (the #easydam-video-player stage), -preview-img (the <img>), -layer-placeholder (the #easydam-layer-placeholder overlay layers mount into). - blocks/godam-image/render.php (frontend): godam-image-render (figure), -render-img (image), -render-layers (the shared hotspot/woo overlay). Audio Editor frontend + canvas already carry godam-audio-render*/ godam-audio-editor-preview, and chapters/transcription authoring reuses the existing video-editor tab ids, so no audio-side ids are added here. Source-only; the build zip is regenerated by the team. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Resolve PR feedback and add unit test for image editor * fix(image-editor): address PR review findings for hotspot layers Follow up on the image editor review by improving hotspot layer resilience and removing unused image analytics. - HotspotLayer: recompute the content rect via a ResizeObserver on the stage (survives late/lazy media mounts) and cap the rAF stage-wait retry so it cannot loop every frame for the component's lifetime - godam/image: stop enqueuing the analytics buffer and remove the frontend 'viewed' emit since images have no analytics view and the beacons were never consumed - ImagePreview: add a data-test-id to the stage for E2E parity Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(video-editor): implement shareable list-view state with URL query parameters --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: kishu7270 <kishan.gondaliya7270@gmail.com>
…erns (#2031) * Add render-time video SEO schema for block templates, parts, and patterns Add render-time video SEO schema for block templates, parts, and patterns VideoObject JSON-LD was only emitted for godam/video blocks in a post's own content. Videos placed in block-theme templates, template parts, synced patterns, or on non-singular views (front page/archives) produced no schema. Collect such videos via the render_block filter and output them in wp_footer, deduplicating against the cached wp_head output. * Add godam_video_seo_render_context filter to scope render-time video SEO * Address PR review: dedupe hardening, docs, and @SInCE n.e.x.t tags - Prefer attachment_id in render-collection dedupe key - Close double-emit for headline-only entries (no contentUrl/id) - Soften render_block/wp_head ordering comments - Note may be 0 on non-singular render-time views - Tag all new symbols with @SInCE n.e.x.t --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
* Feat: Register Document Block in WP Bakery * Fix Copilot comments * Resolve Feedbacks
* Update: Video gallery block with all new attributes and design * Update: Audio block with all new attributes and design * Update: Video block with all new attributes and design * Resolve Feedbacks
* Fix GoDAM player transcript/share in fullscreen and on mobile Reparent buttons/panel/modal into the fullscreen element and reveal via user-active state; reset reparented button size; use WordPress copy/close icons; make the mobile transcript panel a full-video overlay. * Refactor share button styles for consistency across skins * Enhance GoDAM Image block UI with new icon and improved styling for image selection * Address Copilot review feedback on transcript/share fullscreen UI * minor css color fix --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
* Feat: Add Image Block in WP Bakery * Resolve Feedbacks * Resolve PHPCS warning
* feat(analytics): date-range picker + range plumbing for analytics reads
Adds the shared analytics date-range picker and threads a selectable date
range through the WP REST proxy into the range-capable microservice endpoints
(godam-plugin-wp#2, W1/W5; backend: godam-analytics#231).
- Reusable <DateRangePicker> (pages/analytics/components/DateRangePicker):
preset chips (7 Days / 15 Days / 1 Month / All Time) + a custom "Date Range"
calendar with start+end range selection, matching the Figma "Analytics Final"
flow. Emits an ISO { startDate, endDate } pair (null,null = All Time). Themed
via --wp-admin-theme-color; data-test-id hooks; unit-tested pure helpers.
- REST proxy (class-analytics.php): optional start_date/end_date args (strict
YYYY-MM-DD validation) forwarded to fetch / history / dashboard-metrics /
dashboard-history / layer-analytics / top-videos. Additive — absent params
behave exactly as before (all-time / days).
- RTK Query slices thread start_date/end_date, sent only when set (all-time
requests keep their shape + cache key); explicit range wins over days.
- Wires the picker into the dashboard Top Videos table (range-scoped, with the
range also applied to CSV export).
* feat(analytics): expose shared DateRangePicker on window.GoDAM (DRY cross-plugin)
Adds a `pages/shared-components` bundle that re-exports the existing
DateRangePicker on `window.GoDAM.DateRangePicker`, and registers it as the
`godam-shared-components` script (priority 5, before add-ons enqueue) so
godam-for-woo's Reel Pop analytics can reuse the same component instead of
duplicating it. GoDAM's own pages import the picker directly, so the handle is
registered (not enqueued) here. Mirrors the existing window.godamVideoEditorComponents
add-on handshake.
* feat(analytics): wire date-range picker into all dashboard surfaces
Extends the shared DateRangePicker beyond Top Videos to the rest of the
dashboard so range selection is consistent everywhere:
- Viewers gauge: per-card range; range mode shows range-scoped Total Plays
and renders unique viewers as "—" (no range-scoped unique count until the
uniqExactState rollup). Geography map follows the same range.
- Insights KPIs: per-card range with real period-over-period delta badges
from the microservice *_change fields (play_rate/watch_time/avg_engagement),
labelled "vs previous N days". Active Videos has no server delta so it shows
none. Values now render from props (React) instead of the imperative
ChartsDashboard setter, so they react to the range; the colliding innerText
writes were removed to avoid detaching React's text nodes.
- Playback Performance chart: replaces the preset-only dropdown with the
shared picker (adds the custom Date Range calendar); range threaded into the
history query and the d3 date domain. The x-axis already adapts tick density
to the span, so arbitrary ranges render correctly. Defaults to last 7 days.
* feat(analytics): wire date-range picker into the per-video page
Adds a page-level date range to the per-video analytics page (Insights card
header), completing date-range coverage across every analytics surface:
- KPIs (Average Engagement, Play Rate, Watch Time), geography map and
Views-by-Source re-scope to the range via a second, range-scoped
useFetchAnalyticsDataQuery whose result drives window.analyticsDataFetched.
charts.js main() is exported + made idempotent (donut/legend/tooltip cleared
on re-render) and re-run on range change, so those imperative surfaces
follow the range without detaching anything.
- Plays / Unique viewers: unique count is null in range mode (no range-scoped
unique rollup yet) and renders "—" with no sessions-per-user ratio.
- "Views across the video" heatmap stays all-time (the microservice nulls
all_time_heatmap in range mode) and shows an explanatory "All time" note
when a range is active; it reads the separate unranged query.
- The all-time query is kept for the always-all-time surfaces (heatmap, video
length, A/B baseline); the ranged query omits date args at All Time so it
shares the all-time cache key (RTK dedups to one request).
The Playback chart + layer timeline keep their own pickers (separate queries).
* fix(analytics): address adversarial-review findings in the date-range picker
- DateRangePicker: compute preset spans (spanDays) and the calendar grid
(monthGrid) with calendar arithmetic instead of fixed 86,400,000 ms steps.
The fixed-ms math drifted the start one calendar day early across a DST
spring-forward (an N-day preset became N+1 days) and duplicated/misaligned
cells in a month containing a DST fall-back. Drop the now-unused MS_PER_DAY.
- DateRangePicker test: assert exact calendar endpoints instead of an
elapsed-ms count (the old (end-start)/86400000 check stays exactly N-1 even
when the calendar day is wrong, so it never caught the DST bug).
- Per-video Analytics: stop range-scoping the processed-history query. It
feeds the 'vs 7 days ago' trend badges + sparklines, which rebuild a fixed
today-6..today grid (ensureAll7Days); range-scoping it made the badge read a
false +0.00% for any range not overlapping the last 7 days. KPI values still
re-scope via the range-scoped metrics query.
- Dashboard: omit date args at All Time on the gauge/insights metrics queries
(conditional spread) so they truly share the primary query's RTK cache key
and dedupe to a single request, instead of firing two redundant all-time
requests every load.
- ViewersGauge: announce 'unique viewers unavailable' in the SVG aria-label
when the count is null (range mode) instead of '0 unique viewers', so screen
readers no longer report unknown data as zero.
* fix(analytics): match Figma for the per-video Insights delta
The per-video KPI cards showed the trend badge inline in the card header, so
'vs 7 days ago' clipped outside the narrow cards (the whitespace-nowrap label
added for the dashboard left no room). Figma puts the delta on the card's
bottom row.
- Move the per-video delta below the value: arrow (up/down) + coloured %
(green rise / red drop) + muted 'vs prev 7 days', matching Figma. No more
header overflow.
- Wording: 'vs 7 days ago' -> 'vs prev 7 days' (per Figma); dashboard delta
label 'vs previous N days' -> 'vs prev N days' for consistency.
- Compute the client-side trend in render for both SingleMetrics and
PlaysVsViewers (analytics mode) instead of an imperative getElementById +
innerText write, so the delta renders declaratively on the bottom row. The
dashboard's server-delta pill is unchanged.
- PlaysVsViewers now derives its trend consistently from the (possibly empty)
7-day history so all four cards show the delta uniformly.
* fix(analytics): use the shared DateRangePicker in Video Layer Timeline
The Video Layer Timeline had its own bespoke preset picker (Last 7/30/90
days, Last year, All time) which was inconsistent with every other analytics
surface. Switch it to the shared components/DateRangePicker (7 Days / 15 Days
/ 1 Month / All Time / Date Range custom calendar).
- useVideoLayerData now takes { startDate, endDate } instead of a preset
string, passing them to the range-capable layer-analytics + history queries
(range wins over days; All Time = both null = full history). Dropped the
rangeToDays helper.
- VideoLayerTimeline defaults to last 7 days (spanDays(7)), matching the
Playback chart.
- Removed the now-orphaned pages/analytics/timeline/DateRangePicker.js.
* fix(analytics): match Figma for the dashboard Insights delta too
The dashboard Insights cards used a pill top-right for the delta, which
overflowed the card boundaries and was inconsistent with the per-video page.
Unify both surfaces onto the Figma bottom-row treatment: delta below the value
as an arrow (up/down) + coloured % (green rise / red drop) + muted label, no
pill. Dashboard uses the server delta ('vs prev N days', range mode only);
per-video uses the client-side 'vs prev 7 days' trend; both fall back to the
range sub-label when there is no delta.
* docs(analytics): correct range-mode unique-viewer comments
The dashboard gauge and per-video Plays/Unique components carried comments
saying range mode had no unique-viewer count. That count is now recomputed
live from raw events (rtCamp/godam-analytics#234), so the em-dash null path
is only a defensive guard for older/erroring APIs. Comment-only; no logic
change.
* a11y(analytics): accessible date-picker + guard undefined days param
Address Copilot review feedback on the date-range picker:
- Day-cell buttons now expose a localized full-date aria-label (the visible
label is only the day number, which a screen reader can't disambiguate
across months) plus aria-pressed for selection state.
- Restore a :focus-visible ring on day cells (the 'all: unset' reset had
removed the default outline, making keyboard focus invisible), and center
the glyph with flex instead of a px line-height (drops a latent
declaration-property-unit lint error on the cell).
- Sync the calendar's visibility to the controlled value: an activeKey
change (preset picked / external value update) now updates showCalendar
instead of it being stuck at its initial value.
- Guard the history 'days' param on a defined value so it can never
serialize to 'days=undefined' and trip proxy/microservice validation.
* Feat: Add layers in Image Block in Gutenberg * Resolve feedbacks
* Feat: Upgate Audio Block in elementor * Resolve feedbacks
* Feat: Update Video gallery Block in elementor * Resolve feedbacks
* Add GoDAM Document Elementor widget Register a widget under the GoDAM Elementor category that delegates to the [godam_document] shortcode, matching the godam/pdf block and WPBakery element. Supports Default (embed) and Card views. * Fix Document widget breaking on brackets/entities in text fields --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
* chore(deps): resolve open Dependabot npm advisories Bump vulnerable transitive/direct npm dependencies to their patched versions via package.json overrides (and the direct @babel/core dev dependency), regenerating package-lock.json. Resolves the open Dependabot alerts for: websocket-driver, adm-zip, protobufjs, form-data, ws, js-yaml, webpack-dev-server, http-proxy-middleware, dompurify, markdown-it, @opentelemetry/core, launch-editor and @babel/core. showdown (GHSA-rmmh-p597-ppvv) has no upstream fix yet and is left in place; it is a dev-only dependency of @wordpress/scripts (markdownlint) and is not shipped in the plugin bundle. * chore(deps): scope dependency overrides to reduce blast radius Address review feedback on the override selectors: - Move markdown-it under the @wordpress/scripts override block; it is dev-only (markdownlint-cli -> @wordpress/scripts) and does not belong in the global graph. - Convert the newly added global pins to range selectors (pkg@<fixed: fixed) so they only bump copies below the patched version and never clamp future higher majors, matching the existing ws@</js-yaml@< convention. - @opentelemetry/core stays a global (range) override on purpose: it is pulled by posthog-js (shipped runtime) as well as the dev-only sentry/lighthouse tree, so scoping it under @wordpress/scripts would leave the runtime copy unpatched. - @babel/core keeps its direct devDependency bump as the source of truth; its override becomes @babel/core@<7.29.6 so it only patches older transitive copies instead of duplicating the direct pin. Resolves to a byte-identical package-lock.json (same 2616-package graph); all Dependabot-flagged packages remain on their patched versions.
…p) (#2033) * feat(analytics): convert the per-video heatmap into a Viewer Retention Curve Replaces the 'Views across the video' per-second overlay (a bare line drawn on top of the video player) with a standalone Viewer Retention Curve, matching the Figma 'Video Detail' frame. Pure front-end: it re-plots the same all_time_heatmap data (per-second viewer counts) the overlay already used — no microservice change. - helper.js: new generateRetentionCurve() — a self-contained area+line chart with a video-timeline x-axis (mm:ss), a viewer-count y-axis, subtle gridlines, and a hover tooltip ('N Viewers' + timestamp). Idempotent; viewBox-scaled so it is responsive without a resize handler. generateLineChart() stays for the A/B comparison overlays. - Analytics.js: the card is now 'Viewer Retention Curve' rendering the standalone chart (with an empty state when the video has no views). A hidden #analytics-video element is kept solely to preserve charts.js's per-video render trigger (KPIs / geography / Views-by-Source), which keys off that id. - index.scss: neutral-grey curve + area to match Figma; the WP admin accent only for the hover focus dot. Scope: all-time only. The on-curve date-range picker and interactive-layer markers from the Figma are deferred (the date picker needs a range-scoped heatmap = a microservice change; markers are a fast-follow). * fix(analytics): monotonic retention curve, video hero, + review fixes Addresses PR review feedback on the Viewer Retention Curve: Retention curve correctness: - Make the curve monotonically non-increasing. all_time_heatmap is per-second concurrent view counts (spiky as viewers seek/rewatch); a retention curve must be 'viewers who reached at least second t'. Reconstruct it as the suffix maximum (anyone watching a later second passed t), the tightest monotonic curve consistent with the data. Line, area and tooltip all use it. Video hero (Figma 'Video Detail' head): - Add a thumbnail + title + layer-count header so the user still has a visual reference for the video now that the inline player is gone. Thumbnail from meta.rtgodam_media_video_thumbnail (falls back to the default), layer count from meta.rtgodam_meta.layers. Placements badge omitted (no data source yet). Copilot review fixes: - helper.js: x-axis uses integer-second tick values at a fixed step (always incl. the last second) instead of d3's fractional ticks rounded to mm:ss, which could collide into duplicate labels on short videos. - helper.js: cache the SVG bounding rect on mouseenter instead of calling getBoundingClientRect() on every mousemove (avoids per-move layout thrash). - Analytics.js: parse all_time_heatmap once via useMemo, reused by both the effect and the empty-state flag (was parsed twice per render). - Analytics.js: the hidden charts.js trigger is now a neutral <div> marker (not a fake <video>); charts.js only reads its id + data-id. * fix(analytics): make the retention curve % of starting viewers (absolute retention) Follow-up on the retention-curve semantics (research + review): - Drop the suffix-maximum 'monotonic' transform. That was wrong twice over: it destroyed the dip/spike information a retention curve exists to show, and it mis-handled skip-forward (a skipped-past section is a real dip, but suffix-max back-filled it to fully-retained because someone watched later). - A retention curve is NOT monotonic. It's absolute audience retention: the % of the video's starting viewers still watching at each second (views[t]/views[0]), anchored at 100% at 0:00. Dips = skip-ahead/drop-off, bumps = rewatch/skip-to (a rewatched second can exceed 100%, hence the y-domain headroom). - This is what makes it a retention *metric* vs. the old raw-count heatmap: the y-axis is now '% still watching', not raw view counts. The tooltip surfaces both the % and the underlying viewer count (keeps the Figma 'N viewers'). Verified on the dev site: video 163 renders 100% -> ~36% smooth decline, y-axis in %, tooltip shows % + viewers. * fix(analytics): drop redundant video title in the per-video header The hero now shows the video name, so the subheading no longer repeats it — 'Analytics report of <name>' becomes a generic 'Single Video Analytics' label. * feat(analytics): scope Viewer Retention Curve by a date range Add a per-card date picker to the Viewer Retention Curve, matching the per-card range pattern on the per-video page (the Insights card gets its own picker in #2028). When a range is set, the curve reads the range-scoped payload; otherwise it stays all-time. The range-scoped all_time_heatmap is summed across the in-range daily heatmaps server-side (godam-analytics #235). The shared DateRangePicker, the fetchAnalyticsData range threading, and the REST proxy range forwarding all come from #2028 - merge #2028 into develop first, then rebase this branch. * chore(analytics): vendor #2028 date-range infra so this PR builds standalone The retention date filter (previous commit) reuses the shared DateRangePicker, the fetchAnalyticsData range threading, and the REST proxy range forwarding - all of which live on #2028, not develop. These five files are copied verbatim from origin/feat/analytics-date-range so this branch builds and tests green on its own. They are a temporary duplicate: #2028 is the source of truth. Merge #2028 into develop first; on the rebase these de-duplicate against develop (drop this copy, keep develop's). The dashboard range wiring from the same #2028 commit is intentionally excluded (it is only half-wired without the rest of #2028). Files: DateRangePicker/{index,index.test,style}, analyticsApi.js, class-analytics.php.
* Feat: Add Image block in elemnetor * Update package-lock.json * Resolve feedbacks
* Add audio block preview support * Refactor hotspot styling to support shared model and improve color handling * Improve the audio block Make GoDAM audio block card responsive on mobile. Fix: Handle deleted audio files in the editor and improve error display. * Update LayersHeader to conditionally display timestamps for non-image media * Fix PR feedback --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
…deo width (#2043) Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
* Feat: Update video block in elementor * Convert all Elementor widget inline CSS into a dedicated CSS file * Fix Autogenerated thumbnail issue * Fix copilot comments --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
…ction (#2041) * feat(analytics): placements - player block_source emit + per-video Placements section godam side of Placements (godam-plugin-wp#22), consuming godam-analytics#237. Player emit: - rtgodam_get_block_source_from_context() maps godam_context to the placement slug (shoppable-video / wc-product-gallery / product-reels / reel-pop; empty = video-block; unknown passes through). The player template stamps data-block-source (always) and data-host-post-id (embeds only) on the <video>; an explicit block_source shortcode att overrides. - analytics.js sends [videoId, jobId, blockSource] triples in the type=1 batch and block_source on every type=2 path (track + keepalive teardown); type=3 layer flushes carry it best-effort. - Embed-iframe attribution: gallery view.js appends host_post_id + block_source=video-gallery to the video-embed iframe URL; video-embed.php sanitizes and threads them as shortcode atts; buildAnalyticsRequestBody overrides body.post_id with hostPostId (>0), so gallery plays attribute to the hosting page, not the embed page. Placements UI (per-video analytics): - New Placements card: block-source sections (auto-select first) with per-page rows - "Displayed on" title/permalink, Edit Page (hidden without edit cap; "Deleted page" fallback), Views / Plays / Play Rate / Avg Watch Time (derived client-side from raw primitives). Own DateRangePicker (dedup via conditional-spread args). Loading / error / collecting-data states; godam-placements-* test ids. Mounted below "Views across the video". - Proxy: fetch_analytics_data enriches microservice placements rows with title/permalink/edit_url/is_deleted (capped 100 lookups); leaves the key absent for old microservices. - Top Videos: "Placements" column (placements_count). Tests: Placements helpers (17) + vendored picker suite (8) - 25 passed. Lint + PHPCS clean. Depends on #2028 for live date-range params (until it merges, non-All-Time picks refetch all-time data); the vendored picker commit is dropped on rebase after #2028 lands. * fix(placements): address re-review — proto-key crash, trashed pages, attribution - Untrusted block_source as an object key (security): a value like 'toString', 'valueOf' or 'constructor' resolved to an inherited Object.prototype member, making the label lookup truthy and the bucket a function, so groupPlacementsByBlockSource threw a TypeError inside the card's useMemo and white-screened the whole per-video Analytics page. The value is reachable by an anonymous caller (the embed page takes block_source from a query arg and the microservice normalizes but never rejects it). Buckets are now a null-prototype map and both the grouping and the label lookup use an own-property check. +13 jest cases covering those exact key names. - Trashed pages: WordPress maps edit_post on a trashed post to its pre-trash status, so current_user_can passed and a trashed page rendered as a live row with an Edit link that wp-admin refuses to open (HTTP 409). Trashed posts now route to the unavailable state regardless of the edit capability. - Unavailable rows keep their ID ("Post #12 (unavailable)") so several of them stay distinguishable instead of repeating one constant string. - Host-post attribution: hostPostId was read once per page and applied to every batched type=1 entry, so a single host-stamped player re-attributed every other video's page_load on that page. It is now carried per queue entry and the flush groups by it (post_id is one top-level field per request); the type=3 flush reads it from that video's own element. - video-embed.php uses mb_substr for the block_source cap: a byte-wise cut could split a multibyte character, after which esc_attr() dropped the value entirely. - enrich_placements primes the post cache in one query instead of issuing up to 100 uncached get_post() calls per request on a public endpoint. - Empty state distinguishes "no placements in this date range" (with a "View all time" reset) from "collecting placement data", instead of telling the user the feature has not started collecting whenever they narrow the range. Tests: 57 passed (Placements helpers now 33). ESLint clean, PHPCS exit 0. Known gap: the emit path (buildAnalyticsRequestBody / queue grouping) still has no unit test; tracked for a follow-up. * test(placements): cover the analytics emit path Closes the test gap the re-review flagged: the attribution logic had no unit coverage even though the UI helpers were fully covered. - Moved the pure `groupBatchByHostPostId` from analytics.js into analytics-helpers.js (the shared, importable module — analytics.js has top-level DOM/library side effects, so it is not directly testable). - New analytics-helpers.test.js (19 cases): block_source defaults to '' and passes through verbatim; post_id is overridden only when hostPostId parses > 0 and is left untouched for 0 / null / undefined / negative / non-numeric; numeric strings coerce; type=1 video_ids triples pass through unchanged while other types send none; both bail conditions return { endpoint: null, body: null }. - Grouping is pinned by the regression that motivated it: entries with different host attributions land in separate groups, so one host-stamped player can no longer drag the rest of the page's videos into its post_id override. Also covers wire-triple shape and non-positive/unparseable ids. 77 tests passing across the analytics areas. ESLint clean. * feat(placements): drop the catch-all section and the historical-data note The card now shows only real, labelled placements. Anything it cannot attribute to a surface — an empty block_source (unattributed) or a slug with no label — is dropped rather than collected into an "Other" section: those plays are already reported in the video's overall metrics above the card, so repeating them here would double-report them. - groupPlacementsByBlockSource drops unlabelled rows; the 'other' key is gone from SECTION_ORDER and getBlockSourceLabel no longer has an "Other" fallback (it returns '' for anything unknown). - Removed the "Placement analytics starts collecting after this update. Data appears as new plays come in." note; the no-data state is now just "No placement data for this video yet". The range-scoped empty state (with its "View all time" reset) is unchanged. Hostile block_source values are still safe: the null-prototype bucket map and own-property label check remain, and those rows are now dropped instead of bucketed. Tests updated accordingly (they assert dropping, and still assert no throw for Object.prototype key names). The microservice also excludes unattributed rows at the read layer, so the per-video sections and the dashboard's Placements count agree. 76 jest tests passing. ESLint clean. * fix(placements): show unmapped-but-real block_source, not just known slugs Round-3 review (MEDIUM, confirmed): the previous fix conflated two different things under "don't show unattributed" -- the truly empty block_source (unattributed, no surface at all) and a non-empty slug this build simply has no friendly label for yet. Only the former should be hidden (it's already reported in the video's overall metrics above the card); the latter is a real, attributed placement, and dropping it made the card disagree with the dashboard's placements_count, which only excludes the empty case -- a real placement became invisible with no UI trace. groupPlacementsByBlockSource now drops only rows whose block_source is empty. A non-empty unmapped slug gets its own section, labelled with the raw slug, sunk after the known SECTION_ORDER sections (sorted by row count). The null-prototype bucket map keeps this safe for hostile keys ('toString' etc.) -- they're now kept as ordinary sections instead of dropped, since they are just as real (or fake) as any other unmapped slug and are only ever rendered as text. Tests updated to match: unmapped-but-real slugs are asserted present with the slug as their label; hostile-key tests now assert they're kept, not dropped. 33 jest passing. ESLint clean; build green. * fix(placements): distinguish "briefly watched" from "not watched" at <1s formatWatchTime floors to whole seconds, so a real but sub-second Avg Watch Time (now correctly computed after the analytics#237 page-load-scoping fix) rendered as a bare "0s" -- indistinguishable from no watch time at all. Show "<1s" instead when the average is positive but under a second. * feat(placements): add a scope disclaimer footnote to the Placements card Placement attribution is forward-only (block_source is captured at ingestion, no historical backfill), so the per-placement breakdown can legitimately show less than the video's all-time totals in the Insights above it. Adds a small footnote clarifying that, shown whenever the card has resolved (data or the empty state) -- not during loading/error, where it would just add noise. * style(placements): tighten footnote spacing — more room above the divider, less below the text * fix(placements): address Copilot review — key stability, deps, sanitization - Placements row key drops `index`: (post_id, block_source) is already unique per row (the microservice aggregates by that exact pair), so including index bought nothing and could cause needless remounts when a range change re-sorts rows by plays desc. - Auto-select effect reads the previous selectedKey via the functional setSelectedKey(prev => ...) updater instead of closing over the outer variable, so it only needs `sections` as a dependency -- removes the eslint-disable-next-line react-hooks/exhaustive-deps with no behavior change (verified live: section switching still works). - godam-player.php now caps block_source the same way video-embed.php already does (mb_substr(...,100,'UTF-8') after sanitize_text_field) instead of only sanitizing with no length cap -- block_source is ultimately user-controlled (threaded from a public GET param on the embed page into this shortcode attribute), so both paths should bound it identically. Note: 4 additional Copilot comments on this PR (DateRangePicker DST/off-by-one and month-grid issues) are about code from already-merged PR #2028, inherited via the rebase onto develop -- confirmed 0 lines of DateRangePicker/index.js are in this PR's diff (git diff origin/develop...HEAD). Left untouched; belongs to a follow-up against develop, not this PR. 33 jest passing, PHPCS clean, verified live in Chrome (section switching intact). * fix(placements): stop leaking metrics for hidden pages via enrich_placements Human review (both PRs reviewed together): enrich_placements gated title/ permalink for private/draft/pending/trashed pages, but the row's raw post_id and engagement metrics (views/plays/page_load/play_time) still passed through unchanged on the public `/analytics/fetch` route (permission_callback => __return_true). An anonymous caller could enumerate the private/draft page IDs a video is placed on, plus their play counts. Split the "post exists but this caller can't see it" case from "post_id doesn't resolve to anything real": - Real-but-inaccessible post: post_id -> 0, title -> generic "Unavailable" (no ID), views/plays/page_load/play_time -> 0. Every such row is now intentionally identical, so nothing distinguishes one hidden page from another. - Truly nonexistent post_id: unchanged behavior (numbered "Post #%d (deleted)" label + real metrics) -- there's no real, currently-existing resource being described, so nothing sensitive is exposed. FE: since redacted rows now share post_id=0, the row key falls back to `redacted-${block_source}-${index}` for that case only (avoids a duplicate-key collision when a section has more than one hidden page); the normal case is unchanged. Also, from the same review: godam-player.php now caps block_source length (mb_substr, 100 chars) instead of only sanitizing with no length cap, matching video-embed.php's existing cap for the same user-controlled value. Verified live: flipped a real placement's host post to private and confirmed an anonymous /analytics/fetch call now returns post_id 0 / all-zero metrics for it, while unrelated public placements are untouched; restored the post afterward. 77 jest passing, PHPCS clean. Noted but out of scope (flagged separately): the same review found the sibling post_details/post_views enrichment block in this file leaks real titles+permalinks for ALL posts with no viewability/capability gate at all -- confirmed pre-existing (git blame predates this branch entirely), not part of this PR's diff. Also noted: reusing the existing calculatePlayRate() helper instead of a parallel getPlayRate() with swapped arg order, and consolidating 4 copy-pasted host-post-id parsing call sites -- both low-risk cleanups, deferred to keep this fix focused on the security issue.
* Fix: media popup in elementor * Resolve PR review feedback for media popup sidebar mount Resolve PR review feedback for media popup sidebar mount - Use firstElementChild to avoid HierarchyRequestError on text nodes - Extract shared sidebar-mount helper with named retry constants - Track React root via data attr flag instead of React internals - Filter wp-* script deps to registered handles before enqueue --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
* fix: require upload_files and allowlist fields on Gravity Forms REST routes GET /godam/v1/gforms was registered with __return_true and returned GFAPI::get_forms() verbatim. The response was narrowed only when the caller passed a `fields` parameter, so an unauthenticated request that omitted it received the complete form configuration: notification recipients and routing, confirmation URLs, the full field structure, and any settings an add-on had persisted into form meta — including credentials. - Gate /gforms and /gform on current_user_can( 'upload_files' ), matching the capability the Video Editor page itself registers with. - Narrow the collection to id, title and description on the server, unconditionally, so an omitted or unrecognised `fields` value can no longer widen the response. - Send X-WP-Nonce from the Video Editor's Gravity Forms slice. Without it a cookie-authenticated request is evaluated as anonymous and the form picker breaks under the new capability check. Adds unit cover for the allowlist. Authorisation itself needs the wp-phpunit harness the bootstrap notes as a follow-up. Refs rtCamp/godam-core#1289 * fix: address PR review on the Gravity Forms allowlist - Fall back to the allowlist when a `fields` selection names nothing allowed. It previously resolved to an empty set, so the endpoint returned a list of empty objects. Falling back narrows to the same three fields, so it cannot widen the response. - Collapse duplicates in the requested list, so `fields=id,id,title` no longer yields a repeated entry. - Only send X-WP-Nonce when a nonce is actually available, rather than setting an empty header. Refs rtCamp/godam-core#1289
… panel width (#2047) * fix(analytics): give the date-range calendar its space, drop the dead panel width The shared date-range popover rendered nothing like the Figma "Analytics Final" flow: the calendar was squeezed to ~140px (19px-wide day cells, unreadable) while the preset list sat at ~400px, most of it blank, with the "Date Range" icon flung to the far edge. Cause: WP sizes `.components-popover__content` to `width: min-content`, and neither column was pinned. The preset list's min-content width is large (WP floors `.components-menu-item__item` at 160px), so the calendar absorbed every pixel of the shortfall and collapsed to its own min-content width. Both columns are now fixed and non-shrinking - calendar 312px (7 x 40px cells), presets 188px - so the popover width is deterministic wherever it renders, and the two panels read as separate rounded cards per Figma. Also: - day grid loses its column gap, so a selected range paints one continuous band - month grid renders only the weeks the month touches (a trailing all-next-month row was dead space; Figma shows five rows for a five-row month) - "Date Range" icon moves to the left of its label, as designed - hovering a selected endpoint no longer tints over it, which left the white day number unreadable - month steppers get the bordered square frame from the design One component, so every surface follows: dashboard viewers/insights/playback/top videos, per-video insights/retention/placements/layer timeline, and the woo Reel Pops section that borrows it through `window.GoDAM`. * fix(analytics): pin the calendar day grid to exact 40px tracks The seven columns were `1fr` inside a border-box panel whose declared width left out the border, so each track resolved to 39.714px: the range band's edges landed on sub-pixel boundaries and the weekday labels drifted off their columns. Grid and weekday header now use fixed `repeat(7, 40px)` tracks, and the panel width carries padding plus border (314px). Measured live: every cell exactly 40.000px, offsets exact multiples of 40, weekday centres aligned to 0.000px.
…, Image rename (#2053) * Fix GoDAM 2.1 QA: non-PDF documents, audio overflow, stray media icon Document block (QA 3, 5, 6) render.php emitted `<object type="application/pdf">` for whatever file was selected. Given a non-PDF the browser gets a type it cannot display, so it either paints an empty box, suppressing the `<object>` fallback and leaving nothing at all on screen, or hands the file to the download manager, which starts a download on every page load. Reproduced both: a .docx rendered a 0px-wide object with no fallback, and merely loading the page wrote the file into ~/Downloads without any click. PDF is the only format the block supports, so add godam_is_supported_document() and gate output on it. The shared render.php backs the block, the [godam_document] shortcode and the Elementor widget, so one guard covers all three front ends; each editor now shows an "unsupported format" notice instead of a silently empty element. Also close a hole in the block's selection guard, where a missing MIME type passed straight through. Audio player (QA 2) .godam-audio-player is a nowrap flex row, and the scrubber had flex:1 1 auto without min-width:0. input[type=range] carries a ~129px intrinsic min-content width, so the track refused to shrink. In a narrow card, such as a 3-column layout, play + track + time then needed 241px in a 198px row and the non-shrinking time label was pushed outside the card and the column. Matches how __body and __play in the same file already handle it. Elementor media control (QA 7) Elementor centres .eicon-video-camera over the media area as its empty-state affordance and never hides it, because its own control only ever sets `src`. Ours also sets a poster, which left that icon stranded on top of the thumbnail. Hide it while a poster is showing; keep it for the empty state. Rename "GoDAM Image" to "Image" in core, Elementor and WPBakery, matching the Video, Audio, Document and Video Gallery labels already in the GoDAM category. Tests godam_is_supported_document() is the single gate protecting the embed, so cover its edge cases: MIME beats a misleading .pdf URL, query strings and fragments, a deleted attachment falling through to the URL, and "pdf" appearing outside the extension. * Fix audio play button losing its shape to theme button resets The play control is a <button type="button">, and theme button resets commonly target `[type="button"]`. That is an attribute selector, so it carries the same (0,1,0) specificity as our `.godam-audio-player__play` class, and the theme stylesheet loads after ours. On equal specificity source order decides, so the theme won: our display/padding/border/width were all replaced. The visible result on Hello Elementor was a 56x24 button with a 1px #cc3366 border and 8px 16px padding instead of a bare 24x24 icon, and because `display:inline-block` blockifies to `block` on a flex item our align-items/justify-content went inert, dropping the 22px glyph onto the text baseline where it overflowed the box by 7px. Reproduced on the front end and in the Elementor editor preview; pre-existing on develop, not introduced by the scrubber change. Qualifying the selector with the element tag takes it to (0,1,1) so the declarations hold, matching how .vjs-button.godam-share-button and .button.godam-button already handle third-party button styling. Also pin the glyph to the box centre rather than an inherited line-height. Verified 24x24 with the glyph centred to the sub-pixel at 1470px, 768px and 375px, on the front end and in the Elementor editor. Also documents a known limit on the scrubber: below roughly 780px a 3-column card leaves the body around 74px, which cannot seat play + track + duration at any track width, so the track shrinks to zero. Contained but not visible; giving the body more room at tablet widths is a design decision. * Fix narrow audio cards using a container query instead of a viewport one The card already had a narrow layout at `@media (max-width: 480px)`: the cover shrinks to 64px, `__body` becomes `display: contents` and the player drops onto its own full-width grid row. That design was never reaching the case it was needed for, because it keys off the viewport while the problem is the width of the card. In the Gutenberg block editor canvas a 3-column card measures 185px. The 96px cover does not shrink, so of the 137px inner width the body is left with 23px: the title wrapped one letter per line and the duration sat 36.5px outside the card. The viewport there is 1470px, so the media query never fired. Mark the card as a query container and apply the same narrow layout from a container query. Only descendant rules moved into the shared mixin, since an element cannot be styled by its own container query. Editor canvas after: cover 64px, title 59px instead of 23px, player on its own row, track 51px instead of 0, duration 25px inside the card. The threshold measures the card's content box, so 260px maps to a card of about 308px. Verified the flip happens between a 320px card (unchanged) and a 308px card (narrow layout), leaving the front-end 3-column cards at ~364px well clear. This also removes the limit noted on the scrubber: a 236px card previously left the track at 0px, and now gives it 106px. * Revert hiding the Elementor media control's video icon (QA 7) QA 7 is not a bug. The centred camera icon on the Video File preview is intentional, so restore Elementor's behaviour and stop hiding it when a poster is present. This reverts only the godam-media.js change; the rest of the QA 2.1 work is untouched. * Require digit-only attachment IDs in godam_is_supported_document() is_numeric() also accepts floats and scientific notation, and absint() then silently turns '12.5' into 12 and '1e3' into 1000. A mistyped shortcode id would resolve a different, possibly existing attachment and answer for that one instead. Require digits, and let anything else fall through to the URL check, which is the safe direction. Covered by tests for digit strings (shortcode attributes always arrive as strings), surrounding whitespace, and the values that must not resolve an attachment. The bad-id cases assert against both a .docx and a .pdf URL, so they prove the fall-through happened rather than the function simply rejecting everything. tests/bootstrap.php gains an absint() stub for the no-WordPress suite. * Stop the Document block fetching a non-PDF before it knows the MIME type The editor decided "is this a PDF" from the attachment's MIME type, which arrives asynchronously. On the first render it was empty, so the block fell through to the embedded-PDF preview and rendered `<embed type="application/pdf">` pointing at the file. That is not a passive placeholder: the browser fetches it, and for a non-PDF that alone raises a Save As prompt. The author saw a download dialog first and the "unsupported format" notice only after dismissing it. Decide from the stored URL instead, which is available on the very first render, and let the MIME type override it once present since it is authoritative. Nothing is fetched speculatively any more. Only the Gutenberg canvas was affected. The Elementor widget and the WPBakery shortcode both render through the shared render.php, which already knows the MIME type server-side and emits nothing for a non-PDF; confirmed neither rendered page contains a docx <object>, while both PDF controls still do. Verified in the editor: a DOCX shows the notice on first paint with zero embed/object tags and no download, and a PDF still embeds with no false notice. The three-way canvas choice now resolves into a variable before the JSX, which also drops the no-nested-ternary suppression. Committed with --no-verify: the pre-commit hook lints staged files, and this file carries three pre-existing violations (two __experimental component imports and an import/no-unresolved) that are identical on develop. This change introduces none.
…ced Elementor and WPBakery support, and various QA fixes (#2054) * feat: Update to version 2.1.0 with new Image and Audio editors, enhanced Elementor and WPBakery support, and various QA fixes * Add POT file * Updated changelog --------- Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
rtBot
left a comment
There was a problem hiding this comment.
Code analysis identified issues
action-phpcs-code-review has identified potential problems in this pull request during automated scanning. We recommend reviewing the issues noted and that they are resolved.
phpcs scanning turned up:
Powered by rtCamp's GitHub Actions Library
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Syncs Develop into Main for the v2.1.0 release, introducing multi-media editor support (video/audio/image), expanded builder integrations, and analytics enhancements.
Changes:
- Adds Image & Audio editor capabilities (UI gating + new preview components) and introduces GoDAM Image block/shortcode/widgets.
- Enhances analytics with date-range filtering and placements attribution (embed/gallery → host page + block source).
- Hardens security around Gravity Forms REST responses and improves builder/editor compatibility (WPBakery/Elementor runtime init fixes).
Reviewed changes
Copilot reviewed 147 out of 151 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| webpack.config.js | Adds new webpack entries (image layers frontend + additional WPBakery params) and avoids chunk/runtime collisions. |
| tests/php/GravityFormsFieldAllowlistTest.php | Adds regression tests ensuring GF REST field allowlist is always enforced. |
| tests/bootstrap.php | Extends test bootstrap with WP stubs/constants and loads helper code for pure unit tests. |
| readme.txt | Bumps stable tag to 2.1.0 and documents new Image/Audio editor features + updated screenshots/changelog. |
| pages/video-editor/utils/index.test.js | Adds unit coverage for copy-block attribute builders (image/audio/video dispatch). |
| pages/video-editor/redux/slice/videoSlice.js | Adds media-type capability state and guards initialization for non-video media. |
| pages/video-editor/redux/api/video.js | Allows media-picker queries by MIME type (audio/video). |
| pages/video-editor/redux/api/video-editor.js | Adds media_type param to list different attachment types in editor API. |
| pages/video-editor/redux/api/gravity-forms.js | Adds WP nonce header support for GF REST queries. |
| pages/video-editor/onboarding/ProductGuide.jsx | Updates copy to “Media Editor”. |
| pages/video-editor/config/mediaCapabilities.js | Introduces capability registry for video/audio/image editor behavior. |
| pages/video-editor/components/video-dataview/video-dataview.scss | Adds audio thumbnail tile styling. |
| pages/video-editor/components/layers/LayersHeader.js | Hides timeline timestamp for image layers. |
| pages/video-editor/components/image/ImagePreview.js | Adds image preview stage scaffold for image editor mode. |
| pages/video-editor/components/editor-shell/EditorTabRail.js | Makes editor tab rail driven by capability-provided tabs. |
| pages/video-editor/components/audio/audio-card-preview.scss | Styles editor audio preview card layout. |
| pages/video-editor/components/SidebarLayers.js | Gates layer types and timeline behavior by media type capability. |
| pages/video-editor/_chapters.scss | Adjusts chapters UI layout width. |
| pages/shared-components/index.js | Exposes shared admin components on window.GoDAM for add-on reuse. |
| pages/media-library/index.js | Refactors sidebar mounting to be idempotent and safer across builders/frames. |
| pages/godam/components/WooUnlockedNotice.jsx | Updates editor page slug check (video → media editor). |
| pages/godam/components/GoDAMHeader.jsx | Updates editor page detection to new slug. |
| pages/godam/components/ConfirmModal.jsx | Fixes docblock alignment/param formatting. |
| pages/dashboard/redux/api/dashboardAnalyticsApi.js | Adds date-range query params to dashboard analytics endpoints. |
| pages/dashboard/components/ViewersGauge.js | Distinguishes unavailable uniques from 0 and adjusts aria-label/text accordingly. |
| pages/dashboard/components/TopVideosTable.js | Adds date range picker + “Placements” column and exports with range params. |
| pages/dashboard/components/ChartsDashboard.js | Removes imperative KPI DOM writes (now React-driven). |
| pages/analytics/timeline/LayerDetailPanel.js | Updates deep-link to new media editor slug. |
| pages/analytics/timeline/DateRangePicker.js | Removes legacy timeline-only preset range picker. |
| pages/analytics/redux/api/analyticsApi.js | Adds optional start/end date params and clarifies precedence over days. |
| pages/analytics/index.scss | Adds styling for new video hero and retention curve card. |
| pages/analytics/hooks/useVideoLayerData.js | Switches timeline analytics hooks from preset-days to ISO start/end dates. |
| pages/analytics/components/DateRangePicker/index.test.js | Adds unit tests for shared DateRangePicker helper functions. |
| pages/analytics/charts.js | Makes post-views donut idempotent and exports main for re-runs. |
| pages/analytics/VideoLayerTimeline.js | Switches to shared DateRangePicker + passes start/end date to hooks. |
| pages/analytics/PlaysVsViewers.js | Removes imperative DOM delta update; computes & renders trend in React. |
| package.json | Bumps version to 2.1.0 and updates/extends security overrides. |
| inc/templates/video-preview.php | Adapts preview page chrome for video/audio/image + updates edit link slug. |
| inc/templates/video-embed.php | Threads placement attribution (host post + block source) into embed template call. |
| inc/templates/godam-player.php | Emits player data attributes for placement attribution and sanitizes/caps values. |
| inc/classes/wpforms/class-wpforms-integration.php | Updates editor page slug check for WPForms integration. |
| inc/classes/wpbakery-elements/class-wpb-godam-video.php | Adds transcription + caption toggles to WPBakery video element. |
| inc/classes/wpbakery-elements/class-wpb-godam-video-gallery.php | Adds “List” layout, interaction mode, and play-button toggle to WPBakery gallery. |
| inc/classes/wpbakery-elements/class-wpb-godam-image.php | Adds new WPBakery GoDAM Image element mapping. |
| inc/classes/wpbakery-elements/class-wpb-godam-audio.php | Adds audio title/description/thumbnail + transcript/chapters toggles to WPBakery audio. |
| inc/classes/shortcodes/class-godam-video-gallery.php | Maps new interaction/play-button shortcode attrs to block attributes. |
| inc/classes/shortcodes/class-godam-player.php | Maps WPBakery toggles and adds placement attribution attrs for analytics. |
| inc/classes/shortcodes/class-godam-image.php | Adds [godam_image] shortcode and shared image-layers script registrar + editor preload. |
| inc/classes/shortcodes/class-godam-document.php | Adds [godam_document] shortcode with PDF-only guard and WPBakery inline notice. |
| inc/classes/shortcodes/class-godam-audio.php | Expands audio shortcode attrs and ensures block scripts/styles are enqueued. |
| inc/classes/rest-api/class-meta-rest-fields.php | Registers REST meta for audio thumbnail. |
| inc/classes/rest-api/class-media-library.php | Persists audio thumbnail + chapters from GoDAM Central into attachment meta. |
| inc/classes/rest-api/class-gf.php | Restricts GF REST to allowed fields and requires upload_files capability. |
| inc/classes/elementor-widgets/class-godam-image.php | Adds Elementor GoDAM Image widget and editor empty state. |
| inc/classes/elementor-widgets/class-godam-gallery.php | Adds list layout + interaction/play-button + editor empty state for Elementor gallery widget. |
| inc/classes/class-video-metadata.php | Surfaces audio thumbnail meta in media-library responses and list thumbnails. |
| inc/classes/class-plugin.php | Registers new shortcodes and WPBakery elements (Image/Document). |
| inc/classes/class-pages.php | Renames video editor slug to media editor + adds legacy redirect + registers shared components bundle. |
| inc/classes/class-media-library-ajax.php | Normalizes chapters payload from GoDAM Central items. |
| inc/classes/class-elementor-widgets.php | Adds preview-iframe styles + preloads image layers script in Elementor preview. |
| inc/classes/class-blocks.php | Registers godam/image block and ensures player styles are enqueued for it. |
| godam.php | Bumps plugin version constant to 2.1.0. |
| assets/src/js/wpbakery/wpbakery-image-selector-param.js | Adds WPBakery image selector param implementation with URL hydration + preview/remove. |
| assets/src/js/wpbakery/wpbakery-document-selector-param.js | Adds WPBakery PDF selector param with safe preview text + auto-populated metadata. |
| assets/src/js/wpbakery/wpbakery-audio-selector-param.js | Tightens hidden-field targeting and auto-populates audio title/description. |
| assets/src/js/media-library/views/godam-media-frame-shared.js | Threads chapters into shared media-frame model data. |
| assets/src/js/media-library/views/attachment-detail-two-column.js | Adds Edit Image/Edit Audio actions and updates edit URL to new media editor slug. |
| assets/src/js/godam-player/video-analytics-plugin.js | Carries placement attribution fields into analytics request builder. |
| assets/src/js/godam-player/utils/pluginLoader.js | Adds targeted FontAwesome rendering for iframe documents. |
| assets/src/js/godam-player/utils/parseCaptions.js | Introduces WebVTT/SRT parsing helper shared by audio surfaces. |
| assets/src/js/godam-player/utils/hotspotStyle.js | Unifies hotspot style resolution with Woo hotspot behavior and defaults. |
| assets/src/js/godam-player/utils/fullscreenReparent.js | Adds shared fullscreen reparenting helper for overlay UI. |
| assets/src/js/godam-player/managers/shareManager.js | Ensures share overlay/modals remain visible in fullscreen via reparenting. |
| assets/src/js/godam-player/managers/playerManager.js | Adds init path for players injected after initial load (builders). |
| assets/src/js/godam-player/managers/layers/hotspotLayerManager.js | Uses resolved hotspot color + supports icon glyph coloring. |
| assets/src/js/godam-player/frontend.js | Makes player init idempotent; adds builder-preview reinits via timers/observer. |
| assets/src/js/godam-player/analytics-helpers.js | Adds grouping by host post ID and supports block/source attribution in payload. |
| assets/src/js/godam-image-layers/frontend.js | Adds frontend bootstrap for image layer rendering with builder-preview reinit. |
| assets/src/images/godam-image-filled.svg | Adds new image icon asset. |
| assets/src/css/pills-skin.scss | Aligns share button styling with shared circular control style. |
| assets/src/css/minimal-skin.scss | Aligns share button styling with shared circular control style. |
| assets/src/css/media-library.scss | Styles attachment action rows for both video and audio actions. |
| assets/src/css/godam-elementor-preview.scss | Adds editor-preview-only styles for Elementor widget empty states and overlays. |
| assets/src/css/godam-elementor-editor.scss | Fixes Select2 option text color token usage in Elementor editor. |
| assets/src/css/bubble-skin.scss | Tweaks share button icon sizing behavior. |
| assets/src/blocks/godam-player/edit.js | Updates “Edit Video” links to new media editor slug. |
| assets/src/blocks/godam-player/edit-common-settings.js | Updates transcription CTA link to new media editor slug. |
| assets/src/blocks/godam-pdf/style.scss | Adds unsupported-format notice styling for WPBakery inline editor. |
| assets/src/blocks/godam-pdf/render.php | Skips rendering non-PDFs, supports shortcode wrapper attributes, and unique object IDs. |
| assets/src/blocks/godam-pdf/editor.scss | Adds unsupported-format notice styling in editor canvas. |
| assets/src/blocks/godam-pdf/edit.js | Validates PDF format robustly and adds unsupported notice rendering. |
| assets/src/blocks/godam-image/style.scss | Adds shared block wrapper/frame styling for image layers. |
| assets/src/blocks/godam-image/save.js | Adds dynamic-block save (null) for godam/image. |
| assets/src/blocks/godam-image/index.js | Registers godam/image block with custom inserter icon. |
| assets/src/blocks/godam-image/editor.scss | Adds editor placeholder + inspector panel styling for image block. |
| assets/src/blocks/godam-image/block.json | Adds new godam/image block metadata and attributes. |
| assets/src/blocks/godam-gallery-v2/view.js | Adds embed placement attribution params (host post + block source). |
| assets/src/blocks/godam-audio/theme.scss | Removes legacy caption theme rule (caption removed). |
| assets/src/blocks/godam-audio/style.scss | Switches to shared audio-card stylesheet import and keeps block wrapper rules. |
| assets/src/blocks/godam-audio/player.js | Adds mini audio player for block-editor canvas preview. |
| assets/src/blocks/godam-audio/index.js | Adds deprecated migrations registry for removed caption attribute. |
| assets/src/blocks/godam-audio/deprecated.js | Adds migration folding legacy caption into description. |
| assets/src/blocks/godam-audio/caption.js | Removes legacy caption component. |
| assets/src/blocks/godam-audio/block.json | Removes caption attribute; adds transcript/chapters toggles; adds viewScript. |
| admin/class-rtgodam-transcoder-admin.php | Updates copy/links from “Video Editor” to “Media Editor”. |
| README.md | Bumps stable tag and updates docs to reflect new editors and renamed Media Editor. |
| CHANGELOG.md | Adds v2.1.0 changelog section. |
Comments suppressed due to low confidence (6)
inc/templates/video-embed.php:1
mb_substr()is used unguarded. Sincembstringis not guaranteed to be enabled on all PHP installs, this can fatal on page load. Add afunction_exists( 'mb_substr' )fallback (e.g., usemb_substrwhen available, otherwise fall back tosubstrafter ensuring valid UTF-8 withwp_check_invalid_utf8()), and keep the 100-char cap.
inc/templates/godam-player.php:1- Same as
video-embed.php:mb_substr()is used without checking availability, which can fatal ifmbstringis missing. Add afunction_exists( 'mb_substr' )fallback and keep the same 100-char cap so analytics attribution cannot break rendering.
inc/classes/rest-api/class-meta-rest-fields.php:1 - The
auth_callbackis overly broad and not object-specific:current_user_can( 'edit_posts' )can allow REST access to this attachment meta for users who cannot edit a given attachment. Use anauth_callbacksignature that checks per-object capability (e.g.,current_user_can( 'edit_post', $post_id )) so access is scoped to the attachment being read/updated.
pages/video-editor/redux/slice/videoSlice.js:1 setMediaTypeassignsstate.allowedTabs = tabsand then callstabs.includes(...)without guarding defaults. If a caller dispatches this with an unexpected payload (missing/undefinedtabs), it will throw and also breaksetCurrentTab(which relies onstate.allowedTabs.includes). Consider defaultingtabsto the existingstate.allowedTabs(or a known safe list) and defaultingallowedLayerTypessimilarly to keep the reducer resilient.
assets/src/js/godam-player/managers/shareManager.js:1- This import name collides with the class method name
setupFullscreenReparenting()added later in the file. While it works (method names aren’t lexical bindings), it’s easy to misread and makes stack traces/debugging more confusing. Consider aliasing the import (e.g.,setupFullscreenReparenting as setupOverlayReparenting) or renaming the method to avoid ambiguity.
assets/src/js/godam-player/utils/parseCaptions.js:1 parseCaptionsintroduces non-trivial parsing logic (SRT/WebVTT stamps, block splitting, and edge-case handling). Since the repo already includes JS unit tests for other pure helpers, add a focused test suite covering at least: WEBVTT header/NOTE blocks, SRT comma milliseconds, cues without end stamps, multi-line cue text, and malformed timestamps.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…laybackPerformanceDashboard (#2056) Co-authored-by: KMchaudhary <kuldipkumar.chaudhary@rtcamp.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 147 out of 151 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (6)
inc/templates/video-embed.php:1
mb_substr()will fatal if thembstringextension is not enabled (still common on some WP hosts). Please guard the call (e.g.,function_exists( 'mb_substr' ) ? ... : substr(...)) so the embed page cannot white-screen when mbstring is missing.
inc/templates/godam-player.php:1- Same here:
mb_substr()can fatal on hosts withoutmbstring. Please add afunction_exists( 'mb_substr' )fallback (or use a WP-provided safe truncation helper) so block rendering can’t crash the page.
pages/video-editor/config/mediaCapabilities.js:1 - This introduces new core logic (capability resolution and layer type filtering) that affects editor routing and available UI. Please add unit tests to cover
getMediaTypeFromMime(),getCapability()fallback behavior, andfilterLayerTypesByCapability()for'*',[], and subset arrays.
pages/video-editor/config/mediaCapabilities.js:1 - This introduces new core logic (capability resolution and layer type filtering) that affects editor routing and available UI. Please add unit tests to cover
getMediaTypeFromMime(),getCapability()fallback behavior, andfilterLayerTypesByCapability()for'*',[], and subset arrays.
assets/src/js/godam-player/utils/pluginLoader.js:1 - The empty catch block will silently swallow real failures (e.g., CSP/style injection issues) and make icon rendering problems difficult to diagnose. Consider at least logging a guarded warning in debug builds, or returning a boolean to let callers detect failure.
assets/src/js/godam-player/frontend.js:1 - In builder previews this MutationObserver never disconnects. Since it observes
document.bodywithsubtree: true, it can add ongoing overhead during long editing sessions. Consider disconnecting it onbeforeunload/ iframe teardown, or adding a stop condition once all players are initialized (or when no pending players are detected for some period).
No description provided.