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.

NotificationsSidebar.tsx 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as classNames from 'classnames';
  21. import * as differenceInSeconds from 'date-fns/difference_in_seconds';
  22. import * as React from 'react';
  23. import { ClearButton } from 'sonar-ui-common/components/controls/buttons';
  24. import Modal from 'sonar-ui-common/components/controls/Modal';
  25. import DateFormatter from 'sonar-ui-common/components/intl/DateFormatter';
  26. import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
  27. import { translate } from 'sonar-ui-common/helpers/l10n';
  28. import { PrismicFeatureNews } from '../../../api/news';
  29. export interface Props {
  30. fetchMoreFeatureNews: () => void;
  31. loading: boolean;
  32. loadingMore: boolean;
  33. news: PrismicFeatureNews[];
  34. onClose: () => void;
  35. notificationsLastReadDate?: Date;
  36. paging?: T.Paging;
  37. }
  38. export default function NotificationsSidebar(props: Props) {
  39. const { loading, loadingMore, news, notificationsLastReadDate, paging } = props;
  40. const header = translate('embed_docs.whats_new');
  41. return (
  42. <Modal contentLabel={header} onRequestClose={props.onClose}>
  43. <div className="notifications-sidebar">
  44. <div className="notifications-sidebar-top">
  45. <h3>{header}</h3>
  46. <ClearButton
  47. className="button-tiny"
  48. iconProps={{ size: 12, thin: true }}
  49. onClick={props.onClose}
  50. />
  51. </div>
  52. <div className="notifications-sidebar-content">
  53. {loading ? (
  54. <div className="text-center">
  55. <DeferredSpinner className="big-spacer-top" timeout={200} />
  56. </div>
  57. ) : (
  58. news.map((slice, index) => (
  59. <Notification
  60. key={slice.publicationDate}
  61. notification={slice}
  62. unread={isUnread(index, slice.publicationDate, notificationsLastReadDate)}
  63. />
  64. ))
  65. )}
  66. </div>
  67. {!loading && paging && paging.total > news.length && (
  68. <div className="notifications-sidebar-footer">
  69. <div className="spacer-top note text-center">
  70. <a className="spacer-left" href="#" onClick={props.fetchMoreFeatureNews}>
  71. {translate('show_more')}
  72. </a>
  73. {loadingMore && (
  74. <DeferredSpinner className="text-bottom spacer-left position-absolute" />
  75. )}
  76. </div>
  77. </div>
  78. )}
  79. </div>
  80. </Modal>
  81. );
  82. }
  83. export function isUnread(index: number, notificationDate: string, lastReadDate?: Date) {
  84. return !lastReadDate ? index < 1 : differenceInSeconds(notificationDate, lastReadDate) > 0;
  85. }
  86. interface NotificationProps {
  87. notification: PrismicFeatureNews;
  88. unread: boolean;
  89. }
  90. export function Notification({ notification, unread }: NotificationProps) {
  91. return (
  92. <div className={classNames('notifications-sidebar-slice', { unread })}>
  93. <h4>
  94. <DateFormatter date={notification.publicationDate} long={false} />
  95. </h4>
  96. {notification.features.map((feature, index) => (
  97. <Feature feature={feature} key={index} />
  98. ))}
  99. </div>
  100. );
  101. }
  102. interface FeatureProps {
  103. feature: PrismicFeatureNews['features'][0];
  104. }
  105. export function Feature({ feature }: FeatureProps) {
  106. return (
  107. <div className="feature">
  108. <ul className="categories spacer-bottom">
  109. {feature.categories.map(category => (
  110. <li key={category.name} style={{ backgroundColor: category.color }}>
  111. {category.name}
  112. </li>
  113. ))}
  114. </ul>
  115. <span>{feature.description}</span>
  116. {feature.readMore && (
  117. <a
  118. className="learn-more"
  119. href={feature.readMore}
  120. rel="noopener noreferrer nofollow"
  121. target="_blank">
  122. {translate('learn_more')}
  123. </a>
  124. )}
  125. </div>
  126. );
  127. }