Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

youtube component #20

Open
wants to merge 49 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9a021df
component youtube
jeanrodriguez Feb 28, 2019
732019b
added new files for twitter component
jeanrodriguez Feb 28, 2019
b6b99f1
youtube component
jeanrodriguez Feb 28, 2019
03770ca
Create youtube.css
jeanrodriguez Feb 28, 2019
aa83fc3
added youtube video player js
jeanrodriguez Mar 1, 2019
a20a5bb
yotube component
jeanrodriguez Mar 2, 2019
69379a6
youtube component _layout and _component
jeanrodriguez Mar 3, 2019
c10b45e
fix css
jeanrodriguez Mar 4, 2019
858397e
added YOUTUBE_API_KEY env variable, remove unuse css
jeanrodriguez Mar 4, 2019
f42ee11
Revert "added YOUTUBE_API_KEY env variable, remove unuse css"
jeanrodriguez Mar 4, 2019
5358dc6
fix css and add env variable
jeanrodriguez Mar 4, 2019
21b8185
added YOUTUBE_API_KEY
jeanrodriguez Mar 4, 2019
b36e19a
Update schema.yaml
jeanrodriguez Mar 4, 2019
ab1db28
remove unnecesary description. update youtube css
jeanrodriguez Mar 4, 2019
8ddcc6f
Update client.js
jeanrodriguez Mar 4, 2019
9f192f9
remove unused functions
jeanrodriguez Mar 5, 2019
cf79110
Update visibility.js
jeanrodriguez Mar 5, 2019
5abe5ce
removed getViewportHeight
jeanrodriguez Mar 5, 2019
e468746
Update visibility.js
jeanrodriguez Mar 5, 2019
8dd8f1c
removed _assign visbility.js
jeanrodriguez Mar 5, 2019
52b9202
Update gtm.js
jeanrodriguez Mar 5, 2019
3ec66f5
remove unused code visibility and gtm js
jeanrodriguez Mar 5, 2019
d529ab6
remove unused files
jeanrodriguez Mar 5, 2019
b587e7d
Update youtube-video-player.js
jeanrodriguez Mar 5, 2019
86a4ac4
Update youtube.js
jeanrodriguez Mar 5, 2019
fc5c953
update byline removed join and map lodash
jeanrodriguez Mar 5, 2019
0269a5c
Update byline.js
jeanrodriguez Mar 5, 2019
01540b7
Merge branch 'master' of https://github.com/clay/clay-starter into co…
jeanrodriguez Mar 7, 2019
69208b0
youtube component for clay-starter
jeanrodriguez Feb 28, 2019
fa81bda
Merge branch 'code-component-youtube' of https://github.com/clay/clay…
jeanrodriguez Mar 7, 2019
44870bb
js docs
jeanrodriguez Mar 7, 2019
4b08edf
Update subheader.css
jeanrodriguez Mar 7, 2019
d496d06
Update client.js
jeanrodriguez Mar 8, 2019
2bb136f
destruct visible object
jeanrodriguez Mar 8, 2019
b1559d0
Update youtube.css
jeanrodriguez Mar 8, 2019
c659775
Update client-env.json
jeanrodriguez Mar 8, 2019
1dfefbc
Merge branch 'master' of https://github.com/clay/clay-starter into co…
jeanrodriguez Mar 12, 2019
632f53a
Update client.js
jeanrodriguez Mar 12, 2019
d82a6de
feedbacks
jeanrodriguez Mar 12, 2019
679f8bf
Update model.js
jeanrodriguez Mar 13, 2019
daf4866
Update visibility.js
jeanrodriguez Mar 13, 2019
29d5468
removed analytics and added js docs
jeanrodriguez Mar 13, 2019
b34d3f6
removed gtm.js
jeanrodriguez Mar 13, 2019
88538d8
remove nym reference
jeanrodriguez Mar 13, 2019
cea0b08
Merge branch 'master' of https://github.com/clay/clay-starter into co…
jeanrodriguez Mar 13, 2019
fb0dd95
Update schema.yaml
jeanrodriguez Mar 13, 2019
8fc5739
feedback
jeanrodriguez Mar 13, 2019
da8a9e5
Update template.hbs
jeanrodriguez Mar 13, 2019
dbac03e
Update template.hbs
jeanrodriguez Mar 13, 2019
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
1 change: 1 addition & 0 deletions app/.env
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ CLAY_SCHEDULING_ENABLED=true
GOOGLE_CONSUMER_KEY=646733424261-m084l8oaodaubngvpte7f2hufc62l0d6.apps.googleusercontent.com
GOOGLE_CONSUMER_SECRET=KyWza3ugQbwEvn4J1nVA3RiA
GOOGLE_PROFILE_URL=https://www.googleapis.com/oauth2/v3/userinfo
YOUTUBE_API_KEY=AIzaSyCtD1a3SWW3QFzyfkLi0NpwvHL9InosQi8

