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.

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