Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
057adfc
Add new helper to utilise new REST endpoint
getdave Apr 22, 2021
d095a91
Provide docblock
getdave Jun 9, 2021
d3b0f24
Provide docblock
getdave Apr 22, 2021
81b2ec2
Get title tag contents from remote URL
getdave Apr 22, 2021
7eb2240
Create distinct data object for rich meta rather than rely on overidi…
getdave May 4, 2021
1d0eae8
Add example of adding rich details to the link preview
getdave May 4, 2021
50ad09c
Add icon, image and description
getdave May 27, 2021
28353a2
Add icon, image and description
getdave May 27, 2021
dee6d6f
Add in description
getdave May 20, 2021
fece66f
Remove url data to hook and prevent setting state on unmounted
getdave May 20, 2021
47edef2
Avoid ref destructure
getdave May 21, 2021
e6633aa
Add loading animation
getdave May 21, 2021
21fb199
Clear richdata on url change
getdave May 21, 2021
5920515
Avoid rich data in search items
getdave May 21, 2021
48f9090
Account for preview layout variation requirements
getdave May 21, 2021
b0d4a33
Only fetch if fetchRemoteUrlData is available
getdave May 21, 2021
f89d4ab
Fix text overflow ellipsis
getdave May 21, 2021
74501fb
Improve icon visual scaling
getdave May 21, 2021
5f1688a
Manage fetching as state
getdave May 26, 2021
03e4ab6
Add tests for rich data and resolve exposed bugs
getdave Jun 9, 2021
9a108af
Fix broken async/await code and restore test running
getdave May 26, 2021
b15ad00
Tidy code comments
getdave May 26, 2021
b4df9d2
Updates to use visually truncated description text
getdave Jun 9, 2021
5b9736b
Clip image height, center and provide border-radius and background
getdave Jun 9, 2021
03bcacd
Apply correct spacing as per design provided
getdave May 27, 2021
8189a80
Allow text to flow (almost) to edge of `Edit` button before ellipsis
getdave May 27, 2021
b3551c6
Constrain image height to 140px as per Figma design
getdave May 27, 2021
1516592
Add tests to cover missing data edge cases
getdave May 27, 2021
7d91aba
Fix cancel pending fetch when URL changes.
getdave May 27, 2021
0acd56c
Remove isMounted anti pattern in favour of explictly cancelled promis…
getdave May 27, 2021
ba57bb1
Update comments to make reason for cancelling on URL change clearer
getdave May 27, 2021
cfa5775
Fix broken tests by disabling rich reviews in those tests which do no…
getdave May 27, 2021
fecdf86
Move make cancelable util into own file
getdave May 27, 2021
5030f96
Allow for passing options to fetchRemoteUrlData
getdave Jun 9, 2021
08d3e39
Use AbortController to cancel requests rather than cancellable promis…
getdave Jun 4, 2021
7749b6e
Force remount preview when the URL changes
getdave Jun 4, 2021
1d694c9
Implement simpler hook via abortable fetch
getdave Jun 4, 2021
621119c
Fix to ensure correct handling of aborted vs standard fetch errors
getdave Jun 4, 2021
1bdce6d
Add test to cover resetting fetching state if rich data requests fails
getdave Jun 4, 2021
dad3a70
Simplify hook implementation
getdave Jun 7, 2021
d726883
Guard for legacy browsers
getdave Jun 7, 2021
429a42a
Refactor to useReducer
getdave Jun 7, 2021
654b0d4
Disable rich previews by default and enable only in inline text by de…
getdave Jun 7, 2021
b722bd7
Update packages/editor/src/components/provider/use-block-editor-setti…
getdave Jun 7, 2021
b236b71
Update to hasRichPreviews
getdave Jun 7, 2021
e450f66
Use correct prop to enable rich previews in rich text
getdave Jun 7, 2021
4e48952
Fix e2e test to allow unlinking when rich data obscures block toolbar
getdave Jun 8, 2021
de2824e
Add permenant missing data placeholders with loading animation
getdave Jun 8, 2021
0d55791
Fix tests
getdave Jun 9, 2021
ebb9346
Update snapshots
getdave Jun 9, 2021
939f7be
Update doc block
getdave Jun 9, 2021
bf22b24
Restore makecancellable to a local util
getdave Jun 9, 2021
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
3 changes: 3 additions & 0 deletions packages/block-editor/src/components/link-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ function LinkControl( {
suggestionsQuery = {},
noURLSuggestion = false,
createSuggestionButtonText,
hasRichPreviews = false,
} ) {
if ( withCreateSuggestion === undefined && createSuggestion ) {
withCreateSuggestion = true;
Expand Down Expand Up @@ -249,8 +250,10 @@ function LinkControl( {

{ value && ! isEditingLink && ! isCreatingPage && (
<LinkPreview
key={ value?.url } // force remount when URL changes to avoid race conditions for rich previews
value={ value }
onEditClick={ () => setIsEditingLink( true ) }
hasRichPreviews={ hasRichPreviews }
/>
) }

Expand Down
120 changes: 96 additions & 24 deletions packages/block-editor/src/components/link-control/link-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,121 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Button, ExternalLink } from '@wordpress/components';
import {
Button,
ExternalLink,
__experimentalText as Text,
} from '@wordpress/components';
import { filterURLForDisplay, safeDecodeURI } from '@wordpress/url';
import { Icon, globe } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { ViewerSlot } from './viewer-slot';

export default function LinkPreview( { value, onEditClick } ) {
import useRemoteUrlData from './use-remote-url-data';

export default function LinkPreview( {
value,
onEditClick,
hasRichPreviews = false,
} ) {
// Avoid fetching if rich previews are not desired.
const maybeRemoteURL = hasRichPreviews ? value?.url : null;

const { richData, isFetching } = useRemoteUrlData( maybeRemoteURL );

// Rich data may be an empty object so test for that.
const hasRichData = richData && Object.keys( richData ).length;

const displayURL =
( value && filterURLForDisplay( safeDecodeURI( value.url ), 16 ) ) ||
'';

return (
<div
aria-label={ __( 'Currently selected' ) }
aria-selected="true"
className={ classnames( 'block-editor-link-control__search-item', {
'is-current': true,
'is-rich': hasRichData,
'is-fetching': !! isFetching,
'is-preview': true,
} ) }
>
<span className="block-editor-link-control__search-item-header">
<ExternalLink
className="block-editor-link-control__search-item-title"
href={ value.url }
>
{ ( value && value.title ) || displayURL }
</ExternalLink>
{ value && value.title && (
<span className="block-editor-link-control__search-item-info">
{ displayURL }
<div className="block-editor-link-control__search-item-top">
<span className="block-editor-link-control__search-item-header">
<span
className={ classnames(
'block-editor-link-control__search-item-icon',
{
'is-image': richData?.icon,
}
) }
>
{ richData?.icon ? (
<img src={ richData?.icon } alt="" />
) : (
<Icon icon={ globe } />
) }
</span>
<span className="block-editor-link-control__search-item-details">
<ExternalLink
className="block-editor-link-control__search-item-title"
href={ value.url }
>
{ richData?.title || value?.title || displayURL }
</ExternalLink>
{ value?.url && (
<span className="block-editor-link-control__search-item-info">
{ displayURL }
</span>
) }
</span>
) }
</span>

<Button
variant="secondary"
onClick={ () => onEditClick() }
className="block-editor-link-control__search-item-action"
>
{ __( 'Edit' ) }
</Button>
<ViewerSlot fillProps={ value } />
</span>

<Button
variant="secondary"
onClick={ () => onEditClick() }
className="block-editor-link-control__search-item-action"
>
{ __( 'Edit' ) }
</Button>
<ViewerSlot fillProps={ value } />
</div>

{ ( hasRichData || isFetching ) && (
<div className="block-editor-link-control__search-item-bottom">
<div
aria-hidden={ ! richData?.image }
className={ classnames(
'block-editor-link-control__search-item-image',
{
'is-placeholder': ! richData?.image,
}
) }
>
{ richData?.image && (
<img src={ richData?.image } alt="" />
) }
</div>
<div
aria-hidden={ ! richData?.description }
className={ classnames(
'block-editor-link-control__search-item-description',
{
'is-placeholder': ! richData?.description,
}
) }
>
{ richData?.description && (
<Text truncate numberOfLines="2">
{ richData.description }
</Text>
) }
</div>
</div>
) }
</div>
);
}
148 changes: 144 additions & 4 deletions packages/block-editor/src/components/link-control/style.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
$block-editor-link-control-number-of-actions: 1;
$preview-image-height: 140px;

@keyframes loadingpulse {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}

.block-editor-link-control {
position: relative;
Expand Down Expand Up @@ -141,31 +154,51 @@ $block-editor-link-control-number-of-actions: 1;
}

&.is-current {
flex-direction: column; // allow for stacking.
background: transparent;
border: 0;
width: 100%;
cursor: default;
padding: $grid-unit-20;
padding-left: $grid-unit-30;
}

.block-editor-link-control__search-item-header {
display: block;
flex-direction: row;
align-items: flex-start;
margin-right: $grid-unit-10;
overflow: hidden;
white-space: nowrap;
}

&.is-preview .block-editor-link-control__search-item-header {
display: flex;
flex: 1; // fill available space.
}

.block-editor-link-control__search-item-details {
overflow: hidden; // clip to force text ellipsis.
}

.block-editor-link-control__search-item-icon {
margin-right: 1em;
min-width: 24px;
position: relative;
top: 0.2em;
margin-right: $grid-unit-10;
width: 18px; // half of 32px to improve perceived resolution.
height: 18px; // half of 32px to improve perceived resolution.

svg,
img {
max-width: none;
width: 18px; // half of 32px to improve perceived resolution.
}
}


.block-editor-link-control__search-item-info,
.block-editor-link-control__search-item-title {
overflow: hidden;
text-overflow: ellipsis;
padding-right: $grid-unit-30;

.components-external-link__icon {
position: absolute;
Expand All @@ -189,6 +222,10 @@ $block-editor-link-control-number-of-actions: 1;
span {
font-weight: normal;
}

svg {
display: none; // specifically requested to be removed visually as well.
}
}

.block-editor-link-control__search-item-info {
Expand All @@ -206,6 +243,109 @@ $block-editor-link-control-number-of-actions: 1;
background-color: $gray-100;
border-radius: 2px;
}

.block-editor-link-control__search-item-description {
padding-top: 12px;
margin: 0;

&.is-placeholder {
margin-top: 12px;
padding-top: 0;
height: 28px;
display: flex;
flex-direction: column;
justify-content: space-around;

&::before,
&::after {
display: block;
content: "";
height: 0.7em;
width: 100%;
background-color: $gray-100;
border-radius: 3px;
}
}

.components-text {
font-size: 0.9em;
}
}

.block-editor-link-control__search-item-image {
display: flex;
width: 100%;
background-color: $gray-100;
justify-content: center;
height: $preview-image-height; // limit height
max-height: $preview-image-height; // limit height
overflow: hidden;
border-radius: 2px;
margin-top: 12px;

&.is-placeholder {
background-color: $gray-100;
border-radius: 3px;
}

img {
display: block; // remove unwanted space below image
max-width: 100%;
height: $preview-image-height; // limit height
max-height: $preview-image-height; // limit height
}
}
}

.block-editor-link-control__search-item-top {
display: flex;
flex-direction: row;
width: 100%; // clip.
}

.block-editor-link-control__search-item-bottom {
transition: opacity 1.5s;
min-height: 100px;
width: 100%;
}


.block-editor-link-control__search-item.is-fetching {

.block-editor-link-control__search-item-description {
&::before,
&::after {
animation: loadingpulse 1s linear infinite;
animation-delay: 0.5s; // avoid animating for fast network responses
}

}

.block-editor-link-control__search-item-image {
animation: loadingpulse 1s linear infinite;
animation-delay: 0.5s; // avoid animating for fast network responses
}

.block-editor-link-control__search-item-icon {
svg,
img {
opacity: 0;
}

&::before {
content: "";
display: block;
background-color: $gray-100;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 100%;
animation: loadingpulse 1s linear infinite;
animation-delay: 0.5s; // avoid animating for fast network responses
}
}
}

.block-editor-link-control__loading {
Expand Down
Loading