217 lines
6 KiB
JavaScript
217 lines
6 KiB
JavaScript
|
import React from 'react';
|
||
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||
|
import SimpleMarkdown from 'simple-markdown';
|
||
|
import MarkupUtils from '../utils/MarkupUtils';
|
||
|
import MarkdownModal from './MarkdownModal';
|
||
|
import ChangeLogActionCreators from '../actions/ChangeLogActionCreators';
|
||
|
import ChangeLogStore from '../stores/ChangeLogStore';
|
||
|
import ExperimentUtils from '../utils/ExperimentUtils';
|
||
|
import UserSettingsModalActionCreators from '../actions/UserSettingsModalActionCreators';
|
||
|
import i18n from '../i18n';
|
||
|
import '../styles/change_log.styl';
|
||
|
import Embed from './Embed';
|
||
|
import AnalyticsUtils from '../utils/AnalyticsUtils';
|
||
|
import moment from 'moment';
|
||
|
import {UserSettingsSections} from '../Constants';
|
||
|
|
||
|
const CHANGELOG_BLOG_LINK = 'https://blog.discordapp.com/tagged/changelog';
|
||
|
const DEFAULT_RULES = MarkupUtils.getDefaultRules();
|
||
|
const DEFAULT_LHEADING_RULE = SimpleMarkdown.defaultRules.lheading;
|
||
|
const DEFAULT_LINK_RULE = SimpleMarkdown.defaultRules.link;
|
||
|
const DEFAULT_IMAGE_RULE = SimpleMarkdown.defaultRules.image;
|
||
|
|
||
|
const render = MarkupUtils.parserFor({
|
||
|
...DEFAULT_RULES,
|
||
|
image: {
|
||
|
...DEFAULT_IMAGE_RULE,
|
||
|
},
|
||
|
|
||
|
link: {
|
||
|
...DEFAULT_LINK_RULE,
|
||
|
|
||
|
parse(capture, parse, state) {
|
||
|
let callToAction;
|
||
|
if (capture[2].startsWith('https://discordapp.com/nitro')) {
|
||
|
callToAction = e => {
|
||
|
UserSettingsModalActionCreators.open(UserSettingsSections.PREMIUM);
|
||
|
state.changeLog.track('change_log_cta_clicked');
|
||
|
ChangeLogActionCreators.hideChangeLog();
|
||
|
e.preventDefault();
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
...DEFAULT_LINK_RULE.parse(capture, parse, state),
|
||
|
callToAction,
|
||
|
};
|
||
|
},
|
||
|
|
||
|
react(node, output, state) {
|
||
|
const link = (
|
||
|
<a
|
||
|
key={state.key}
|
||
|
href={SimpleMarkdown.sanitizeUrl(node.target)}
|
||
|
title={node.title}
|
||
|
onClick={node.callToAction}
|
||
|
target="_blank"
|
||
|
className={node.callToAction ? 'cta' : null}>
|
||
|
{output(node.content, state)}
|
||
|
</a>
|
||
|
);
|
||
|
|
||
|
return link;
|
||
|
// return node.callToAction ? (
|
||
|
// <div className="changelog-button">
|
||
|
// {link}
|
||
|
// </div>
|
||
|
// ) : link;
|
||
|
},
|
||
|
},
|
||
|
|
||
|
list: SimpleMarkdown.defaultRules.list,
|
||
|
lheading: {
|
||
|
...DEFAULT_LHEADING_RULE,
|
||
|
|
||
|
parse(capture, parse, state) {
|
||
|
return {
|
||
|
className: /\{(.+?)}/.exec(capture[1])[1],
|
||
|
level: capture[2] === '=' ? 1 : 2,
|
||
|
content: SimpleMarkdown.parseInline(parse, capture[1].replace(/\{.+?}/, ''), state),
|
||
|
};
|
||
|
},
|
||
|
|
||
|
react(node, output, state) {
|
||
|
return React.createElement(
|
||
|
`h${node.level}`,
|
||
|
{
|
||
|
key: state.key,
|
||
|
className: node.className,
|
||
|
},
|
||
|
output(node.content, state)
|
||
|
);
|
||
|
},
|
||
|
},
|
||
|
});
|
||
|
|
||
|
const ChangeLog = React.createClass({
|
||
|
mixins: [PureRenderMixin],
|
||
|
|
||
|
statics: {
|
||
|
modalConfig: {
|
||
|
store: ChangeLogStore,
|
||
|
center: true,
|
||
|
},
|
||
|
},
|
||
|
|
||
|
close() {
|
||
|
return ChangeLogActionCreators.hideChangeLog();
|
||
|
},
|
||
|
|
||
|
track(event, onlyId = false) {
|
||
|
const {date, revision} = ChangeLogStore.getChangelog();
|
||
|
// eslint-disable-next-line camelcase
|
||
|
let payload = {change_log_id: `${date}:${revision}`};
|
||
|
if (!onlyId) {
|
||
|
payload = {
|
||
|
/* eslint-disable camelcase */
|
||
|
seconds_open: Math.round((Date.now() - this.mountedAt) / 1000),
|
||
|
max_scrolled_percentage: this.maxScrolledPercentage.toPrecision(4) * 100,
|
||
|
/* eslint-enable camelcase */
|
||
|
...payload,
|
||
|
};
|
||
|
}
|
||
|
AnalyticsUtils.track(event, payload);
|
||
|
},
|
||
|
|
||
|
joinEligibleExperiment() {
|
||
|
const changeLog = ChangeLogStore.getChangelog();
|
||
|
if (changeLog.experiment_names) {
|
||
|
ExperimentUtils.triggerFirstEligibleUserExperiment(changeLog.experiment_names);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getScrollerRef(scroller) {
|
||
|
this._scrollerNode = scroller ? scroller.getScrollerNode() : null;
|
||
|
},
|
||
|
|
||
|
componentDidMount() {
|
||
|
this.mountedAt = Date.now();
|
||
|
this.maxScrolledPercentage = 0;
|
||
|
this.joinEligibleExperiment();
|
||
|
this.track('change_log_opened', true);
|
||
|
},
|
||
|
|
||
|
componentWillUnmount() {
|
||
|
this.track('change_log_closed');
|
||
|
},
|
||
|
|
||
|
handleScroll() {
|
||
|
if (this._scrollerNode) {
|
||
|
const height = this._scrollerNode.scrollHeight.toPrecision(2) - this._scrollerNode.offsetHeight;
|
||
|
const percentage = this._scrollerNode.scrollTop / height;
|
||
|
this.maxScrolledPercentage = Math.max(this.maxScrolledPercentage, percentage);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
renderHeader(changeLog) {
|
||
|
return i18n.Messages.CHANGE_LOG_HEADER.format({
|
||
|
date: changeLog.date ? moment(changeLog.date).toDate() : new Date(),
|
||
|
});
|
||
|
},
|
||
|
|
||
|
renderFooter() {
|
||
|
return i18n.Messages.MISSED_AN_UPDATE.format({link: CHANGELOG_BLOG_LINK});
|
||
|
},
|
||
|
|
||
|
renderVideo(changeLog) {
|
||
|
const width = 451;
|
||
|
const height = 254;
|
||
|
const id = changeLog.youtube_video_id;
|
||
|
|
||
|
return id
|
||
|
? <Embed
|
||
|
scale={false}
|
||
|
type="changelog"
|
||
|
inlineMedia={true}
|
||
|
provider={{name: 'youtube'}}
|
||
|
url={`https://youtu.be/${id}`}
|
||
|
playable={true}
|
||
|
thumbnail={{
|
||
|
// eslint-disable-next-line camelcase
|
||
|
proxy_url: `https://i.ytimg.com/vi/${id}/maxresdefault.jpg`,
|
||
|
width,
|
||
|
height,
|
||
|
}}
|
||
|
video={{
|
||
|
url: `https://www.youtube.com/embed/${id}?vq=large&rel=0&controls=0&showinfo=0`,
|
||
|
width,
|
||
|
height,
|
||
|
}}
|
||
|
/>
|
||
|
: <img ref="gif" className="lead-video" src={require('../images/change-log-header.gif')} />;
|
||
|
},
|
||
|
|
||
|
render() {
|
||
|
const changeLog = ChangeLogStore.getChangelog();
|
||
|
|
||
|
return (
|
||
|
<MarkdownModal
|
||
|
className="change-log"
|
||
|
ref={this.getScrollerRef}
|
||
|
onClose={this.close}
|
||
|
renderHeader={() => this.renderHeader(changeLog)}
|
||
|
renderFooter={this.renderFooter}
|
||
|
onScroll={this.handleScroll}>
|
||
|
{this.renderVideo(changeLog)}
|
||
|
{render(changeLog.body, false, {changeLog: this})}
|
||
|
</MarkdownModal>
|
||
|
);
|
||
|
},
|
||
|
});
|
||
|
|
||
|
export default ChangeLog;
|
||
|
|
||
|
|
||
|
|
||
|
// WEBPACK FOOTER //
|
||
|
// ./discord_app/components/ChangeLog.js
|