The code powering m.abunchtell.com https://m.abunchtell.com
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

210 satır
5.8 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import Immutable from 'immutable';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import classnames from 'classnames';
  6. import Icon from 'mastodon/components/icon';
  7. import { decode as decodeIDNA } from 'mastodon/utils/idna';
  8. const getHostname = url => {
  9. const parser = document.createElement('a');
  10. parser.href = url;
  11. return parser.hostname;
  12. };
  13. const trim = (text, len) => {
  14. const cut = text.indexOf(' ', len);
  15. if (cut === -1) {
  16. return text;
  17. }
  18. return text.substring(0, cut) + (text.length > len ? '…' : '');
  19. };
  20. const domParser = new DOMParser();
  21. const addAutoPlay = html => {
  22. const document = domParser.parseFromString(html, 'text/html').documentElement;
  23. const iframe = document.querySelector('iframe');
  24. if (iframe) {
  25. if (iframe.src.indexOf('?') !== -1) {
  26. iframe.src += '&';
  27. } else {
  28. iframe.src += '?';
  29. }
  30. iframe.src += 'autoplay=1&auto_play=1';
  31. // DOM parser creates html/body elements around original HTML fragment,
  32. // so we need to get innerHTML out of the body and not the entire document
  33. return document.querySelector('body').innerHTML;
  34. }
  35. return html;
  36. };
  37. export default class Card extends React.PureComponent {
  38. static propTypes = {
  39. card: ImmutablePropTypes.map,
  40. maxDescription: PropTypes.number,
  41. onOpenMedia: PropTypes.func.isRequired,
  42. compact: PropTypes.bool,
  43. defaultWidth: PropTypes.number,
  44. cacheWidth: PropTypes.func,
  45. };
  46. static defaultProps = {
  47. maxDescription: 50,
  48. compact: false,
  49. };
  50. state = {
  51. width: this.props.defaultWidth || 280,
  52. embedded: false,
  53. };
  54. componentWillReceiveProps (nextProps) {
  55. if (!Immutable.is(this.props.card, nextProps.card)) {
  56. this.setState({ embedded: false });
  57. }
  58. }
  59. handlePhotoClick = () => {
  60. const { card, onOpenMedia } = this.props;
  61. onOpenMedia(
  62. Immutable.fromJS([
  63. {
  64. type: 'image',
  65. url: card.get('embed_url'),
  66. description: card.get('title'),
  67. meta: {
  68. original: {
  69. width: card.get('width'),
  70. height: card.get('height'),
  71. },
  72. },
  73. },
  74. ]),
  75. 0
  76. );
  77. };
  78. handleEmbedClick = () => {
  79. const { card } = this.props;
  80. if (card.get('type') === 'photo') {
  81. this.handlePhotoClick();
  82. } else {
  83. this.setState({ embedded: true });
  84. }
  85. }
  86. setRef = c => {
  87. if (c) {
  88. if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth);
  89. this.setState({ width: c.offsetWidth });
  90. }
  91. }
  92. renderVideo () {
  93. const { card } = this.props;
  94. const content = { __html: addAutoPlay(card.get('html')) };
  95. const { width } = this.state;
  96. const ratio = card.get('width') / card.get('height');
  97. const height = width / ratio;
  98. return (
  99. <div
  100. ref={this.setRef}
  101. className='status-card__image status-card-video'
  102. dangerouslySetInnerHTML={content}
  103. style={{ height }}
  104. />
  105. );
  106. }
  107. render () {
  108. const { card, maxDescription, compact } = this.props;
  109. const { width, embedded } = this.state;
  110. if (card === null) {
  111. return null;
  112. }
  113. const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
  114. const horizontal = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
  115. const interactive = card.get('type') !== 'link';
  116. const className = classnames('status-card', { horizontal, compact, interactive });
  117. const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
  118. const ratio = card.get('width') / card.get('height');
  119. const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
  120. const description = (
  121. <div className='status-card__content'>
  122. {title}
  123. {!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
  124. <span className='status-card__host'>{provider}</span>
  125. </div>
  126. );
  127. let embed = '';
  128. let thumbnail = <div style={{ backgroundImage: `url(${card.get('image')})`, width: horizontal ? width : null, height: horizontal ? height : null }} className='status-card__image-image' />;
  129. if (interactive) {
  130. if (embedded) {
  131. embed = this.renderVideo();
  132. } else {
  133. let iconVariant = 'play';
  134. if (card.get('type') === 'photo') {
  135. iconVariant = 'search-plus';
  136. }
  137. embed = (
  138. <div className='status-card__image'>
  139. {thumbnail}
  140. <div className='status-card__actions'>
  141. <div>
  142. <button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
  143. {horizontal && <a href={card.get('url')} target='_blank' rel='noopener'><Icon id='external-link' /></a>}
  144. </div>
  145. </div>
  146. </div>
  147. );
  148. }
  149. return (
  150. <div className={className} ref={this.setRef}>
  151. {embed}
  152. {!compact && description}
  153. </div>
  154. );
  155. } else if (card.get('image')) {
  156. embed = (
  157. <div className='status-card__image'>
  158. {thumbnail}
  159. </div>
  160. );
  161. } else {
  162. embed = (
  163. <div className='status-card__image'>
  164. <Icon id='file-text' />
  165. </div>
  166. );
  167. }
  168. return (
  169. <a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}>
  170. {embed}
  171. {description}
  172. </a>
  173. );
  174. }
  175. }