You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
7.8 KiB

  1. import escapeTextContentForBrowser from 'escape-html';
  2. import loadPolyfills from '../mastodon/load_polyfills';
  3. import ready from '../mastodon/ready';
  4. import { start } from '../mastodon/common';
  5. start();
  6. window.addEventListener('message', e => {
  7. const data = e.data || {};
  8. if (!window.parent || data.type !== 'setHeight') {
  9. return;
  10. }
  11. ready(() => {
  12. window.parent.postMessage({
  13. type: 'setHeight',
  14. id: data.id,
  15. height: document.getElementsByTagName('html')[0].scrollHeight,
  16. }, '*');
  17. });
  18. });
  19. function main() {
  20. const IntlMessageFormat = require('intl-messageformat').default;
  21. const { timeAgoString } = require('../mastodon/components/relative_timestamp');
  22. const { delegate } = require('rails-ujs');
  23. const emojify = require('../mastodon/features/emoji/emoji').default;
  24. const { getLocale } = require('../mastodon/locales');
  25. const { messages } = getLocale();
  26. const React = require('react');
  27. const ReactDOM = require('react-dom');
  28. const Rellax = require('rellax');
  29. const createHistory = require('history').createBrowserHistory;
  30. const scrollToDetailedStatus = () => {
  31. const history = createHistory();
  32. const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status');
  33. const location = history.location;
  34. if (detailedStatuses.length === 1 && (!location.state || !location.state.scrolledToDetailedStatus)) {
  35. detailedStatuses[0].scrollIntoView();
  36. history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true });
  37. }
  38. };
  39. ready(() => {
  40. const locale = document.documentElement.lang;
  41. const dateTimeFormat = new Intl.DateTimeFormat(locale, {
  42. year: 'numeric',
  43. month: 'long',
  44. day: 'numeric',
  45. hour: 'numeric',
  46. minute: 'numeric',
  47. });
  48. [].forEach.call(document.querySelectorAll('.emojify'), (content) => {
  49. content.innerHTML = emojify(content.innerHTML);
  50. });
  51. [].forEach.call(document.querySelectorAll('time.formatted'), (content) => {
  52. const datetime = new Date(content.getAttribute('datetime'));
  53. const formattedDate = dateTimeFormat.format(datetime);
  54. content.title = formattedDate;
  55. content.textContent = formattedDate;
  56. });
  57. [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => {
  58. const datetime = new Date(content.getAttribute('datetime'));
  59. const now = new Date();
  60. content.title = dateTimeFormat.format(datetime);
  61. content.textContent = timeAgoString({
  62. formatMessage: ({ id, defaultMessage }, values) => (new IntlMessageFormat(messages[id] || defaultMessage, locale)).format(values),
  63. formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date),
  64. }, datetime, now, now.getFullYear());
  65. });
  66. const reactComponents = document.querySelectorAll('[data-component]');
  67. if (reactComponents.length > 0) {
  68. import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container')
  69. .then(({ default: MediaContainer }) => {
  70. [].forEach.call(reactComponents, (component) => {
  71. [].forEach.call(component.children, (child) => {
  72. component.removeChild(child);
  73. });
  74. });
  75. const content = document.createElement('div');
  76. ReactDOM.render(<MediaContainer locale={locale} components={reactComponents} />, content);
  77. document.body.appendChild(content);
  78. scrollToDetailedStatus();
  79. })
  80. .catch(error => {
  81. console.error(error);
  82. scrollToDetailedStatus();
  83. });
  84. } else {
  85. scrollToDetailedStatus();
  86. }
  87. const parallaxComponents = document.querySelectorAll('.parallax');
  88. if (parallaxComponents.length > 0 ) {
  89. new Rellax('.parallax', { speed: -1 });
  90. }
  91. if (document.body.classList.contains('with-modals')) {
  92. const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
  93. const scrollbarWidthStyle = document.createElement('style');
  94. scrollbarWidthStyle.id = 'scrollbar-width';
  95. document.head.appendChild(scrollbarWidthStyle);
  96. scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
  97. }
  98. });
  99. delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
  100. if (button !== 0) {
  101. return true;
  102. }
  103. window.location.href = target.href;
  104. return false;
  105. });
  106. delegate(document, '.status__content__spoiler-link', 'click', function() {
  107. const contentEl = this.parentNode.parentNode.querySelector('.e-content');
  108. if (contentEl.style.display === 'block') {
  109. contentEl.style.display = 'none';
  110. this.parentNode.style.marginBottom = 0;
  111. } else {
  112. contentEl.style.display = 'block';
  113. this.parentNode.style.marginBottom = null;
  114. }
  115. return false;
  116. });
  117. delegate(document, '.modal-button', 'click', e => {
  118. e.preventDefault();
  119. let href;
  120. if (e.target.nodeName !== 'A') {
  121. href = e.target.parentNode.href;
  122. } else {
  123. href = e.target.href;
  124. }
  125. window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
  126. });
  127. delegate(document, '#account_display_name', 'input', ({ target }) => {
  128. const name = document.querySelector('.card .display-name strong');
  129. if (name) {
  130. if (target.value) {
  131. name.innerHTML = emojify(escapeTextContentForBrowser(target.value));
  132. } else {
  133. name.textContent = document.querySelector('#default_account_display_name').textContent;
  134. }
  135. }
  136. });
  137. delegate(document, '#account_avatar', 'change', ({ target }) => {
  138. const avatar = document.querySelector('.card .avatar img');
  139. const [file] = target.files || [];
  140. const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
  141. avatar.src = url;
  142. });
  143. const getProfileAvatarAnimationHandler = (swapTo) => {
  144. //animate avatar gifs on the profile page when moused over
  145. return ({ target }) => {
  146. const swapSrc = target.getAttribute(swapTo);
  147. //only change the img source if autoplay is off and the image src is actually different
  148. if(target.getAttribute('data-autoplay') === 'false' && target.src !== swapSrc) {
  149. target.src = swapSrc;
  150. }
  151. };
  152. };
  153. delegate(document, 'img#profile_page_avatar', 'mouseover', getProfileAvatarAnimationHandler('data-original'));
  154. delegate(document, 'img#profile_page_avatar', 'mouseout', getProfileAvatarAnimationHandler('data-static'));
  155. delegate(document, '#account_header', 'change', ({ target }) => {
  156. const header = document.querySelector('.card .card__img img');
  157. const [file] = target.files || [];
  158. const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
  159. header.src = url;
  160. });
  161. delegate(document, '#account_locked', 'change', ({ target }) => {
  162. const lock = document.querySelector('.card .display-name i');
  163. if (target.checked) {
  164. lock.style.display = 'inline';
  165. } else {
  166. lock.style.display = 'none';
  167. }
  168. });
  169. delegate(document, '.input-copy input', 'click', ({ target }) => {
  170. target.focus();
  171. target.select();
  172. target.setSelectionRange(0, target.value.length);
  173. });
  174. delegate(document, '.input-copy button', 'click', ({ target }) => {
  175. const input = target.parentNode.querySelector('.input-copy__wrapper input');
  176. const oldReadOnly = input.readonly;
  177. input.readonly = false;
  178. input.focus();
  179. input.select();
  180. input.setSelectionRange(0, input.value.length);
  181. try {
  182. if (document.execCommand('copy')) {
  183. input.blur();
  184. target.parentNode.classList.add('copied');
  185. setTimeout(() => {
  186. target.parentNode.classList.remove('copied');
  187. }, 700);
  188. }
  189. } catch (err) {
  190. console.error(err);
  191. }
  192. input.readonly = oldReadOnly;
  193. });
  194. }
  195. loadPolyfills().then(main).catch(error => {
  196. console.error(error);
  197. });