MM-64483 YouTube Preview UI Update (#31288)
* fix youtube thumbnails * fix lint issues, update snapshots * Update _videos.scss * Update _videos.scss * revert change to 'YouTubeVideo' to 'YoutubeVideo' * switch back to Youtube instead of YouTube * accessibility fixes -add focus state * fix lint issues * Update youtube_video.test.tsx.snap * remove logic for delaying different aspect ratios. always use 16:9 * Update _videos.scss * fix rounded corners * use css variables for radius * restore attributes from original, remove bottom margin from player to prevent vertical shift * fix issue with margin and nested 'video-div' elements * update class to 'video-playing' * fix linter issues * address copilot review feedback and remove unused css * Update youtube_video.test.tsx * fixed failing test * lint issue fix * Update youtube_video.test.tsx * review feedback changes * Update _videos.scss * update test and snapshot * fix lint issues * add scaling button on hover state back in * feat: Make YouTube video component strings translatable - Add useIntl import and formatMessage usage - Create YouTubePrefix component for internationalized prefix - Update YouTubeThumbnail component to use translatable strings - Add translation IDs: - youtube_video.play.aria_label - youtube_video.thumbnail.alt_text - youtube_video.play_button.aria_label - youtube_video.type Addresses reviewer feedback to make hardcoded strings translatable using React Intl's useIntl hook. Co-authored-by: Matthew Birtch <matthewbirtch@users.noreply.github.com> * fix lint issues * Update en.json * Update youtube_video.test.tsx.snap * remove youtube prefix * fix lint issues * Update youtube_video.test.tsx.snap * fix lint issue --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Matthew Birtch <matthewbirtch@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
47a7e62b44
Коммит
b42f4fbffd
@@ -37,7 +37,7 @@ describe('YouTube Video', () => {
|
||||
cy.get('.play-button', {timeout: TIMEOUTS.TEN_SEC}).click();
|
||||
|
||||
// * Video should be loaded in the iframe
|
||||
cy.get('.video-div > iframe').should('exist');
|
||||
cy.get('.video-playing iframe').should('exist');
|
||||
|
||||
// # Collapse video
|
||||
cy.get('.post__embed-visibility').click();
|
||||
@@ -52,7 +52,7 @@ describe('YouTube Video', () => {
|
||||
cy.get('.play-button').should('exist');
|
||||
|
||||
// * Video should not be played in the iframe
|
||||
cy.get('.video-div > iframe').should('not.exist');
|
||||
cy.get('.video-playing iframe').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,41 +63,51 @@ exports[`YoutubeVideo should match init snapshot 1`] = `
|
||||
</h4>
|
||||
<div
|
||||
className="video-div embed-responsive-item"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="embed-responsive video-div__placeholder"
|
||||
<YouTubeThumbnail
|
||||
onError={[Function]}
|
||||
play={[Function]}
|
||||
thumbnailUrl="https://img.youtube.com/vi/xqCoNej8Zxo/maxresdefault.jpg"
|
||||
useMaxResThumbnail={true}
|
||||
videoTitle="Youtube title"
|
||||
>
|
||||
<div
|
||||
aria-label="Play Youtube title on YouTube"
|
||||
className="video-thumbnail__container"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Connect(Component)
|
||||
src="linkForThumbnail"
|
||||
src="https://img.youtube.com/vi/xqCoNej8Zxo/maxresdefault.jpg"
|
||||
>
|
||||
<Memo(ExternalImage)
|
||||
dispatch={[Function]}
|
||||
enableSVGs={false}
|
||||
hasImageProxy={false}
|
||||
src="linkForThumbnail"
|
||||
src="https://img.youtube.com/vi/xqCoNej8Zxo/maxresdefault.jpg"
|
||||
>
|
||||
<img
|
||||
alt="youtube video thumbnail"
|
||||
alt="Thumbnail for Youtube title on YouTube"
|
||||
className="video-thumbnail"
|
||||
src="linkForThumbnail"
|
||||
onError={[Function]}
|
||||
src="https://img.youtube.com/vi/xqCoNej8Zxo/maxresdefault.jpg"
|
||||
/>
|
||||
</Memo(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
<div
|
||||
className="block"
|
||||
aria-label="Play video"
|
||||
className="play-button"
|
||||
role="presentation"
|
||||
>
|
||||
<span
|
||||
className="play-button"
|
||||
>
|
||||
<span />
|
||||
</span>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
className="icon-play"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</YouTubeThumbnail>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,19 +139,21 @@ exports[`YoutubeVideo should match snapshot for playing state 1`] = `
|
||||
</h4>
|
||||
<div
|
||||
className="video-div embed-responsive-item"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<iframe
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen={true}
|
||||
frameBorder="0"
|
||||
height="360px"
|
||||
referrerPolicy="no-referrer"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-presentation"
|
||||
src="https://www.youtube.com/embed/xqCoNej8Zxo?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1"
|
||||
title="Youtube title"
|
||||
width="480px"
|
||||
/>
|
||||
<div
|
||||
className="video-playing"
|
||||
>
|
||||
<iframe
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen={true}
|
||||
frameBorder="0"
|
||||
height="100%"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-presentation"
|
||||
src="https://www.youtube.com/embed/xqCoNej8Zxo?autoplay=1&rel=0&fs=1&enablejsapi=1"
|
||||
title="Youtube title"
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -171,19 +183,22 @@ exports[`YoutubeVideo should match snapshot for playing state and \`youtubeRefer
|
||||
</h4>
|
||||
<div
|
||||
className="video-div embed-responsive-item"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<iframe
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen={true}
|
||||
frameBorder="0"
|
||||
height="360px"
|
||||
referrerPolicy="strict-origin-when-cross-origin"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-presentation"
|
||||
src="https://www.youtube.com/embed/xqCoNej8Zxo?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1"
|
||||
title="Youtube title"
|
||||
width="480px"
|
||||
/>
|
||||
<div
|
||||
className="video-playing"
|
||||
>
|
||||
<iframe
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen={true}
|
||||
frameBorder="0"
|
||||
height="100%"
|
||||
referrerPolicy="origin"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-presentation"
|
||||
src="https://www.youtube.com/embed/xqCoNej8Zxo?autoplay=1&rel=0&fs=1&enablejsapi=1"
|
||||
title="Youtube title"
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,8 +7,6 @@ import {Provider} from 'react-redux';
|
||||
|
||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
||||
import ExternalImage from 'components/external_image';
|
||||
|
||||
import mockStore from 'tests/test_store';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
@@ -56,8 +54,10 @@ describe('YoutubeVideo', () => {
|
||||
</Provider>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find(ExternalImage).prop('src')).toEqual('linkForThumbnail');
|
||||
expect(wrapper.find('a').text()).toEqual('Youtube title');
|
||||
|
||||
// Verify that useMaxResThumbnail is true by default
|
||||
expect((wrapper.find('YoutubeVideo').instance() as YoutubeVideo).state.useMaxResThumbnail).toBe(true);
|
||||
expect(wrapper.find('h4').text()).toEqual('YouTube - Youtube title');
|
||||
});
|
||||
|
||||
test('should match snapshot for playing state', () => {
|
||||
@@ -75,6 +75,12 @@ describe('YoutubeVideo', () => {
|
||||
);
|
||||
wrapper.setState({playing: true});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
// Verify that the iframe has a referrerPolicy attribute (set to 'origin') when youtubeReferrerPolicy is true.
|
||||
expect(wrapper.find('.video-playing iframe').prop('referrerPolicy')).toEqual('origin');
|
||||
|
||||
// Verify that the iframe src includes the new parameters
|
||||
expect(wrapper.find('.video-playing iframe').prop('src')).toEqual('https://www.youtube.com/embed/xqCoNej8Zxo?autoplay=1&rel=0&fs=1&enablejsapi=1');
|
||||
});
|
||||
|
||||
test('should use url if secure_url is not present', () => {
|
||||
@@ -89,6 +95,26 @@ describe('YoutubeVideo', () => {
|
||||
};
|
||||
const wrapper = shallow(<YoutubeVideo {...props}/>);
|
||||
|
||||
expect(wrapper.find(ExternalImage).prop('src')).toEqual('linkUrl');
|
||||
// Verify that useMaxResThumbnail is true by default
|
||||
expect(wrapper.state('useMaxResThumbnail')).toBe(true);
|
||||
});
|
||||
|
||||
describe('thumbnail fallback', () => {
|
||||
it('should fallback to hqdefault.jpg on image error', () => {
|
||||
const wrapper = shallow(<YoutubeVideo {...baseProps}/>);
|
||||
|
||||
// Simulate an image error by calling handleImageError.
|
||||
(wrapper.instance() as YoutubeVideo).handleImageError();
|
||||
|
||||
// Verify that useMaxResThumbnail is now false (will use hqdefault.jpg).
|
||||
expect(wrapper.state('useMaxResThumbnail')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should initialize with useMaxResThumbnail set to true', () => {
|
||||
const wrapper = shallow(<YoutubeVideo {...baseProps}/>);
|
||||
|
||||
// Verify that the component initializes with useMaxResThumbnail = true
|
||||
expect(wrapper.state('useMaxResThumbnail')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import type {OpenGraphMetadata} from '@mattermost/types/posts';
|
||||
|
||||
@@ -20,24 +21,113 @@ type Props = {
|
||||
|
||||
type State = {
|
||||
playing: boolean;
|
||||
useMaxResThumbnail: boolean;
|
||||
prevLink: string;
|
||||
}
|
||||
|
||||
type YouTubeThumbnailProps = {
|
||||
play: () => void;
|
||||
videoTitle: string;
|
||||
onError: () => void;
|
||||
thumbnailUrl: string;
|
||||
useMaxResThumbnail: boolean;
|
||||
};
|
||||
|
||||
function YouTubeThumbnail({play, videoTitle, onError, thumbnailUrl}: YouTubeThumbnailProps) {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
return (
|
||||
<div
|
||||
className='video-thumbnail__container'
|
||||
onClick={play}
|
||||
role='button'
|
||||
aria-label={formatMessage({
|
||||
id: 'youtube_video.play.aria_label',
|
||||
defaultMessage: 'Play {videoTitle} on YouTube',
|
||||
}, {
|
||||
videoTitle,
|
||||
})}
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
play();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ExternalImage src={thumbnailUrl}>
|
||||
{(src) => (
|
||||
<img
|
||||
className='video-thumbnail'
|
||||
src={src}
|
||||
alt={formatMessage({
|
||||
id: 'youtube_video.thumbnail.alt_text',
|
||||
defaultMessage: 'Thumbnail for {videoTitle} on YouTube',
|
||||
}, {
|
||||
videoTitle,
|
||||
})}
|
||||
onError={onError}
|
||||
/>
|
||||
)}
|
||||
</ExternalImage>
|
||||
<div
|
||||
className='play-button'
|
||||
role='presentation'
|
||||
aria-label={formatMessage({
|
||||
id: 'youtube_video.play_button.aria_label',
|
||||
defaultMessage: 'Play video',
|
||||
})}
|
||||
>
|
||||
<i
|
||||
className='icon-play'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default class YoutubeVideo extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
playing: false,
|
||||
useMaxResThumbnail: true,
|
||||
prevLink: props.link,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: Props, state: State): State | null {
|
||||
static getDerivedStateFromProps(props: Props, state: State): Partial<State> | null {
|
||||
const nextState: Partial<State> = {};
|
||||
|
||||
if (!props.show && state.playing) {
|
||||
return {playing: false};
|
||||
nextState.playing = false;
|
||||
}
|
||||
return null;
|
||||
|
||||
if (props.link !== state.prevLink) {
|
||||
nextState.useMaxResThumbnail = true;
|
||||
nextState.prevLink = props.link;
|
||||
}
|
||||
|
||||
return Object.keys(nextState).length > 0 ? nextState : null;
|
||||
}
|
||||
|
||||
getMaxResUrl(link: string) {
|
||||
const videoId = getVideoId(link);
|
||||
return `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
|
||||
}
|
||||
|
||||
getHQUrl(link: string) {
|
||||
const videoId = getVideoId(link);
|
||||
return `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
|
||||
}
|
||||
|
||||
handleImageError = () => {
|
||||
this.setState({
|
||||
useMaxResThumbnail: false,
|
||||
});
|
||||
};
|
||||
|
||||
play = () => {
|
||||
this.setState({playing: true});
|
||||
};
|
||||
@@ -48,11 +138,12 @@ export default class YoutubeVideo extends React.PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const {metadata, link} = this.props;
|
||||
|
||||
const videoId = getVideoId(link);
|
||||
const videoTitle = metadata?.title || 'unknown';
|
||||
const time = handleYoutubeTime(link);
|
||||
|
||||
const thumbnailUrl = this.state.useMaxResThumbnail ? this.getMaxResUrl(link) : this.getHQUrl(link);
|
||||
|
||||
const header = (
|
||||
<h4>
|
||||
<span className='video-type'>{'YouTube - '}</span>
|
||||
@@ -71,51 +162,37 @@ export default class YoutubeVideo extends React.PureComponent<Props, State> {
|
||||
|
||||
if (this.state.playing) {
|
||||
content = (
|
||||
<iframe
|
||||
src={'https://www.youtube.com/embed/' + videoId + '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' + time}
|
||||
width='480px'
|
||||
height='360px'
|
||||
frameBorder='0'
|
||||
allowFullScreen={true}
|
||||
allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
|
||||
referrerPolicy={this.props.youtubeReferrerPolicy ? 'strict-origin-when-cross-origin' : 'no-referrer'}
|
||||
title={videoTitle}
|
||||
sandbox='allow-scripts allow-same-origin allow-popups allow-presentation'
|
||||
/>
|
||||
<div className='video-playing'>
|
||||
<iframe
|
||||
src={`https://www.youtube.com/embed/${videoId}?autoplay=1&rel=0&fs=1&enablejsapi=1${time}`}
|
||||
title={videoTitle}
|
||||
width='100%'
|
||||
height='100%'
|
||||
frameBorder='0'
|
||||
allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'
|
||||
allowFullScreen={true}
|
||||
referrerPolicy={this.props.youtubeReferrerPolicy ? 'origin' : undefined}
|
||||
sandbox='allow-scripts allow-same-origin allow-popups allow-presentation'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const image = metadata?.images[0];
|
||||
|
||||
content = (
|
||||
<div className='embed-responsive video-div__placeholder'>
|
||||
<div className='video-thumbnail__container'>
|
||||
<ExternalImage src={image?.secure_url || image?.url || ''}>
|
||||
{(safeUrl) => (
|
||||
<img
|
||||
src={safeUrl}
|
||||
alt='youtube video thumbnail'
|
||||
className='video-thumbnail'
|
||||
/>
|
||||
)}
|
||||
</ExternalImage>
|
||||
<div className='block'>
|
||||
<span className='play-button'><span/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<YouTubeThumbnail
|
||||
play={this.play}
|
||||
videoTitle={videoTitle}
|
||||
onError={this.handleImageError}
|
||||
thumbnailUrl={thumbnailUrl}
|
||||
useMaxResThumbnail={this.state.useMaxResThumbnail}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className='post__embed-container'
|
||||
>
|
||||
<div className='post__embed-container'>
|
||||
<div>
|
||||
{header}
|
||||
<div
|
||||
className='video-div embed-responsive-item'
|
||||
onClick={this.play}
|
||||
>
|
||||
<div className='video-div embed-responsive-item'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
@@ -123,7 +200,7 @@ export default class YoutubeVideo extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
public static isYoutubeLink(link: string): boolean {
|
||||
static isYoutubeLink(link: string): boolean {
|
||||
return Boolean(link.trim().match(ytRegex));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6449,5 +6449,8 @@
|
||||
"workspace_limits.teams_limit_reached.view_upgrade_options": "View upgrade options",
|
||||
"workspace_limits.upgrade": "Upgrade to avoid {planName} data limits",
|
||||
"workspace_limits.upgrade_reasons.free": "{planName} is restricted to {messagesLimit} message history and {storageLimit} file storage. You can delete items to free up space or upgrade to a paid plan.",
|
||||
"yourcomputer": "Your computer"
|
||||
"yourcomputer": "Your computer",
|
||||
"youtube_video.play_button.aria_label": "Play video",
|
||||
"youtube_video.play.aria_label": "Play {videoTitle} on YouTube",
|
||||
"youtube_video.thumbnail.alt_text": "Thumbnail for {videoTitle} on YouTube"
|
||||
}
|
||||
|
||||
@@ -1,44 +1,117 @@
|
||||
@use "utils/functions";
|
||||
@use "utils/variables";
|
||||
@use "utils/mixins";
|
||||
|
||||
.post {
|
||||
// Container for both video and thumbnail
|
||||
.video-div {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow: hidden; // Ensure hover effects stay contained
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
height: 360px;
|
||||
border-radius: 4px;
|
||||
max-height: 270px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
iframe,
|
||||
video {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.video-thumbnail {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.block {
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
height: 150px;
|
||||
border-radius: 10px;
|
||||
margin: -75px 0 0 -100px;
|
||||
background-color: functions.alpha-color(variables.$black, 0.5);
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
border-radius: var(--radius-s);
|
||||
aspect-ratio: 16/9; // Always use 16:9
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
// Container for thumbnails
|
||||
.video-thumbnail__container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border-radius: var(--radius-s);
|
||||
aspect-ratio: 16/9; // Always use 16:9
|
||||
background-color: #000;
|
||||
outline: none; // Remove default focus outline
|
||||
|
||||
// Use existing button focus mixin
|
||||
@include mixins.button-focus;
|
||||
|
||||
// Darkening overlay on hover
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
&::after {
|
||||
background-color: rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.play-button {
|
||||
background-color: #FF0033; //youtube red
|
||||
transform: scale(1.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Playing state container
|
||||
.video-playing {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
aspect-ratio: 16/9; // Always use 16:9
|
||||
}
|
||||
|
||||
// Thumbnail image
|
||||
.video-thumbnail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
// Play button container
|
||||
.play-button {
|
||||
position: absolute;
|
||||
top: calc(50% - 28px);
|
||||
left: calc(50% - 40px);
|
||||
display: flex;
|
||||
width: 80px;
|
||||
height: 56px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-m);
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, transform 0.2s ease;
|
||||
|
||||
i.icon-play {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
color: #fff;
|
||||
font-size: 48px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Video title and type
|
||||
.video-type {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
opacity: 0.8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
@@ -46,28 +119,6 @@
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.play-button {
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
right: 51px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 4px solid functions.alpha-color(variables.$white, 0.4);
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 20px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 36px solid transparent;
|
||||
border-bottom: 36px solid transparent;
|
||||
border-left: 60px solid functions.alpha-color(variables.$white, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.img-div {
|
||||
position: relative;
|
||||
max-width: 480px;
|
||||
@@ -93,11 +144,4 @@
|
||||
.video-loading {
|
||||
height: 368px;
|
||||
}
|
||||
|
||||
.video-div__placeholder {
|
||||
display: flex;
|
||||
height: 360px;
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user