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.

SubCategoryDefinitionsList.tsx 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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 { groupBy, isEqual, sortBy } from 'lodash';
  21. import * as React from 'react';
  22. import { Location, withRouter } from '../../../components/hoc/withRouter';
  23. import { sanitizeStringRestricted } from '../../../helpers/sanitize';
  24. import { scrollToElement } from '../../../helpers/scrolling';
  25. import { SettingWithCategory } from '../../../types/settings';
  26. import { getSubCategoryDescription, getSubCategoryName } from '../utils';
  27. import DefinitionsList from './DefinitionsList';
  28. import EmailForm from './EmailForm';
  29. export interface SubCategoryDefinitionsListProps {
  30. category: string;
  31. component?: T.Component;
  32. fetchValues: Function;
  33. location: Location;
  34. settings: Array<SettingWithCategory>;
  35. subCategory?: string;
  36. }
  37. const SCROLL_OFFSET_TOP = 200;
  38. const SCROLL_OFFSET_BOTTOM = 500;
  39. export class SubCategoryDefinitionsList extends React.PureComponent<
  40. SubCategoryDefinitionsListProps
  41. > {
  42. componentDidMount() {
  43. this.fetchValues();
  44. }
  45. componentDidUpdate(prevProps: SubCategoryDefinitionsListProps) {
  46. const prevKeys = prevProps.settings.map(setting => setting.definition.key);
  47. const keys = this.props.settings.map(setting => setting.definition.key);
  48. if (prevProps.component !== this.props.component || !isEqual(prevKeys, keys)) {
  49. this.fetchValues();
  50. }
  51. const { hash } = this.props.location;
  52. if (hash && prevProps.location.hash !== hash) {
  53. const query = `[data-key=${hash.substr(1).replace(/[.#/]/g, '\\$&')}]`;
  54. const element = document.querySelector<HTMLHeadingElement | HTMLLIElement>(query);
  55. this.scrollToSubCategoryOrDefinition(element);
  56. }
  57. }
  58. scrollToSubCategoryOrDefinition = (element: HTMLHeadingElement | HTMLLIElement | null) => {
  59. if (element) {
  60. const { hash } = this.props.location;
  61. if (hash && hash.substr(1) === element.getAttribute('data-key')) {
  62. scrollToElement(element, {
  63. topOffset: SCROLL_OFFSET_TOP,
  64. bottomOffset: SCROLL_OFFSET_BOTTOM,
  65. smooth: true
  66. });
  67. }
  68. }
  69. };
  70. fetchValues() {
  71. const keys = this.props.settings.map(setting => setting.definition.key);
  72. return this.props.fetchValues(keys, this.props.component && this.props.component.key);
  73. }
  74. renderEmailForm = (subCategoryKey: string) => {
  75. const isEmailSettings = this.props.category === 'general' && subCategoryKey === 'email';
  76. if (!isEmailSettings) {
  77. return null;
  78. }
  79. return <EmailForm />;
  80. };
  81. render() {
  82. const bySubCategory = groupBy(this.props.settings, setting => setting.definition.subCategory);
  83. const subCategories = Object.keys(bySubCategory).map(key => ({
  84. key,
  85. name: getSubCategoryName(bySubCategory[key][0].definition.category, key),
  86. description: getSubCategoryDescription(bySubCategory[key][0].definition.category, key)
  87. }));
  88. const sortedSubCategories = sortBy(subCategories, subCategory =>
  89. subCategory.name.toLowerCase()
  90. );
  91. const filteredSubCategories = this.props.subCategory
  92. ? sortedSubCategories.filter(c => c.key === this.props.subCategory)
  93. : sortedSubCategories;
  94. return (
  95. <ul className="settings-sub-categories-list">
  96. {filteredSubCategories.map(subCategory => (
  97. <li key={subCategory.key}>
  98. <h2
  99. className="settings-sub-category-name"
  100. data-key={subCategory.key}
  101. ref={this.scrollToSubCategoryOrDefinition}>
  102. {subCategory.name}
  103. </h2>
  104. {subCategory.description != null && (
  105. <div
  106. className="settings-sub-category-description markdown"
  107. // eslint-disable-next-line react/no-danger
  108. dangerouslySetInnerHTML={{
  109. __html: sanitizeStringRestricted(subCategory.description)
  110. }}
  111. />
  112. )}
  113. <DefinitionsList
  114. component={this.props.component}
  115. scrollToDefinition={this.scrollToSubCategoryOrDefinition}
  116. settings={bySubCategory[subCategory.key]}
  117. />
  118. {this.renderEmailForm(subCategory.key)}
  119. </li>
  120. ))}
  121. </ul>
  122. );
  123. }
  124. }
  125. export default withRouter(SubCategoryDefinitionsList);