]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12375 Fix floating TOC in documentation
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Thu, 2 Jan 2020 09:27:22 +0000 (10:27 +0100)
committerSonarTech <sonartech@sonarsource.com>
Wed, 8 Jan 2020 19:46:10 +0000 (20:46 +0100)
server/sonar-docs/src/components/HeadingsLink.tsx
server/sonar-docs/src/components/__tests__/HeadingsLink-test.tsx [new file with mode: 0644]
server/sonar-docs/src/components/__tests__/__snapshots__/HeadingsLink-test.tsx.snap [new file with mode: 0644]
server/sonar-docs/src/layouts/layout.css

index fb595c1217683ca1a95c49e020b1dec500d2c2a6..e9d620097debf9f795ce5f8d73f2beb3e7dcaa1d 100644 (file)
@@ -21,7 +21,6 @@ import * as React from 'react';
 import { MarkdownHeading } from '../@types/graphql-types';
 import HeadingAnchor from './HeadingAnchor';
 
-const MINIMUM_TOP_MARGIN = 80;
 const HEADER_SCROLL_MARGIN = 100;
 
 interface Props {
@@ -31,7 +30,6 @@ interface Props {
 interface State {
   activeIndex: number;
   headers: MarkdownHeading[];
-  marginTop: number;
 }
 
 export default class HeadingsLink extends React.PureComponent<Props, State> {
@@ -43,18 +41,17 @@ export default class HeadingsLink extends React.PureComponent<Props, State> {
       activeIndex: -1,
       headers: props.headers.filter(
         h => h.depth === 2 && h.value && h.value.toLowerCase() !== 'table of contents'
-      ),
-      marginTop: MINIMUM_TOP_MARGIN
+      )
     };
   }
 
   componentDidMount() {
     document.addEventListener('scroll', this.scrollHandler, true);
-    this.scrollHandler();
   }
 
   componentWillReceiveProps(nextProps: Props) {
     this.setState({
+      activeIndex: -1,
       headers: nextProps.headers.filter(
         h => h.depth === 2 && h.value && h.value.toLowerCase() !== 'table of contents'
       )
@@ -65,6 +62,16 @@ export default class HeadingsLink extends React.PureComponent<Props, State> {
     document.removeEventListener('scroll', this.scrollHandler, true);
   }
 
+  scrollHandler = () => {
+    if (this.skipScrollingHandler) {
+      this.skipScrollingHandler = false;
+      return;
+    }
+
+    const scrollTop = window.pageYOffset || document.body.scrollTop;
+    this.highlightHeading(scrollTop);
+  };
+
   highlightHeading = (scrollTop: number) => {
     let headingIndex = 0;
     for (let i = 0; i < this.state.headers.length; i++) {
@@ -74,11 +81,7 @@ export default class HeadingsLink extends React.PureComponent<Props, State> {
       }
       headingIndex = i;
     }
-    const scrollLimit = document.body.scrollHeight - document.body.clientHeight;
-    this.setState({
-      activeIndex: headingIndex,
-      marginTop: Math.max(MINIMUM_TOP_MARGIN, Math.min(scrollTop, scrollLimit))
-    });
+    this.setState({ activeIndex: headingIndex });
     this.markH2(headingIndex + 1, false);
   };
 
@@ -99,16 +102,6 @@ export default class HeadingsLink extends React.PureComponent<Props, State> {
     }
   };
 
-  scrollHandler = () => {
-    if (this.skipScrollingHandler) {
-      this.skipScrollingHandler = false;
-      return;
-    }
-
-    const scrollTop = window.pageYOffset || document.body.scrollTop;
-    this.highlightHeading(scrollTop);
-  };
-
   clickHandler = (index: number) => {
     this.markH2(index, true);
   };
@@ -120,21 +113,23 @@ export default class HeadingsLink extends React.PureComponent<Props, State> {
     }
 
     return (
-      <div className="headings-container" style={{ marginTop: this.state.marginTop + 'px' }}>
-        <span>On this page</span>
-        <ul>
-          {headers.map((header, index) => {
-            return (
-              <HeadingAnchor
-                active={this.state.activeIndex === index}
-                clickHandler={this.clickHandler}
-                index={index + 1}
-                key={index}>
-                {header.value}
-              </HeadingAnchor>
-            );
-          })}
-        </ul>
+      <div className="headings-container">
+        <div className="headings-container-fixed">
+          <span>On this page</span>
+          <ul>
+            {headers.map((header, index) => {
+              return (
+                <HeadingAnchor
+                  active={this.state.activeIndex === index}
+                  clickHandler={this.clickHandler}
+                  index={index + 1}
+                  key={index}>
+                  {header.value}
+                </HeadingAnchor>
+              );
+            })}
+          </ul>
+        </div>
       </div>
     );
   }
diff --git a/server/sonar-docs/src/components/__tests__/HeadingsLink-test.tsx b/server/sonar-docs/src/components/__tests__/HeadingsLink-test.tsx
new file mode 100644 (file)
index 0000000..5340a53
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import HeadingsLink from '../HeadingsLink';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<HeadingsLink['props']> = {}) {
+  return shallow(
+    <HeadingsLink
+      headers={[
+        { value: 'Table of Contents', depth: 2 },
+        { value: 'Foo', depth: 2 },
+        { value: 'Br', depth: 2 }
+      ]}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-docs/src/components/__tests__/__snapshots__/HeadingsLink-test.tsx.snap b/server/sonar-docs/src/components/__tests__/__snapshots__/HeadingsLink-test.tsx.snap
new file mode 100644 (file)
index 0000000..3449ded
--- /dev/null
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+  className="headings-container"
+>
+  <div
+    className="headings-container-fixed"
+  >
+    <span>
+      On this page
+    </span>
+    <ul>
+      <HeadingAnchor
+        active={false}
+        clickHandler={[Function]}
+        index={1}
+        key="0"
+      >
+        Foo
+      </HeadingAnchor>
+      <HeadingAnchor
+        active={false}
+        clickHandler={[Function]}
+        index={2}
+        key="1"
+      >
+        Br
+      </HeadingAnchor>
+    </ul>
+  </div>
+</div>
+`;
index adf69979c52fd2568af3f5dd2a569384eae22160..7e5f94ea1455b052f170c6d56ef3e46ad134b032 100644 (file)
@@ -328,10 +328,18 @@ a.search-result .note {
 }
 
 .page-container .headings-container {
-  float: right;
   width: 200px;
-  border-left: 1px solid #cfd3d7;
+  float: right;
+  margin-top: 80px;
+}
+
+.page-container .headings-container-fixed {
+  position: fixed;
+  width: inherit;
   padding-left: 26px;
+  border-left: 1px solid #cfd3d7;
+  z-index: 100;
+  background: white;
 }
 
 .page-container .headings-container span {