1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-24 11:38:30 +00:00

4.0.0-rc4

* Added: Smooth Scrolling for Chat. (Thanks @neuspadrin)
* Changed: Disable mouse interaction with the background of the metadata bar when it's visible over the player in theater mode.
* Fixed: All directory features except host menus.

* API Added: tList(...) method for i18n to support localizing strings with embedded links, etc.
This commit is contained in:
SirStendec 2018-07-03 18:39:00 -04:00
parent be1e1bfd47
commit 0f88ce216f
16 changed files with 264 additions and 111 deletions

View file

@ -1,3 +1,16 @@
<div class="list-header">4.0.0-rc4<span>@012130d37baabae67622</span> <time datetime="2018-07-03">(2018-07-03)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Added: Smooth Scrolling for Chat. (Thanks neuspadrin on GitHub!)</li>
<li>Fixed: All directory features except the Host Menu.</li>
<li>Changed: Disable mouse interaction with the background of the metadata bar when it's visible in theater mode.</li>
</ul>
<div class="list-header">4.0.0-rc3.4<span>@104efaa42e14d30191d1</span> <time datetime="2018-07-02">(2018-07-02)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Stream uptime when watching a channel.</li>
<li class="tw-align-right">~ Arthur, King of the Britons</li>
</ul>
<div class="list-header">4.0.0-rc3.3<span>@85ad2d458fb808c0365f</span> <time datetime="2018-06-29">(2018-06-29)</time></div>
<ul class="chat-menu-content menu-side-padding">
<li>Fixed: Update class names for all input elements to use Twitch's new, long-winded methodology.</li>

View file

