logo

mastofe

My custom branche(s) on git.pleroma.social/pleroma/mastofe git clone https://hacktivis.me/git/mastofe.git

card.js (5707B)


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