Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/src/blocks/godam-image/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "godam/image",
"title": "GoDAM Image",
"title": "Image",
"category": "media",
"icon": "format-image",
"description": "Display an image with interactive GoDAM hotspot and product layers.",
Expand Down
84 changes: 68 additions & 16 deletions assets/src/blocks/godam-pdf/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ function PdfEdit( {
const [ uploadingFile, setUploadingFile ] = useState( null );
const { createErrorNotice, createWarningNotice } = useDispatch( noticesStore );

// Fetch attachment metadataauto-generated cover and file size.
const { autoGeneratedCover, fileSize } = useSelect( ( select ) => {
// Fetch attachment metadata: auto-generated cover, file size and MIME type.
const { autoGeneratedCover, fileSize, mimeType } = useSelect( ( select ) => {
if ( ! id ) {
return { autoGeneratedCover: '', fileSize: 0 };
return { autoGeneratedCover: '', fileSize: 0, mimeType: '' };
}
const media = select( coreStore ).getEntityRecord( 'postType', 'attachment', id );
return {
Expand All @@ -109,6 +109,7 @@ function PdfEdit( {
media?.meta?.rtgodam_media_pdf_thumbnail ||
'',
fileSize: media?.media_details?.filesize || 0,
mimeType: media?.mime_type || '',
};
}, [ id ] );

Expand Down Expand Up @@ -188,31 +189,40 @@ function PdfEdit( {
return;
}

// Validate MIME type — reject anything that isn't a PDF.
if ( isBlobURL( media.url ) ) {
// An in-flight upload: the MIME type is not populated yet, so validation
// is deferred to the follow-up call that carries the finished attachment.
setTemporaryURL( media.url );
// Capture file name + size so the upload UI can display them.
setUploadingFile( {
name: media.filename || media.title || __( 'Document', 'godam' ),
size: media.filesizeInBytes || 0,
} );
return;
}

// Validate the format: reject anything that isn't a PDF.
// The MediaUpload allowedTypes filter restricts the library UI, but a
// runtime check is needed as a safety net (e.g. drag-and-drop bypasses it).
// Use the MIME string, not `media.type`: library selections expose
// `mime` ("application/pdf") while uploads expose `mime_type`; `media.type`
// is the REST post type ("attachment") for uploads and would wrongly reject them.
// Testing for equality (rather than inequality) matters: a missing MIME type
// used to pass straight through, so fall back to the extension instead of
// letting an unknown format in.
const mime = media.mime || media.mime_type || '';
if ( mime && mime !== 'application/pdf' ) {
const isPdf = mime
? 'application/pdf' === mime
: /\.pdf(?:[?#]|$)/i.test( media.url || '' );

if ( ! isPdf ) {
createWarningNotice(
__( 'Only PDF files can be attached to this block. Please select a PDF document.', 'godam' ),
{ type: 'snackbar', isDismissible: true },
);
return;
}

if ( isBlobURL( media.url ) ) {
setTemporaryURL( media.url );
// Capture file name + size so the upload UI can display them.
setUploadingFile( {
name: media.filename || media.title || __( 'Document', 'godam' ),
size: media.filesizeInBytes || 0,
} );
return;
}

// Normalize the description to a string. Library selections expose it as
// a plain string, but freshly uploaded files come from the REST API where
// `description` is an object ( { raw, rendered } ). Storing the object
Expand Down Expand Up @@ -285,6 +295,25 @@ function PdfEdit( {
const isCardView = previewMode === 'card';
const hasDocument = !! ( src || temporaryURL );

// Content saved before the format was validated (or added by URL) can still hold
// a non-PDF. The front end renders nothing for those, so flag it here rather than
// showing an <embed> that just paints blank.
// Decided from the stored URL first, because that is the only signal available
// on the very first render. The attachment's MIME type arrives asynchronously,
// and waiting for it means rendering the <embed> speculatively: the browser
// fetches the file, and for a non-PDF that alone raises a download prompt
// before any notice can replace it. The MIME type still overrides the URL once
// present, since it is the authoritative answer.
//
// Trade-off: a PDF served from an extension-less URL is flagged until its MIME
// arrives. Erring that way is deliberate, because the opposite error hands the
// visitor's browser a file to download.
const isUnsupported = hasDocument && ! temporaryURL && (
mimeType
? 'application/pdf' !== mimeType
: ! /\.pdf(?:[?#]|$)/i.test( src || '' )
);

const classes = clsx( className, {
'is-transient': !! temporaryURL,
[ `is-preview-${ previewMode || 'default' }` ]: true,
Expand Down Expand Up @@ -384,6 +413,20 @@ function PdfEdit( {
);
}

// ── Editor canvas: unsupported format ─────────────────────────────────────
const UnsupportedCanvas = (
<div className="godam-pdf-unsupported" data-test-id="godam-pdf-content-unsupported">
<span className="godam-pdf-unsupported__icon dashicons dashicons-media-document" />
<h3 className="godam-pdf-unsupported__title">
{ __( 'Unsupported file format', 'godam' ) }
</h3>
<p className="godam-pdf-unsupported__desc">
{ __( 'Only PDF files are supported. Replace this file with a PDF; it will not be shown on your page.', 'godam' ) }
</p>
<p className="godam-pdf-unsupported__file">{ fileName }</p>
</div>
);

// ── Editor canvas: Default View (embedded PDF) ────────────────────────────
// Wrap in <Disabled> when the block isn't focused so the embedded PDF
// doesn't capture clicks/scroll, making the block selectable normally.
Expand Down Expand Up @@ -445,6 +488,15 @@ function PdfEdit( {
</div>
);

// Resolved before the JSX: an unsupported file wins over the view toggle, since
// neither preview can represent it.
let canvasContent = DefaultViewCanvas;
if ( isUnsupported ) {
canvasContent = UnsupportedCanvas;
} else if ( isCardView ) {
canvasContent = CardViewCanvas;
}

return (
<>
{ /* ── Toolbar ─────────────────────────────────────────────────── */ }
Expand Down Expand Up @@ -675,7 +727,7 @@ function PdfEdit( {

{ /* ── Block canvas ─────────────────────────────────────────────── */ }
<figure { ...blockProps } data-test-id="godam-pdf-canvas">
{ isCardView ? CardViewCanvas : DefaultViewCanvas }
{ canvasContent }
<Caption
attributes={ attributes }
setAttributes={ setAttributes }
Expand Down
47 changes: 47 additions & 0 deletions assets/src/blocks/godam-pdf/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,50 @@
50% { width: 40%; margin-left: 30%; }
100% { width: 10%; margin-left: 90%; }
}

// ── Unsupported-format notice ───────────────────────────────────────────────
// Editor-only. The front end renders nothing for a non-PDF (see render.php), so
// this is the author's only signal that the file will not appear on the page.
.godam-pdf-unsupported {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 4px;
padding: 40px 24px;
border: 1px dashed #c3c4c7;
border-radius: 8px;
background: #f6f7f7;

&__icon {
font-size: 32px;
width: 32px;
height: 32px;
margin-bottom: 4px;
color: #a7aaad;
}

&__title {
margin: 0;
font-size: 15px;
font-weight: 600;
line-height: 1.3;
color: #1e1e1e;
}

&__desc {
margin: 0;
max-width: 340px;
font-size: 13px;
line-height: 1.5;
color: #646970;
}

&__file {
margin: 4px 0 0;
font-size: 12px;
color: #8c8f94;
word-break: break-all;
}
}
18 changes: 18 additions & 0 deletions assets/src/blocks/godam-pdf/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@
return;
}

/*
* PDF is the only supported format. Rendering a non-PDF would emit an
* <object type="application/pdf"> pointing at a file the browser cannot display,
* which either paints an empty box (the <object> fallback is suppressed, so the
* visitor sees nothing) or starts a file download on every page load. Emit
* nothing instead; the editors surface an "unsupported format" notice so the
* author can see and fix it.
*
* Checked against the original attachment id / src rather than $godam_sources[0],
* because that may be a transcoded URL whose extension no longer reflects the
* source file. A numeric id resolves via its stored MIME type, so $godam_src is
* only consulted for GoDAM tab media and URL-only documents, exactly the cases
* where $godam_src is guaranteed set (see the source-building block above).
*/
if ( ! godam_is_supported_document( $godam_attachment_id, $godam_src ) ) {
return;
}

$godam_file_name = basename( $godam_sources[0] );

// Root wrapper: in block context merge WordPress' block-support attributes
Expand Down
19 changes: 19 additions & 0 deletions assets/src/blocks/godam-pdf/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,22 @@
}
}
}

// ── Unsupported-format notice ───────────────────────────────────────────────
// Shown only inside WPBakery's front-end editor (see GoDAM_Document::render()).
// The published page renders nothing at all for a non-PDF, so visitors never
// see this.
.godam-document-unsupported {
padding: 16px 20px;
border: 1px dashed #c3c4c7;
border-radius: 8px;
background: #f6f7f7;
text-align: center;

p {
margin: 0;
font-size: 13px;
line-height: 1.5;
color: #646970;
}
}
62 changes: 58 additions & 4 deletions assets/src/css/_godam-audio-card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 20px 24px;
// Makes the card queryable by its own width, so the narrow layout below can
// key off how wide the CARD is rather than how wide the viewport is. A card in
// a 3-column layout, or in the block editor canvas, is narrow while the
// viewport is not.
container-type: inline-size;

*,
*::before,
Expand Down Expand Up @@ -96,7 +101,16 @@
width: 100%;
margin: 16px 0 0;

&__play {
// Qualified with the element tag on purpose. Theme button resets commonly use
// `[type="button"]`, an attribute selector carrying the same (0,1,0) specificity
// as a class, and the theme stylesheet loads after ours, so on a plain
// `.godam-audio-player__play` selector source order hands the theme the win:
// its display/padding/border/width replace ours, `display:inline-block`
// blockifies to `block` on this flex item, align-items/justify-content go inert,
// and the 22px glyph drops onto the text baseline and overflows the 24px box.
// `button.godam-audio-player__play` is (0,1,1), so these hold. Matches how
// .vjs-button.godam-share-button and .button.godam-button do it elsewhere.
button#{&}__play {
flex: 0 0 auto;
width: 24px;
height: 24px;
Expand All @@ -109,13 +123,17 @@
display: flex;
align-items: center;
justify-content: center;
// Keep the glyph on the box's own centre line rather than a theme-inherited
// text baseline, so centring does not depend on the inherited line-height.
line-height: 0;

&:hover:not( :disabled ) {
color: #000;
background: transparent;
}

svg {
display: block;
width: 22px;
height: 22px;
fill: currentcolor;
Expand All @@ -124,6 +142,15 @@

&__scrubber {
flex: 1 1 auto;
// input[type=range] carries an intrinsic min-content width (~129px in
// Chrome), and the flex default of min-width:auto pins the track to it. In a
// narrow card, such as a 3-column layout, play + track + duration then need
// more room than the row has, and the non-shrinking duration label is pushed
// outside the card and the column. Allow the track to shrink so the row
// always fits. Genuinely narrow cards are handled by the container query at
// the end of this file, which gives the player its own full-width row, so
// the track no longer has to shrink to nothing to make space.
min-width: 0;
appearance: none;
-webkit-appearance: none;
padding: 0;
Expand Down Expand Up @@ -362,16 +389,18 @@
}
}

// ── Mobile: shrink cover, drop the player onto its own full-width row ───────────
// ── Narrow layout: shrink cover, drop the player onto its own full-width row ────
// The player is markup-nested inside .godam-audio-card__body (alongside the title
// and description). `display: contents` on the body dissolves its box so those
// children become grid items of __head, letting the player span the full width
// beneath the [cover][title / description] row without any markup change. Applies
// identically on the block editor, the front end and the customization preview.
@media ( max-width: 480px ) {
// Only descendant selectors live here, so the same block can be applied from a
// container query: an element cannot be styled by its own container query, and
// .godam-audio-card is the container.
@mixin godam-audio-card-narrow-layout {

.godam-audio-card {
padding: 16px;

&__head {
display: grid;
Expand Down Expand Up @@ -409,3 +438,28 @@
margin-top: 12px;
}
}

// Small viewport: the whole page is narrow, so tighten the card padding too.
@media ( max-width: 480px ) {

.godam-audio-card {
padding: 16px;
}

@include godam-audio-card-narrow-layout;
}

// Narrow CARD on a wide viewport. A 96px non-shrinking cover leaves almost
// nothing for the body once the card itself is small: in the block editor canvas
// a 3-column card measures 185px, so the body gets 23px, the title wraps one
// letter per line and the duration escapes the card. The viewport media query
// above cannot catch this, because the viewport is wide while the card is not.
// The query measures the card's content box, so this 260px maps to a card of
// roughly 308px. The default row layout stays usable down to about a 280px card
// (below that the track drops under 40px), and front-end 3-column cards measure
// ~364px, so this threshold clears both with room to spare rather than sitting a
// few pixels off the widths that must not change.
@container ( max-width: 260px ) {

@include godam-audio-card-narrow-layout;
}
Loading
Loading