@ -361,6 +361,10 @@ export class TranslationManager extends Module {
t(...args) {
return this._.t(...args);
}
tList(...args) {
return this._.tList(...args);
}
}
@ -391,6 +395,7 @@ export default class TranslationCore {
const allowMissing = options.allowMissing ? transformPhrase : null;
this.onMissingKey = typeof options.onMissingKey === 'function' ? options.onMissingKey : allowMissing;
this.transformPhrase = typeof options.transformPhrase === 'function' ? options.transformPhrase : transformPhrase;
this.transformList = typeof options.transformList === 'function' ? options.transformList : transformList;
this.delimiter = options.delimiter || /\s*\|\|\|\|\s*/;
this.tokenRegex = options.tokenRegex || /%\{(.*?)(?:\|(.*?))?\}/g;
this.formatters = Object.assign({}, DEFAULT_FORMATTERS, options.formatters || {});
@ -457,7 +462,7 @@ export default class TranslationCore {
this.extend(phrases);
}
t(key, phrase, options, use_default) {
preT(key, phrase, options, use_default) {
const opts = options == null ? {} : options;
let p, locale;
@ -492,8 +497,20 @@ export default class TranslationCore {
if ( this.transformation )
p = this.transformation(key, p, opts, locale, this.tokenRegex);
return [p, opts, locale];
}
t(key, phrase, options, use_default) {
const [p, opts, locale] = this.preT(key, phrase, options, use_default);
return this.transformPhrase(p, opts, locale, this.tokenRegex, this.formatters);
}
tList(key, phrase, options, use_default) {
const [p, opts, locale] = this.preT(key, phrase, options, use_default);
return this.transformList(p, opts, locale, this.tokenRegex, this.formatters);
}
}
@ -503,6 +520,55 @@ export default class TranslationCore {
const DOLLAR_REGEX = /\$/g;
export function transformList(phrase, substitutions, locale, token_regex, formatters) {
const is_array = Array.isArray(phrase);
if ( substitutions == null )
return is_array ? phrase[0] : phrase;
let p = phrase;
const options = typeof substitutions === 'number' ? {count: substitutions} : substitutions;
if ( is_array )
p = p[pluralTypeIndex(
locale || 'en',
has(options, 'count') ? options.count : 1
)] || p[0];
const result = [];
token_regex.lastIndex = 0;
let idx = 0, match;
while((match = token_regex.exec(p))) {
const nix = match.index,
arg = match[1],
fmt = match[2];
if ( nix !== idx )
result.push(p.slice(idx, nix));
let val = get(arg, options);
if ( val != null ) {
const formatter = formatters[fmt];
if ( typeof formatter === 'function' )
val = formatter(val, locale, options);
else if ( typeof val === 'string' )
val = REPLACE.call(val, DOLLAR_REGEX, '$$');
result.push(val);
}
idx = nix + match[0].length;
}
if ( idx < p.length )
result.push(p.slice(idx));
return result;
}
export function transformPhrase(phrase, substitutions, locale, token_regex, formatters) {
const is_array = Array.isArray(phrase);
if ( substitutions == null )

View file

@ -100,7 +100,7 @@ class FrankerFaceZ extends Module {
FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = {
major: 4, minor: 0, revision: 0, extra: '-rc3.3',
major: 4, minor: 0, revision: 0, extra: '-rc4',
build: __webpack_hash__,
toString: () =>
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`

View file

@ -61,7 +61,7 @@ export default class Metadata extends Module {
setup() {
const socket = this.resolve('socket'),
apollo = this.resolve('site.apollo'),
created_at = apollo.getFromQuery('ChannelPage_ChannelInfoBar_User', 'data.user.stream.createdAt');
created_at = apollo.getFromQuery('ChannelPage_ChannelHeader', 'data.user.stream.createdAt');
if ( ! created_at )
return {};

View file

@ -21,8 +21,8 @@ export default class ChannelBar extends Module {
this.inject('metadata');
this.inject('socket');
this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', CHANNEL_QUERY);
this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', data => {
this.apollo.registerModifier('ChannelPage_ChannelHeader', CHANNEL_QUERY);
this.apollo.registerModifier('ChannelPage_ChannelHeader', data => {
const u = data && data.data && data.data.user;
if ( u ) {
const o = u.profileViewCount = new Number(u.profileViewCount || 0);

View file

@ -51,7 +51,7 @@ export default class Scroller extends Module {
ui: {
path: 'Chat > Behavior >> General',
title: 'Smooth Scrolling',
description: 'Animates new chat messages into view. Will speed up if necessary to keep up with chat.',
description: 'Smoothly slide new chat messages into view. Speed will increase as necessary to keep up with chat.',
component: 'setting-select-box',
data: [
{value: 0, title: 'Disabled'},
@ -85,9 +85,8 @@ export default class Scroller extends Module {
this.chat.context.on('changed:chat.scroller.smooth-scroll', val => {
this.smoothScroll = val;
for(const inst of this.ChatScroller.instances) {
for(const inst of this.ChatScroller.instances)
inst.ffzSetSmoothScroll(val);
}
});
this.ChatScroller.ready((cls, instances) => {
@ -250,52 +249,62 @@ export default class Scroller extends Module {
}
cls.prototype.smoothScrollBottom = function() {
if(this.state.ffzSmoothAnimation){
cancelAnimationFrame(this.state.ffzSmoothAnimation);
}
this.isScrollingToBottom = true;
if ( this._ffz_smooth_animation )
cancelAnimationFrame(this._ffz_smooth_animation);
this.ffz_is_smooth_scrolling = true;
// Step setting value is # pixels to scroll per 10ms.
// 1 is pretty slow, 2 medium, 3 fast, 4 very fast.
let step = this.ffz_smooth_scroll;
const scrollContent = this.scroll.scrollContent;
const targetTop = scrollContent.scrollHeight - scrollContent.clientHeight;
const difference = targetTop - scrollContent.scrollTop;
let step = this.ffz_smooth_scroll,
old_time = Date.now();
const scroll_content = this.scroll.scrollContent,
target_top = scroll_content.scrollHeight - scroll_content.clientHeight,
difference = target_top - scroll_content.scrollTop;
// If we are falling behind speed us up
if (difference > scrollContent.clientHeight) {
if ( difference > scroll_content.clientHeight ) {
// we are a full scroll away, just jump there
step = difference;
} else if (difference > 200) {
} else if ( difference > 200 ) {
// we are starting to fall behind, speed it up a bit
step += step * parseInt(difference / 200, 10);
}
let prevTime = Date.now();
const smoothAnimation = () => {
if(this.state.ffzFrozen) {
this.isScrollingToBottom = false;
return;
}
// See how much time has passed to get a step based off the delta
const currentTime = Date.now();
const delta = currentTime - prevTime;
const currentStep = step * (delta / 10);
// we need to move at least one full pixel for scrollTop to do anything in this delta.
if (currentStep >= 1) {
prevTime = currentTime;
if (scrollContent.scrollTop < (scrollContent.scrollHeight - scrollContent.clientHeight)) {
scrollContent.scrollTop += currentStep;
this.state.ffzSmoothAnimation = requestAnimationFrame(smoothAnimation);
} else {
scrollContent.scrollTop = scrollContent.scrollHeight - scrollContent.clientHeight;
this.isScrollingToBottom = false;
}
} else {
// the frame happened so quick since last update we didn't move a full pixel yet.
// should only be possible if the FPS of a browser went over 60fps.
this.state.ffzSmoothAnimation = requestAnimationFrame(smoothAnimation);
step += step * Math.floor(difference / 200);
}
const smoothAnimation = () => {
if ( this.state.ffzFrozen )
return this.ffz_is_smooth_scrolling = false;
// See how much time has passed to get a step based off the delta
const current_time = Date.now(),
delta = current_time - old_time,
current_step = step * (delta / 10);
// we need to move at least one full pixel for scrollTop to do anything in this delta.
if ( current_step >= 1 ) {
const scroll_top = scroll_content.scrollTop,
target_top = scroll_content.scrollHeight - scroll_content.clientHeight;
old_time = current_time;
if ( scroll_top < target_top ) {
scroll_content.scrollTop = scroll_top + current_step;
this._ffz_smooth_animation = requestAnimationFrame(smoothAnimation);
} else {
// We've reached the bottom.
scroll_content.scrollTop = target_top;
this.ffz_is_smooth_scrolling = false;
}
} else {
// The frame happened so quick since last update that we haven't moved a full pixel.
// Just wait.
this._ffz_smooth_animation = requestAnimationFrame(smoothAnimation);
}
}
smoothAnimation();
}
@ -308,13 +317,12 @@ export default class Scroller extends Module {
this._old_scroll = this.scrollToBottom;
this.scrollToBottom = function() {
if ( ! this.ffz_freeze_enabled || ! this.state.ffzFrozen ) {
if (this.ffz_smooth_scroll !== 0) {
if ( this.ffz_smooth_scroll )
this.smoothScrollBottom();
} else {
else
this._old_scroll();
}
}
}
this._ffz_handleScroll = this.handleScrollEvent;
this.handleScrollEvent = function(e) {

View file

@ -5,6 +5,12 @@
right: calc(var(--ffz-chat-width) + 25rem);
z-index: 3500;
opacity: 0;
pointer-events: none;
.tw-tooltip-wrapper, .tw-stat, .ffz-stat, button, a {
pointer-events: all;
}
}
.channel-page-layout__scroll-area--theatre-mode:hover .channel-info-bar {

View file

@ -3,9 +3,6 @@ query {
edges {
node {
createdAt
broadcaster {
profileImageURL(width: 50)
}
}
}
}

View file

@ -30,8 +30,6 @@ export default class BrowsePopular extends SiteModule {
}
modifyStreams(res) { // eslint-disable-line class-methods-use-this
this.log.info('Streams', res);
const edges = get('data.streams.edges', res);
if ( ! edges || ! edges.length )
return res;

View file

@ -2,7 +2,6 @@ query {
currentUser {
followedLiveUsers {
nodes {
profileImageURL(width: 50)
stream {
type
createdAt

View file

@ -2,7 +2,6 @@ query {
currentUser {
followedLiveUsers {
nodes {
profileImageURL(width: 50)
stream {
createdAt
}

View file

@ -3,7 +3,6 @@ query {
followedLiveUsers {
edges {
node {
profileImageURL(width: 50)
stream {
createdAt
}

View file

@ -14,6 +14,7 @@ import FOLLOWED_INDEX from './followed_index.gql';
import FOLLOWED_HOSTS from './followed_hosts.gql';
import FOLLOWED_CHANNELS from './followed_channels.gql';
import FOLLOWED_LIVE from './followed_live.gql';
import SUBSCRIBED_CHANNELS from './sidenav_subscribed.gql';
export default class Following extends SiteModule {
constructor(...args) {
@ -63,27 +64,37 @@ export default class Following extends SiteModule {
changed: () => this.ChannelCard.forceUpdate()
});
this.apollo.registerModifier('FollowedChannels_RENAME2', FOLLOWED_CHANNELS);
this.apollo.registerModifier('SideNav_SubscribedChannels', SUBSCRIBED_CHANNELS);
this.apollo.registerModifier('FollowedIndex_CurrentUser', FOLLOWED_INDEX);
this.apollo.registerModifier('FollowingLive_CurrentUser', FOLLOWED_LIVE);
this.apollo.registerModifier('FollowingHosts_CurrentUser', FOLLOWED_HOSTS);
this.apollo.registerModifier('FollowedChannels', FOLLOWED_CHANNELS);
this.apollo.registerModifier('FollowedChannels_RENAME2', res => this.modifyLiveUsers(res), false);
this.apollo.registerModifier('SideNav_SubscribedChannels', res => this.modifyLiveUsers(res, 'subscribedChannels'), false);
this.apollo.registerModifier('FollowingLive_CurrentUser', res => this.modifyLiveUsers(res), false);
this.apollo.registerModifier('FollowingHosts_CurrentUser', res => this.modifyLiveHosts(res), false);
this.apollo.registerModifier('FollowedIndex_CurrentUser', res => {
this.modifyLiveUsers(res);
this.modifyLiveHosts(res);
}, false);
this.apollo.registerModifier('FollowedChannels', res => this.modifyLiveUsers(res), false);
this.apollo.registerModifier('FollowingLive_CurrentUser', res => this.modifyLiveUsers(res), false);
this.apollo.registerModifier('FollowingHosts_CurrentUser', res => this.modifyLiveHosts(res), false);
this.hosts = new WeakMap;
}
modifyLiveUsers(res) {
const edges = get('data.currentUser.followedLiveUsers.nodes', res);
if ( ! edges || ! edges.length )
return res;
modifyLiveUsers(res, path = 'followedLiveUsers') {
const followed_live = get(`data.currentUser.${path}`, res);
if ( ! followed_live )
return;
if ( followed_live.nodes )
followed_live.nodes = this.parent.processNodes(followed_live.nodes);
else if ( followed_live.edges )
followed_live.edges = this.parent.processNodes(followed_live.edges);
res.data.currentUser.followedLiveUsers.nodes = this.parent.processNodes(edges);
return res;
}
@ -103,23 +114,29 @@ export default class Following extends SiteModule {
hosted = node.hosting,
stream = hosted && hosted.stream;
if ( ! stream || stream.game && blocked_games.includes(stream.game.game) )
if ( ! stream || stream.game && blocked_games.includes(stream.game.name) )
continue;
const store = {}; // stream.viewersCount = new Number(stream.viewersCount || 0);
if ( ! stream.viewersCount ) {
if ( ! do_grouping || ! hosts[hosted.login] )
out.push(edge);
continue;
}
const store = stream.viewersCount = new Number(stream.viewersCount || 0);
store.createdAt = stream.createdAt;
store.title = stream.title;
store.game = stream.game;
//store.game = stream.game;
if ( do_grouping ) {
const host_nodes = hosts[hosted.login];
if ( host_nodes ) {
host_nodes.push(node);
store.host_nodes = node._ffz_host_nodes = host_nodes;
this.hosts.set(store, host_nodes);
} else {
store.host_nodes = node._ffz_host_nodes = hosts[hosted.login] = [node];
this.hosts.set(store, hosts[hosted.login] = [node]);
out.push(edge);
}
@ -133,8 +150,13 @@ export default class Following extends SiteModule {
ensureQueries () {
this.apollo.ensureQuery(
'FollowedChannels',
'data.currentUser.followedLiveUsers.nodes.0.profileImageURL'
'FollowedChannels_RENAME2',
'data.currentUser.followedLiveUsers.nodes.0.stream.createdAt'
);
this.apollo.ensureQuery(
'SideNav_SubscribedChannels',
'data.currentUser.subscribedChannels.edges.0.node.stream.createdAt'
);
if ( this.router.current_name !== 'dir-following' )

View file

@ -25,7 +25,7 @@ export default class Game extends SiteModule {
['dir-game-index', 'dir-community']
);
this.apollo.registerModifier('GamePage_Game', GAME_QUERY);
this.apollo.registerModifier('GamePage_Game_RENAME2', GAME_QUERY);
}
onEnable() {

View file

@ -22,6 +22,8 @@ export const CARD_CONTEXTS = ((e ={}) => {
})();
const CREATIVE_ID = 488191;
const DIR_ROUTES = ['dir', 'dir-community', 'dir-community-index', 'dir-creative', 'dir-following', 'dir-game-index', 'dir-game-clips', 'dir-game-videos', 'dir-all', 'dir-category', 'user-videos', 'user-clips'];
@ -44,7 +46,7 @@ export default class Directory extends SiteModule {
this.inject(Game);
this.inject(BrowsePopular);
this.apollo.registerModifier('GamePage_Game', res => this.modifyStreams(res), false);
this.apollo.registerModifier('GamePage_Game_RENAME2', res => this.modifyStreams(res), false);
this.DirectoryCard = this.fine.define(
'directory-card',
@ -52,12 +54,6 @@ export default class Directory extends SiteModule {
DIR_ROUTES
);
this.CardWrapper = this.fine.define(
'directory-card-wrapper',
n => n.renderFallback && n.renderStreamFlag,
DIR_ROUTES
);
this.settings.add('directory.uptime', {
default: 1,
@ -123,8 +119,11 @@ export default class Directory extends SiteModule {
component: 'setting-check-box'
},
changed: () => this.CardWrapper.forceUpdate()
changed: () => this.DirectoryCard.forceUpdate()
});
this.routeClick = this.routeClick.bind(this);
}
@ -132,28 +131,25 @@ export default class Directory extends SiteModule {
this.css_tweaks.toggleHide('profile-hover', this.settings.get('directory.show-channel-avatars') === 2);
this.css_tweaks.toggleHide('dir-live-ind', this.settings.get('directory.hide-live'));
this.on('i18n:update', () => this.DirectoryCard.forceUpdate());
const t = this,
React = await this.web_munch.findModule('react');
const createElement = React && React.createElement;
this.CardWrapper.ready(cls => {
const old_render = cls.prototype.render;
this.DirectoryCard.ready(cls => {
const old_render = cls.prototype.render,
old_render_iconic = cls.prototype.renderIconicImage,
old_render_titles = cls.prototype.renderTitles;
cls.prototype.render = function() {
if ( get('props.streamNode.type', this) === 'rerun' && t.settings.get('directory.hide-vodcasts') )
if ( get('props.streamType', this) === 'rerun' && t.settings.get('directory.hide-vodcasts') )
return null;
return old_render.call(this);
}
this.CardWrapper.forceUpdate();
});
this.DirectoryCard.ready(cls => {
const old_render_iconic = cls.prototype.renderIconicImage,
old_render_titles = cls.prototype.renderTitles;
cls.prototype.renderIconicImage = function() {
if ( this.props.context !== CARD_CONTEXTS.SingleChannelList &&
t.settings.get('directory.show-channel-avatars') !== 1 )
@ -163,27 +159,48 @@ export default class Directory extends SiteModule {
}
cls.prototype.renderTitles = function() {
const nodes = get('props.currentViewerCount.host_nodes', this);
const nodes = t.following.hosts.get(get('props.currentViewerCount', this));
if ( this.props.hostedByChannelLogin == null || ! nodes || ! nodes.length )
return old_render_titles.call(this);
const channel = nodes[0].hosting,
stream = channel.stream;
stream = channel.stream,
game = stream && stream.game,
channel_url = `/${channel.login}`,
game_url = game && `/directory/game/${stream.game.name}`,
user_link = <a href={channel_url} data-href={channel_url} onClick={t.routeClick} class="tw-link tw-link--inherit">{channel.displayName}</a>,
game_link = game && <a href={game_url} data-href={game_url} onClick={t.routeClick} class="tw-link tw-link--inherit">{game.name}</a>;
return (<div>
<a class="tw-link tw-link--inherit" data-test-selector="preview-card-titles__primary-link">
<a href={channel_url} data-href={channel_url} onClick={t.routeClick} class="tw-link tw-link--inherit" data-test-selector="preview-card-titles__primary-link">
<h3 class="tw-ellipsis tw-font-size-5 tw-strong" title={stream.title}>{stream.title}</h3>
</a>
<div class="preview-card-titles__subtitle-wrapper">
<div data-test-selector="preview-card-titles__subtitle">
<p class="tw-c-text-alt tw-ellipsis">
<a class="tw-link tw-link--inherit">{channel.displayName}</a> playing <a class="tw-link tw-link--inherit">{stream.game.name}</a>
</p>
<p class="tw-c-text-alt tw-ellipsis">{
game ?
game.id == CREATIVE_ID ?
t.i18n.tList('directory.user-creative', '%{user} being %{game}', {
user: user_link,
game: game_link
}) :
t.i18n.tList('directory.user-playing', '%{user} playing %{game}', {
user: user_link,
game: game_link
})
: user_link
}</p>
</div>
<div data-test-selector="preview-card-titles__subtitle">
<p class="tw-c-text-alt tw-ellipsis">
Hosted by {nodes.length > 1 ? `${nodes.length} channels` : nodes[0].displayName}
</p>
<p class="tw-c-text-alt tw-ellipsis">{
nodes.length > 1 ?
t.i18n.t('directory.hosted.by-many', 'Hosted by %{count} channel%{count|en_plural}', nodes.length) :
t.i18n.tList('directory.hosted.by-one', 'Hosted by %{user}', {
user: <a href={`/${nodes[0].login}`} data-href={`/${nodes[0].login}`} onClick={t.routeClick} class="tw-link tw-link--inherit">{nodes[0].displayName}</a>
})
}</p>
</div>
</div>
</div>);
@ -194,7 +211,7 @@ export default class Directory extends SiteModule {
// Game Directory Channel Cards
// TODO: Better query handling.
this.apollo.ensureQuery(
'GamePage_Game',
'GamePage_Game_RENAME2',
'data.directory.streams.edges.0.node.createdAt'
);
@ -216,9 +233,7 @@ export default class Directory extends SiteModule {
return;
const props = inst.props,
game = props.gameTitle || props.playerMetadataGame,
is_video = props.durationInSeconds != null,
is_host = props.hostedByChannelLogin != null;
game = props.gameTitle || props.playerMetadataGame;
container.classList.toggle('ffz-hide-thumbnail', this.settings.provider.get('directory.game.hidden-thumbnails', []).includes(game));
@ -237,18 +252,22 @@ export default class Directory extends SiteModule {
processNodes(edges, is_game_query = false, blocked_games) {
const out = [];
if ( blocked_games === undefined )
if ( ! Array.isArray(blocked_games) )
blocked_games = this.settings.provider.get('directory.game.blocked-games', []);
for(const edge of edges) {
const node = edge.node || edge,
store = {}; // node.viewersCount = new Number(node.viewersCount || 0);
stream = node.stream || node;
store.createdAt = node.createdAt;
store.title = node.title;
store.game = node.game;
if ( stream.viewersCount ) {
const store = stream.viewersCount = new Number(stream.viewersCount || 0);
if ( is_game_query || (! node.game || node.game && ! blocked_games.includes(node.game.game)) )
store.createdAt = stream.createdAt;
store.title = stream.title;
//store.game = stream.game;
}
if ( is_game_query || (! stream.game || stream.game && ! blocked_games.includes(stream.game.name)) )
out.push(edge);
}
@ -257,8 +276,6 @@ export default class Directory extends SiteModule {
modifyStreams(res) { // eslint-disable-line class-methods-use-this
this.log.info('Modify Streams', res);
const is_game_query = get('data.directory.__typename', res) === 'Game',
edges = get('data.directory.streams.edges', res);
@ -310,7 +327,7 @@ export default class Directory extends SiteModule {
</div>
{inst.ffz_uptime_span = <p />}
</div>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-center">
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{this.i18n.t('metadata.uptime.tooltip', 'Stream Uptime')}
{inst.ffz_uptime_tt = <div class="tw-pd-t-05" />}
</div>
@ -366,10 +383,12 @@ export default class Directory extends SiteModule {
inst.ffz_av_login = props.channelLogin;
inst.ffz_av_src = src;
const link = props.channelLinkTo && props.channelLinkTo.pathname;
card.appendChild(<a
class="ffz-channel-avatar"
href={props.channelLinkTo && props.channelLinkTo.pathname}
onClick={e => this.routeClick(e, props.channelLinkTo)} // eslint-disable-line react/jsx-no-bind
href={link}
onClick={e => this.routeClick(e, link)} // eslint-disable-line react/jsx-no-bind
>
<div class={`tw-absolute tw-right-0 tw-border-l tw-c-background ${is_video ? 'tw-top-0 tw-border-b' : 'tw-bottom-0 tw-border-t'}`}>
<figure class="tw-aspect tw-aspect--align-top">
@ -380,12 +399,25 @@ export default class Directory extends SiteModule {
}
routeClick(event, route) {
routeClick(event, url) {
event.preventDefault();
event.stopPropagation();
if ( route && route.pathname )
this.router.history.push(route.pathname);
if ( ! url ) {
const target = event.currentTarget;
if ( target ) {
const ds = target.dataset;
if ( ds && ds.href )
url = ds.href;
else if ( target.href )
url = target.href;
}
}
if ( url )
this.router.history.push(url);
}

View file

@ -0,0 +1,14 @@
query {
currentUser {
subscribedChannels {
edges {
node {
stream {
type
createdAt
}
}
}
}
}
}