CLAY_SITE_NAME="Clay Demo"
CLAY_SITE_HOST="localhost"
Expand Down
12 changes: 6 additions & 6 deletions app/components/article/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function stripHeadlineTags(oldHeadline) {

/**
* sanitize headline
* @param {object} data
* @param {Object} data
*/
function sanitizeInputs(data) {
if (has(data.headline)) {
Expand All @@ -33,8 +33,8 @@ function sanitizeInputs(data) {
/**
* set the publish date from the locals (even if it's already set),
* and format it correctly
* @param {object} data
* @param {object} locals
* @param {Object} data
* @param {Object} locals
*/
function formatDate(data, locals) {
if (_get(locals, 'date')) {
Expand All @@ -51,8 +51,8 @@ function formatDate(data, locals) {

/**
* set the canonical url from the locals (even if it's already set)
* @param {object} data
* @param {object} locals
* @param {Object} data
* @param {Object} locals
*/
function setCanonicalUrl(data, locals) {
if (_get(locals, 'publishUrl')) {
Expand All @@ -62,7 +62,7 @@ function setCanonicalUrl(data, locals) {

/**
* Set the feed image to the lede url if it isn't already set
* @param {object} data
* @param {Object} data
*/
function generateFeedImage(data) {
if (data.ledeUrl) {
Expand Down
1 change: 1 addition & 0 deletions app/components/article/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ content:
- divider
- image
- code-sample
- youtube

ledeUrl:
_label: Lede Image URL
Expand Down
3 changes: 3 additions & 0 deletions app/components/youtube-player-head/bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
_components:
youtube-player-head:
allowed: true
2 changes: 2 additions & 0 deletions app/components/youtube-player-head/schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_description: |
Note: Required for `video-player` component. YouTube API scripts, add component to head for best YouTube player performance.
19 changes: 19 additions & 0 deletions app/components/youtube-player-head/template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- data-uri="{{ default _ref _self }}" -->
{{#unless @root.locals.edit}}
{{! NYM flags for `video-player` component instances
player instances will first look for `nymYTApiReady` to be `true`
if `false`, player instances will listen for the event `nym-youtube-event:youtube-api-ready` }}
<script>
var nymYTApiReady = false;
window.onYouTubeIframeAPIReady = function () {
{{! when the YouTube API script is ready, dispatch an `nym-youtube-event:youtube-api-ready` event }}
var nymEventYTApiReady = new Event('nym-youtube-event:youtube-api-ready');
document.dispatchEvent(nymEventYTApiReady);
{{! change this flag to 'true' when the YouTube api is ready }}
nymYTApiReady = true;
}
</script>

{{! add the YouTube API script to the head for fastest player creation }}
<script type="text/javascript" src="https://www.youtube.com/iframe_api"></script>
{{/unless}}
14 changes: 14 additions & 0 deletions app/components/youtube/bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
_components:
youtube:
videoId: ''
videoType: 'editorial'
videoLocation: 'article'
playerCaption: ''
autoPlay: true
autoPlayNextVideo: true
videoPlaylist: ''
playerBorderTopCTA: 'Watch'
playerBorderTop: false
playerBorderBottom: false
previousTypeRelated: false
customPlay: false
118 changes: 118 additions & 0 deletions app/components/youtube/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use strict';

const youtubeVideoPlayer = require('../../services/universal/youtube-video-player'),
{ Visible, isElementNotHidden } = require('../../services/client/visibility'),
{ reportNow } = require('../../services/client/gtm');

module.exports = (el) => {
var autoplay = el.getAttribute('data-autoplay-video') === 'true',
videoConfig = {
videoContainerId: el.getAttribute('data-element-id').trim(),
videoId: el.getAttribute('data-video-id').trim(),
// player variables and settings
playerParams: {
loop: 1,
listType: 'playlist',
list: el.getAttribute('data-playlist').trim(),
autoplay: autoplay ? 1 : 0,
controls: 1,
enablejsapi: 1,
modestbranding: 1,
rel: 0,
showinfo: 0,
wmode: 'transparent'
},
customParams: {
autoPlayNextVideo: el.getAttribute('data-autoplay-next-video').trim(),
trackVideoType: el.getAttribute('data-track-video-type').trim(),
customPlayer: el.getAttribute('data-custom-play').trim(),
templateid: el.getAttribute('data-element-id').trim(),
muted: autoplay // always mute autplaying videos
}
},
analytics = getAnalyticsCustomDimensions(el),
visible;

if (videoConfig.customParams.trackVideoType === 'Sponsored') {
videoConfig.playerParams.list = '';
}

visible = new Visible(el, { preloadThreshold: 800 });

// when the video player element enters the viewport, load the video(s)
if (visible.preload && isElementNotHidden(el)) {
// if the YouTube api is ready the videos(s) can be loaded
if (window.nymYTApiReady === true) {
youtubeVideoPlayer.init(videoConfig);
} else {
// wait and listen for the YouTube api to be ready before loading the video(s)
document.addEventListener('nym-youtube-event:youtube-api-ready', function () {
youtubeVideoPlayer.init(videoConfig);
});
}
} else {
visible.on('preload', function () {
youtubeVideoPlayer.init(videoConfig);
});
}

/**
* Player ready event
* this fires when the player is initially loaded and pushes variables specific to the
* component into the data layer. Information about the video itself is captured from the
* native gtm.video trigger on play and finish
*/
document.addEventListener('player-ready-' + videoConfig.videoContainerId, function () {
reportNow(Object.assign({
youtubeAction: 'player ready'
}, analytics));
});

/**
* Player start event
*
* we don't need to send an event here, updating the video id for posterity
* also might be nice to send an event if we see the video id changed?
*/
document.addEventListener('player-start-' + videoConfig.videoContainerId, function (evt) {
var hasChanged = el.getAttribute('data-video-id') !== evt.player.videoId;

if (hasChanged) {
updateElementAttributes(el, evt.player);
// this will tell the gtm.video trigger to stop ignoring gtm.video events
// in the case that an external video was played initially then switched to
// an internal playlist
reportNow(Object.assign({
event: 'youtubeVideoReset',
youtubeVideoId: evt.player.videoId,
youtubeChannelName: 'New York Magazine'
}));
}
});
};

/**
* Updates Element attributes
* @param {Object} el - DOM node element
* @param {Object} config - Attributes values from player
*/
function updateElementAttributes(el, config) {
el.setAttribute('data-video-id', config.videoId);
}

/**
* Gets analytics custom dimensions for video player
* @param {Object} el
* @returns {Object} analytics
*/
function getAnalyticsCustomDimensions(el) {
return {
event: 'youtubeVideo',
youtubeVideoId: el.getAttribute('data-video-id'),
youtubeVideoLocation: el.getAttribute('data-track-video-location'),
youtubeVideoType: el.getAttribute('data-track-video-type'),
youtubeVideoTitle: el.getAttribute('data-track-video-title'),
youtubeChannelName: el.getAttribute('data-track-channel-name'),
youtubeVideoDuration: el.getAttribute('data-track-video-duration')
};
}
91 changes: 91 additions & 0 deletions app/components/youtube/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict';

const _get = require('lodash/get'),
{ getVideoDetails } = require('../../services/universal/youtube'),
defaultPlayerBorderTopCTA = 'Watch';

/**
* Override various settings by type of video
* @param {Object} data
*/
function updateSettingsByType(data) {
switch (data.videoType) {
case 'related':
// By default, display borders and CTA when `related` type is first selected, afterwards accept user's selection
data.playerBorderTopCTA = !data.previousTypeRelated && !data.playerBorderTopCTA ? defaultPlayerBorderTopCTA : data.playerBorderTopCTA;
data.playerBorderTop = !data.previousTypeRelated ? true : data.playerBorderTop;
data.playerBorderBottom = !data.previousTypeRelated ? true : data.playerBorderBottom;
data.previousTypeRelated = true;
break;
case 'sponsored':
data.autoPlay = false;
data.autoPlayNextVideo = false;
default:
// Toggle borders off if user previously selected `related` type. `sponsored` and `editorial` types share defaults
data.playerBorderTop = data.previousTypeRelated ? false : data.playerBorderTop;
data.playerBorderBottom = data.previousTypeRelated ? false : data.playerBorderBottom;
data.previousTypeRelated = false;
}
}

function clearVideoId(data) {
data.videoId = (data.videoId || '').split('&')[0];

return data;
}

function setVideoDetails(data, videoDetails) {
var maxResThumb;

if (!videoDetails.title) {
data.videoValid = false;

return data;
}

maxResThumb = _get(videoDetails, 'thumbnails.maxres.url');

data.videoValid = true;
data.channelName = videoDetails.channelTitle;
data.videoTitle = videoDetails.title;
data.videoThumbnail = maxResThumb ? maxResThumb : _get(videoDetails, 'thumbnails.high.url'); // get the maxres if available, otherwise get the high res which we know will be there
data.videoDuration = videoDetails.duration;

return data;
}

function getDefaultPlaylistBySite(locals) {
switch (locals.site.slug) {
case 'wwwthecut':
return 'PL4B448958847DA6FB';
case 'vulture':
return 'PLZQfnFyelTBOQ15kmHSgEbdjzLMWzZpL7';
case 'grubstreet':
return 'PLtmzdzCeRsyG_td56GV9JtS3yif177lfK';
case 'di':
return 'PLtmzdzCeRsyHbGTxOX4BZvSgXBh20n-_4';
case 'selectall':
return 'PLtmzdzCeRsyHh67c-VlEj8Nqpj5nL8pf6';
default:
return 'PLtmzdzCeRsyFQ64kOTZS7eBLQ1fH2feu7'; // if its a site without a default playlist, use the 'latest from new york' playlist
}
}

module.exports.save = (uri, data, locals) => {
clearVideoId(data);
updateSettingsByType(data);

if (data.videoId && !data.videoPlaylist) {
data.videoPlaylist = getDefaultPlaylistBySite(locals);
}

if (data.videoId) {
return getVideoDetails(data.videoId)
.then(videoDetails => setVideoDetails(data, videoDetails));
}

data.videoValid = true; // technically not an invalid video because no videoId so we don't want to show an error message in edit mode

return data;
};

Loading