您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

functions.any.js 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // @flow
  2. import md5 from 'js-md5';
  3. import { setCalendarEvents } from './actions';
  4. import { APP_LINK_SCHEME, parseURIString } from '../base/util';
  5. import { MAX_LIST_LENGTH } from './constants';
  6. const ALLDAY_EVENT_LENGTH = 23 * 60 * 60 * 1000;
  7. /**
  8. * Returns true of the calendar entry is to be displayed in the app, false
  9. * otherwise.
  10. *
  11. * @param {Object} entry - The calendar entry.
  12. * @returns {boolean}
  13. */
  14. function _isDisplayableCalendarEntry(entry) {
  15. // Entries are displayable if:
  16. // - Ends in the future (future or ongoing events)
  17. // - Is not an all day event and there is only one attendee (these events
  18. // are usually placeholder events that don't need to be shown.)
  19. return entry.endDate > Date.now()
  20. && !((entry.allDay
  21. || entry.endDate - entry.startDate > ALLDAY_EVENT_LENGTH)
  22. && (!entry.attendees || entry.attendees.length < 2));
  23. }
  24. /**
  25. * Updates the calendar entries in redux when new list is received. The feature
  26. * calendar-sync doesn't display all calendar events, it displays unique
  27. * title, URL, and start time tuples, and it doesn't display subsequent
  28. * occurrences of recurring events, and the repetitions of events coming from
  29. * multiple calendars.
  30. *
  31. * XXX The function's {@code this} is the redux store.
  32. *
  33. * @param {Array<CalendarEntry>} events - The new event list.
  34. * @private
  35. * @returns {void}
  36. */
  37. export function _updateCalendarEntries(events: Array<Object>) {
  38. if (!events || !events.length) {
  39. return;
  40. }
  41. // eslint-disable-next-line no-invalid-this
  42. const { dispatch, getState } = this;
  43. const knownDomains = getState()['features/base/known-domains'];
  44. const entryMap = new Map();
  45. for (const event of events) {
  46. const entry = _parseCalendarEntry(event, knownDomains);
  47. if (entry && _isDisplayableCalendarEntry(entry)) {
  48. // As was stated above, we don't display subsequent occurrences of
  49. // recurring events, and the repetitions of events coming from
  50. // multiple calendars.
  51. const key = md5.hex(JSON.stringify([
  52. // Obviously, we want to display different conference/meetings
  53. // URLs. URLs are the very reason why we implemented the feature
  54. // calendar-sync in the first place.
  55. entry.url,
  56. // We probably want to display one and the same URL to people if
  57. // they have it under different titles in their Calendar.
  58. // Because maybe they remember the title of the meeting, not the
  59. // URL so they expect to see the title without realizing that
  60. // they have the same URL already under a different title.
  61. entry.title,
  62. // XXX Eventually, given that the URL and the title are the
  63. // same, what sets one event apart from another is the start
  64. // time of the day (note the use of toTimeString() bellow)! The
  65. // day itself is not important because we don't want multiple
  66. // occurrences of a recurring event or repetitions of an even
  67. // from multiple calendars.
  68. new Date(entry.startDate).toTimeString()
  69. ]));
  70. const existingEntry = entryMap.get(key);
  71. // We want only the earliest occurrence (which hasn't ended in the
  72. // past, that is) of a recurring event.
  73. if (!existingEntry || existingEntry.startDate > entry.startDate) {
  74. entryMap.set(key, entry);
  75. }
  76. }
  77. }
  78. dispatch(
  79. setCalendarEvents(
  80. Array.from(entryMap.values())
  81. .sort((a, b) => a.startDate - b.startDate)
  82. .slice(0, MAX_LIST_LENGTH)));
  83. }
  84. /**
  85. * Checks a string against a positive pattern and a negative pattern. Returns
  86. * the string if it matches the positive pattern and doesn't provide any match
  87. * against the negative pattern. Null otherwise.
  88. *
  89. * @param {string} str - The string to check.
  90. * @param {string} positivePattern - The positive pattern.
  91. * @param {string} negativePattern - The negative pattern.
  92. * @returns {string}
  93. */
  94. function _checkPattern(str, positivePattern, negativePattern) {
  95. const positiveRegExp = new RegExp(positivePattern, 'gi');
  96. let positiveMatch = positiveRegExp.exec(str);
  97. while (positiveMatch !== null) {
  98. const url = positiveMatch[0];
  99. if (!new RegExp(negativePattern, 'gi').exec(url)) {
  100. return url;
  101. }
  102. positiveMatch = positiveRegExp.exec(str);
  103. }
  104. }
  105. /**
  106. * Updates the calendar entries in Redux when new list is received.
  107. *
  108. * @param {Object} event - An event returned from the native calendar.
  109. * @param {Array<string>} knownDomains - The known domain list.
  110. * @private
  111. * @returns {CalendarEntry}
  112. */
  113. function _parseCalendarEntry(event, knownDomains) {
  114. if (event) {
  115. const url = _getURLFromEvent(event, knownDomains);
  116. const startDate = Date.parse(event.startDate);
  117. const endDate = Date.parse(event.endDate);
  118. // we want to hide all events that
  119. // - has no start or end date
  120. // - for web, if there is no url and we cannot edit the event (has
  121. // no calendarId)
  122. if (isNaN(startDate)
  123. || isNaN(endDate)
  124. || (navigator.product !== 'ReactNative'
  125. && !url
  126. && !event.calendarId)) {
  127. // Ignore the event.
  128. } else {
  129. return {
  130. allDay: event.allDay,
  131. attendees: event.attendees,
  132. calendarId: event.calendarId,
  133. endDate,
  134. id: event.id,
  135. startDate,
  136. title: event.title,
  137. url
  138. };
  139. }
  140. }
  141. return null;
  142. }
  143. /**
  144. * Retrieves a Jitsi Meet URL from an event if present.
  145. *
  146. * @param {Object} event - The event to parse.
  147. * @param {Array<string>} knownDomains - The known domain names.
  148. * @private
  149. * @returns {string}
  150. */
  151. function _getURLFromEvent(event, knownDomains) {
  152. const linkTerminatorPattern = '[^\\s<>$]';
  153. const urlRegExp
  154. = `http(s)?://(${knownDomains.join('|')})/${linkTerminatorPattern}+`;
  155. const schemeRegExp = `${APP_LINK_SCHEME}${linkTerminatorPattern}+`;
  156. const excludePattern = '/static/';
  157. const fieldsToSearch = [
  158. event.title,
  159. event.url,
  160. event.location,
  161. event.notes,
  162. event.description
  163. ];
  164. for (const field of fieldsToSearch) {
  165. if (typeof field === 'string') {
  166. const match
  167. = _checkPattern(field, urlRegExp, excludePattern)
  168. || _checkPattern(field, schemeRegExp, excludePattern);
  169. if (match) {
  170. const url = parseURIString(match);
  171. if (url) {
  172. return url.toString();
  173. }
  174. }
  175. }
  176. }
  177. return null;
  178. }