aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-docs/src
diff options
context:
space:
mode:
authorPascal Mugnier <pascal.mugnier@sonarsource.com>2018-09-10 11:32:33 +0200
committersonartech <sonartech@sonarsource.com>2018-09-19 10:50:57 +0200
commit55822b5c264747a28161902119482ef7f000ab66 (patch)
treea5a984280429502ffe41219b21502bece97727b1 /server/sonar-docs/src
parent80418249fe5b1f8cf3ee10f4f80d538351fd106c (diff)
downloadsonarqube-55822b5c264747a28161902119482ef7f000ab66.tar.gz
sonarqube-55822b5c264747a28161902119482ef7f000ab66.zip
MMF-1377 Finalize the static documentation site (#673)
Diffstat (limited to 'server/sonar-docs/src')
-rw-r--r--server/sonar-docs/src/images/SonarQubeIcon.svg1
-rw-r--r--server/sonar-docs/src/images/alerts/danger.svg1
-rw-r--r--server/sonar-docs/src/images/alerts/info.svg1
-rw-r--r--server/sonar-docs/src/images/alerts/wrong.svg1
-rw-r--r--server/sonar-docs/src/images/community-icon.svg2
-rw-r--r--server/sonar-docs/src/images/sq-icon-small.svg1
-rw-r--r--server/sonar-docs/src/images/tw-icon-small.svg1
-rw-r--r--server/sonar-docs/src/layouts/components/CategoryLink.js59
-rw-r--r--server/sonar-docs/src/layouts/components/Footer.js47
-rw-r--r--server/sonar-docs/src/layouts/components/HeaderList.js41
-rw-r--r--server/sonar-docs/src/layouts/components/HeaderListProvider.js47
-rw-r--r--server/sonar-docs/src/layouts/components/HeadingsLink.js93
-rw-r--r--server/sonar-docs/src/layouts/components/OutsideClickHandler.js54
-rw-r--r--server/sonar-docs/src/layouts/components/Search.js98
-rw-r--r--server/sonar-docs/src/layouts/components/SearchEntryResult.js74
-rw-r--r--server/sonar-docs/src/layouts/components/Sidebar.js128
-rw-r--r--server/sonar-docs/src/layouts/components/SubpageLink.js35
-rw-r--r--server/sonar-docs/src/layouts/components/VersionSelect.js68
-rw-r--r--server/sonar-docs/src/layouts/components/icons/AlertWarnIcon.js32
-rw-r--r--server/sonar-docs/src/layouts/components/icons/ChevronDownIcon.js32
-rw-r--r--server/sonar-docs/src/layouts/components/icons/ChevronUpIcon.js32
-rw-r--r--server/sonar-docs/src/layouts/components/icons/DownloadIcon.js36
-rw-r--r--server/sonar-docs/src/layouts/components/icons/Icon.js52
-rw-r--r--server/sonar-docs/src/layouts/index.js135
-rw-r--r--server/sonar-docs/src/layouts/utils.js103
-rw-r--r--server/sonar-docs/src/pages/analysis/index.md4
-rw-r--r--server/sonar-docs/src/pages/branches/short-lived-branches.md2
-rw-r--r--server/sonar-docs/src/pages/user-account.md19
-rw-r--r--server/sonar-docs/src/templates/page.css566
-rw-r--r--server/sonar-docs/src/templates/page.js104
-rw-r--r--server/sonar-docs/src/utils/typography.js8
31 files changed, 1782 insertions, 95 deletions
diff --git a/server/sonar-docs/src/images/SonarQubeIcon.svg b/server/sonar-docs/src/images/SonarQubeIcon.svg
new file mode 100644
index 00000000000..b39bd4947da
--- /dev/null
+++ b/server/sonar-docs/src/images/SonarQubeIcon.svg
@@ -0,0 +1 @@
+<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 540.33 156.33"><defs><style>.cls-1{fill:#1b171b}.cls-3{fill:#4e9bcd}</style></defs><path class="cls-1" d="M11.89 101.92a29.92 29.92 0 0 0 13.23 3.74c4.65 0 6.57-1.62 6.57-4.14s-1.51-3.74-7.27-5.66c-10.21-3.44-14.15-9-14-14.85 0-9.2 7.89-16.17 20.11-16.17a33.07 33.07 0 0 1 13.95 2.83l-2.78 10.6A24.24 24.24 0 0 0 31 75.44c-3.74 0-5.87 1.51-5.87 4 0 2.33 1.93 3.54 8 5.66 9.4 3.23 13.34 8 13.44 15.26 0 9.19-7.27 16-21.42 16-6.47 0-12.22-1.42-16-3.44zM100.63 90.09c0 18.09-12.83 26.38-26.08 26.38C60.11 116.48 49 107 49 91s10.5-26.17 26.37-26.17c15.16 0 25.26 10.41 25.26 25.26zm-35.78.51c0 8.49 3.54 14.85 10.11 14.85 6 0 9.8-6 9.8-14.85 0-7.38-2.83-14.87-9.8-14.87-7.37.01-10.11 7.59-10.11 14.87zM106.11 81.71c0-6.16-.2-11.42-.41-15.76H119l.7 6.76h.31a18.08 18.08 0 0 1 15.25-7.88c10.11 0 17.69 6.66 17.69 21.22v29.31h-15.31V88c0-6.37-2.22-10.71-7.78-10.71a8.18 8.18 0 0 0-7.78 5.71 10.41 10.41 0 0 0-.61 3.84v28.51h-15.36zM189.39 115.36l-.91-5h-.3c-3.23 3.95-8.3 6.07-14.15 6.07-10 0-16-7.29-16-15.16 0-12.83 11.52-19 29-18.91v-.7c0-2.63-1.42-6.37-9-6.37a27.8 27.8 0 0 0-13.64 3.73l-2.84-9.9c3.44-1.93 10.21-4.35 19.2-4.35 16.48 0 21.73 9.7 21.73 21.32v17.18a75.92 75.92 0 0 0 .71 12zM187.58 92c-8.08-.1-14.35 1.83-14.35 7.78 0 3.95 2.63 5.87 6.07 5.87a8.39 8.39 0 0 0 8-5.66 10.87 10.87 0 0 0 .31-2.63zM210.63 82.21c0-7.27-.2-12-.41-16.26h13.24L224 75h.4c2.53-7.17 8.59-10.2 13.34-10.2a16.56 16.56 0 0 1 3.26.2v14.48a21.82 21.82 0 0 0-4.14-.41c-5.66 0-9.5 3-10.52 7.78a18.94 18.94 0 0 0-.3 3.44v25.07h-15.41zM342.35 102c0 5 .1 9.5.41 13.34h-7.89l-.51-8h-.19a18.43 18.43 0 0 1-16.17 9.1c-7.68 0-16.89-4.24-16.89-21.42V66.44H310v27.09c0 9.29 2.83 15.57 10.92 15.57a12.88 12.88 0 0 0 11.72-8.1 13.15 13.15 0 0 0 .81-4.55v-30h8.9zM352.67 115.36c.2-3.34.4-8.3.4-12.64V43.6h8.79v30.73h.2c3.13-5.46 8.79-9 16.68-9 12.12 0 20.71 10.11 20.61 25 0 17.49-11 26.18-21.92 26.18-7.08 0-12.73-2.73-16.37-9.2h-.31l-.4 8.09zm9.19-19.61a16.48 16.48 0 0 0 .41 3.23 13.71 13.71 0 0 0 13.33 10.41c9.31 0 14.85-7.58 14.85-18.79 0-9.8-5-18.19-14.55-18.19a14.17 14.17 0 0 0-13.54 10.91 17.47 17.47 0 0 0-.51 3.64zM411.5 92.52c.19 12 7.88 17 16.77 17a32.24 32.24 0 0 0 13.54-2.52l1.52 6.37c-3.13 1.41-8.49 3-16.27 3-15.06 0-24.06-9.9-24.06-24.65s8.69-26.38 22.94-26.38c16 0 20.21 14 20.21 23a33.67 33.67 0 0 1-.3 4.14zm26.07-6.37c.1-5.66-2.31-14.46-12.32-14.46-9 0-12.94 8.3-13.65 14.46z"/><path d="M290.55 75.25a26.41 26.41 0 1 0-11.31 39.07l10.22 16.6 8.11-5.51-10.22-16.6a26.42 26.42 0 0 0 3.2-33.56M279.1 105.4a18.5 18.5 0 1 1 4.9-25.7 18.52 18.52 0 0 1-4.9 25.7" fill-rule="evenodd" fill="#1b171b"/><path class="cls-3" d="M506.94 115.57h-6.27c0-50.44-41.62-91.48-92.78-91.48v-6.26c54.62 0 99.05 43.84 99.05 97.74z"/><path class="cls-3" d="M511.27 81.93c-7.52-31.65-33.16-58.06-65.27-67.29l1.44-5c33.93 9.74 61 37.65 68.95 71.1zM516.09 52.23a96 96 0 0 0-37.17-41.49l2.17-3.57a100.24 100.24 0 0 1 38.8 43.31z"/></svg> \ No newline at end of file
diff --git a/server/sonar-docs/src/images/alerts/danger.svg b/server/sonar-docs/src/images/alerts/danger.svg
new file mode 100644
index 00000000000..b7967ce5e8a
--- /dev/null
+++ b/server/sonar-docs/src/images/alerts/danger.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.36 20.088"><defs><style>.cls-1{fill:#cda251}</style></defs><g id="danger" transform="translate(0 -35.857)"><g id="Groupe_5722" data-name="Groupe 5722" transform="translate(0 35.857)"><g id="Groupe_5721" data-name="Groupe 5721"><path id="Tracé_9564" data-name="Tracé 9564" class="cls-1" d="M23.059 52.763l-9.582-15.891a2.1 2.1 0 0 0-3.594 0L.3 52.763a2.1 2.1 0 0 0 1.8 3.182h19.162a2.1 2.1 0 0 0 1.8-3.182zm-1.294 1.368a.579.579 0 0 1-.5.294H2.1a.578.578 0 0 1-.495-.876l9.582-15.891a.578.578 0 0 1 .989 0l9.582 15.891a.578.578 0 0 1 .007.582z" transform="translate(0 -35.857)"/></g></g><g id="Groupe_5724" data-name="Groupe 5724" transform="translate(10.657 42.114)"><g id="Groupe_5723" data-name="Groupe 5723"><path id="Tracé_9565" data-name="Tracé 9565" class="cls-1" d="M234.608 173.005c-.579 0-1.03.31-1.03.861 0 1.679.2 4.092.2 5.771 0 .437.381.621.833.621.339 0 .818-.183.818-.621 0-1.679.2-4.092.2-5.771a.907.907 0 0 0-1.021-.861z" transform="translate(-233.578 -173.005)"/></g></g><g id="Groupe_5726" data-name="Groupe 5726" transform="translate(10.614 50.341)"><g id="Groupe_5725" data-name="Groupe 5725"><path id="Tracé_9566" data-name="Tracé 9566" class="cls-1" d="M233.738 353.306a1.087 1.087 0 1 0 0 2.173 1.087 1.087 0 0 0 0-2.173z" transform="translate(-232.651 -353.306)"/></g></g></g></svg> \ No newline at end of file
diff --git a/server/sonar-docs/src/images/alerts/info.svg b/server/sonar-docs/src/images/alerts/info.svg
new file mode 100644
index 00000000000..53a51953e41
--- /dev/null
+++ b/server/sonar-docs/src/images/alerts/info.svg
@@ -0,0 +1 @@
+<svg data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>info</title><path d="M24 12.2a2.37 2.37 0 1 0 2.37 2.37A2.37 2.37 0 0 0 24 12.2zM24 20.49a1.78 1.78 0 0 0-1.78 1.78v10.66a1.78 1.78 0 1 0 3.55 0V22.27A1.78 1.78 0 0 0 24 20.49z"/><path d="M24 3.91a19.55 19.55 0 1 0 19.58 19.54A19.57 19.57 0 0 0 24 3.91zm0 35.54a16 16 0 1 1 16-16 16 16 0 0 1-16 16z"/></svg> \ No newline at end of file
diff --git a/server/sonar-docs/src/images/alerts/wrong.svg b/server/sonar-docs/src/images/alerts/wrong.svg
new file mode 100644
index 00000000000..528f1622012
--- /dev/null
+++ b/server/sonar-docs/src/images/alerts/wrong.svg
@@ -0,0 +1 @@
+<svg data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>wrong</title><path d="M14.39 33a1.77 1.77 0 0 0 2.51 0l7-7 7 7a1.78 1.78 0 0 0 1.25.52 1.8 1.8 0 0 0 1.3-.52 1.78 1.78 0 0 0 0-2.51l-7-7 7-7A1.77 1.77 0 0 0 30.94 14l-7 7-7-7a1.77 1.77 0 0 0-2.51 2.51l7 7-7 7a1.78 1.78 0 0 0-.04 2.49z"/><path d="M24 3.91a19.55 19.55 0 1 0 19.58 19.54A19.57 19.57 0 0 0 24 3.91zm0 35.54a16 16 0 1 1 16-16 16 16 0 0 1-16 16z"/></svg> \ No newline at end of file
diff --git a/server/sonar-docs/src/images/community-icon.svg b/server/sonar-docs/src/images/community-icon.svg
new file mode 100644
index 00000000000..dfa53dfe92a
--- /dev/null
+++ b/server/sonar-docs/src/images/community-icon.svg
@@ -0,0 +1,2 @@
+
+<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><defs><style>.cls-1{fill:#c9232c}</style></defs><g id="Groupe_5744" data-name="Groupe 5744"><g id="user"><g id="User-2" data-name="User"><g id="Groupe_5718" data-name="Groupe 5718"><path id="Tracé_9562" data-name="Tracé 9562" d="M18.27 37.36c5.28 0 9.57-5.25 9.57-11.7S23.54 14 18.27 14a8.72 8.72 0 0 0-6.85 3.51 13.1 13.1 0 0 0-2.73 8.19c0 6.41 4.31 11.66 9.58 11.66zm0-21.28c4.1 0 7.44 4.3 7.44 9.58s-3.34 9.57-7.44 9.57-7.45-4.29-7.45-9.57 3.34-9.57 7.45-9.57zm7.44 19.15a1.07 1.07 0 0 0 0 2.13 7.45 7.45 0 0 1 7.45 7.44 1.07 1.07 0 0 1-1.07 1.07H4.44a1.07 1.07 0 0 1-1.07-1.07 7.45 7.45 0 0 1 7.45-7.44 1.07 1.07 0 0 0 0-2.13 9.61 9.61 0 0 0-9.58 9.58A3.2 3.2 0 0 0 4.44 48H32.1a3.19 3.19 0 0 0 3.19-3.19 9.6 9.6 0 0 0-9.58-9.58z"/></g></g></g><g id="Groupe_5727" data-name="Groupe 5727"><path id="Tracé_8533" data-name="Tracé 8533" class="cls-1" d="M43.69 27.71h-1.61a23.58 23.58 0 0 0-23.7-23.4v-1.6a25.19 25.19 0 0 1 25.31 25"/><path id="Tracé_8534" data-name="Tracé 8534" class="cls-1" d="M44.8 19.13A24.11 24.11 0 0 0 28.1 1.91l.38-1.29a25.51 25.51 0 0 1 17.64 18.2z"/><path id="Tracé_8535" data-name="Tracé 8535" class="cls-1" d="M46.61 11.53A24.67 24.67 0 0 0 37.09.9l.56-.9a25.7 25.7 0 0 1 9.93 11.07z"/></g></g></svg> \ No newline at end of file
diff --git a/server/sonar-docs/src/images/sq-icon-small.svg b/server/sonar-docs/src/images/sq-icon-small.svg
new file mode 100644
index 00000000000..f59febd15c4
--- /dev/null
+++ b/server/sonar-docs/src/images/sq-icon-small.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M11.939 13.282h-.756C11.183 7.203 6.167 2.256 0 2.256v-.755c6.585 0 11.939 5.286 11.939 11.781zm.522-4.053c-.906-3.816-3.997-7-7.872-8.111l.173-.603c4.09 1.174 7.353 4.538 8.311 8.57l-.612.144zm.581-3.575A11.572 11.572 0 0 0 8.562.648l.261-.43a12.081 12.081 0 0 1 4.677 5.22l-.458.216z" fill="#4e9bcd" fill-rule="nonzero"/></svg>
diff --git a/server/sonar-docs/src/images/tw-icon-small.svg b/server/sonar-docs/src/images/tw-icon-small.svg
new file mode 100644
index 00000000000..b88dc689044
--- /dev/null
+++ b/server/sonar-docs/src/images/tw-icon-small.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M13.5 2.57a5.52 5.52 0 0 1-1.593.428 2.685 2.685 0 0 0 1.216-1.524 5.431 5.431 0 0 1-1.756.668 2.666 2.666 0 0 0-2.022-.874c-.765 0-1.417.27-1.957.809a2.67 2.67 0 0 0-.809 1.958c0 .205.023.417.068.634a7.724 7.724 0 0 1-3.182-.853A7.84 7.84 0 0 1 .942 1.773a2.713 2.713 0 0 0-.377 1.396c0 .474.112.914.334 1.32.223.405.523.733.9.985a2.746 2.746 0 0 1-1.251-.352v.035c0 .668.21 1.254.63 1.76.42.506.949.824 1.589.955a2.846 2.846 0 0 1-.728.094c-.16 0-.334-.014-.523-.042a2.71 2.71 0 0 0 .977 1.366c.474.357 1.01.541 1.61.552a5.417 5.417 0 0 1-3.435 1.182c-.245 0-.468-.011-.668-.034a7.684 7.684 0 0 0 4.249 1.242c.982 0 1.904-.155 2.766-.467.863-.311 1.599-.728 2.21-1.25a8.407 8.407 0 0 0 1.581-1.803c.442-.68.772-1.39.989-2.129A7.86 7.86 0 0 0 12.112 4 5.805 5.805 0 0 0 13.5 2.57z" fill="#4d9bce" fill-rule="nonzero"/></svg>
diff --git a/server/sonar-docs/src/layouts/components/CategoryLink.js b/server/sonar-docs/src/layouts/components/CategoryLink.js
new file mode 100644
index 00000000000..2cfc743b1ec
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/CategoryLink.js
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import Link from 'gatsby-link';
+import SubpageLink from './SubpageLink';
+import HeadingsLink from './HeadingsLink';
+import { sortNodes } from '../utils';
+import ChevronDownIcon from './icons/ChevronDownIcon';
+import ChevronUpIcon from './icons/ChevronUpIcon';
+
+export default function CategoryLink({ node, location, headers, onToggle }) {
+ const hasChild = node.pages && node.pages.length > 0;
+ const prefix = process.env.GATSBY_USE_PREFIX === '1' ? '/' + process.env.GATSBY_DOCS_VERSION : '';
+ const { slug } = node.fields;
+ const isCurrentPage = location.pathname === prefix + slug;
+ const open = location.pathname.startsWith(prefix + slug);
+ return (
+ <div>
+ <h2 className={isCurrentPage || open ? 'active' : ''}>
+ <Link to={slug} title={node.frontmatter.title}>
+ {hasChild && open && <ChevronUpIcon />}
+ {hasChild && !open && <ChevronDownIcon />}
+ {node.frontmatter.title}
+ </Link>
+ </h2>
+ {isCurrentPage && <HeadingsLink headers={headers} />}
+ {hasChild &&
+ open && (
+ <div className="sub-menu">
+ {sortNodes(node.pages).map(page => (
+ <SubpageLink
+ key={page.fields.slug}
+ headers={headers}
+ displayHeading={location.pathname === prefix + page.fields.slug}
+ node={page}
+ />
+ ))}
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/components/Footer.js b/server/sonar-docs/src/layouts/components/Footer.js
new file mode 100644
index 00000000000..e807022e79b
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/Footer.js
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+
+export default function Footer() {
+ return (
+ <div className="page-footer">
+ <a
+ rel="noopener noreferrer"
+ target="_blank"
+ title="Creative Commons License"
+ href="https://creativecommons.org/licenses/by-nc/3.0/us/">
+ <img
+ alt="Creative Commons License"
+ src="https://licensebuttons.net/l/by-nc/3.0/us/88x31.png"
+ />
+ </a>
+ © 2008-2017, SonarSource S.A, Switzerland. Except where otherwise noted, content in this space
+ is licensed under a{' '}
+ <a
+ rel="noopener noreferrer"
+ target="_blank"
+ href="https://creativecommons.org/licenses/by-nc/3.0/us/">
+ Creative Commons Attribution-NonCommercial 3.0 United States License.
+ </a>{' '}
+ SONARQUBE is a trademark of SonarSource SA. All other trademarks and copyrights are the
+ property of their respective owners.
+ </div>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/components/HeaderList.js b/server/sonar-docs/src/layouts/components/HeaderList.js
new file mode 100644
index 00000000000..56a029e3b66
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/HeaderList.js
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import * as PropTypes from 'prop-types';
+
+export default class HeaderList extends React.PureComponent {
+ static contextTypes = {
+ headers: PropTypes.object.isRequired
+ };
+
+ componentDidMount() {
+ this.context.headers.setHeaders(this.props.headers);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.headers.length !== this.props.headers.length) {
+ this.context.headers.setHeaders(prevProps.headers);
+ }
+ }
+
+ render() {
+ return null;
+ }
+}
diff --git a/server/sonar-docs/src/layouts/components/HeaderListProvider.js b/server/sonar-docs/src/layouts/components/HeaderListProvider.js
new file mode 100644
index 00000000000..1318b5c3854
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/HeaderListProvider.js
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import * as PropTypes from 'prop-types';
+
+export default class HeaderListProvider extends React.Component {
+ headers = [];
+
+ static childContextTypes = {
+ headers: PropTypes.object
+ };
+
+ state = { headers: [] };
+
+ getChildContext = () => {
+ return {
+ headers: {
+ setHeaders: this.setHeaders
+ }
+ };
+ };
+
+ setHeaders = headers => {
+ this.setState({ headers });
+ };
+
+ render() {
+ return this.props.children({ headers: this.state.headers });
+ }
+}
diff --git a/server/sonar-docs/src/layouts/components/HeadingsLink.js b/server/sonar-docs/src/layouts/components/HeadingsLink.js
new file mode 100644
index 00000000000..d7cf430ac01
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/HeadingsLink.js
@@ -0,0 +1,93 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+
+export default class HeadingsLink extends React.Component {
+ componentDidMount() {
+ document.addEventListener('scroll', this.scrollHandler, true);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('scroll', this.scrollHandler, true);
+ }
+
+ highlightHeading = (index, scrollTo) => {
+ const previousNode = document.querySelector('.targetted-heading');
+ if (previousNode) {
+ previousNode.classList.remove('targetted-heading');
+ }
+
+ const node = document.querySelector('#header-' + index);
+ if (node) {
+ node.classList.add('targetted-heading');
+ if (scrollTo) {
+ window.scrollTo(0, node.offsetTop - 30);
+ }
+ }
+ };
+
+ scrollHandler = () => {
+ const headings = Array.from(document.querySelectorAll('.headings-container ul li a'));
+ const scrollTop = window.pageYOffset | document.body.scrollTop;
+ let headingIndex = 0;
+ for (let i = 0; i < headings.length; i++) {
+ if (document.querySelector('#header-' + (i + 1)).offsetTop > scrollTop + 40) {
+ break;
+ }
+ headingIndex = i;
+ }
+ headings.forEach(h => h.classList.remove('active'));
+ headings[headingIndex].classList.add('active');
+ this.highlightHeading(headingIndex + 1, false);
+ };
+
+ clickHandler = target => {
+ return event => {
+ event.stopPropagation();
+ event.preventDefault();
+ this.highlightHeading(target, true);
+ };
+ };
+
+ render() {
+ const headers = this.props.headers.filter(
+ h => h.depth === 2 && h.value.toLowerCase() !== 'table of contents'
+ );
+ if (headers.length < 1) {
+ return null;
+ }
+
+ return (
+ <div className="headings-container">
+ <ul>
+ {headers.map((header, index) => {
+ return (
+ <li key={index + 1}>
+ <a onClick={this.clickHandler(index + 1)} href={'#header-' + (index + 1)}>
+ {header.value}
+ </a>
+ </li>
+ );
+ })}
+ </ul>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-docs/src/layouts/components/OutsideClickHandler.js b/server/sonar-docs/src/layouts/components/OutsideClickHandler.js
new file mode 100644
index 00000000000..b5c1e9657dd
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/OutsideClickHandler.js
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import { findDOMNode } from 'react-dom';
+
+export default class OutsideClickHandler extends React.Component {
+ element = null;
+
+ componentDidMount() {
+ setTimeout(() => {
+ this.addClickHandler();
+ }, 0);
+ }
+
+ componentWillUnmount() {
+ this.removeClickHandler();
+ }
+
+ addClickHandler = () => {
+ window.addEventListener('click', this.handleWindowClick);
+ };
+
+ removeClickHandler = () => {
+ window.removeEventListener('click', this.handleWindowClick);
+ };
+
+ handleWindowClick = event => {
+ const node = findDOMNode(this);
+ if (!node || !node.contains(event.target)) {
+ this.props.onClickOutside();
+ }
+ };
+
+ render() {
+ return this.props.children;
+ }
+}
diff --git a/server/sonar-docs/src/layouts/components/Search.js b/server/sonar-docs/src/layouts/components/Search.js
new file mode 100644
index 00000000000..569bf4c4eb9
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/Search.js
@@ -0,0 +1,98 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 React, { Component } from 'react';
+import lunr, { LunrIndex } from 'lunr';
+
+// Search component
+export default class Search extends Component {
+ index = null;
+
+ constructor(props) {
+ super(props);
+ this.index = lunr(function() {
+ this.ref('id');
+ this.field('title', { boost: 10 });
+ this.field('text');
+
+ this.metadataWhitelist = ['position'];
+
+ props.pages.forEach(page =>
+ this.add({
+ id: page.id,
+ title: page.frontmatter.title,
+ text: page.html.replace(/<(?:.|\n)*?>/gm, '')
+ })
+ );
+ });
+ }
+
+ getFormattedResults = (query, results) => {
+ return results.map(match => {
+ const page = this.props.pages.find(page => page.id === match.ref);
+ const highlights = {};
+ let longestTerm = '';
+
+ // remember the longest term that matches the query *exactly*
+ Object.keys(match.matchData.metadata).forEach(term => {
+ if (query.toLowerCase().includes(term.toLowerCase()) && longestTerm.length < term.length) {
+ longestTerm = term;
+ }
+
+ Object.keys(match.matchData.metadata[term]).forEach(fieldName => {
+ const { position: positions } = match.matchData.metadata[term][fieldName];
+ highlights[fieldName] = [...(highlights[fieldName] || []), ...positions];
+ });
+ });
+
+ return {
+ page: {
+ id: page.id,
+ slug: page.fields.slug,
+ title: page.frontmatter.title,
+ text: page.html.replace(/<(?:.|\n)*?>/gm, '')
+ },
+ highlights,
+ longestTerm
+ };
+ });
+ };
+
+ handleChange = event => {
+ const { value } = event.currentTarget;
+ if (value != '') {
+ const results = this.getFormattedResults(value, this.index.search(`${value}~1 ${value}*`));
+ this.props.onResultsChange(results);
+ } else {
+ this.props.onResultsChange([]);
+ }
+ };
+
+ render() {
+ return (
+ <input
+ aria-label="Search"
+ className="search-input"
+ onChange={this.handleChange}
+ placeholder="Search..."
+ type="search"
+ />
+ );
+ }
+}
diff --git a/server/sonar-docs/src/layouts/components/SearchEntryResult.js b/server/sonar-docs/src/layouts/components/SearchEntryResult.js
new file mode 100644
index 00000000000..bfd0daba0ca
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/SearchEntryResult.js
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import Link from 'gatsby-link';
+import { highlightMarks, cutWords } from '../utils';
+
+export default function SearchResultEntry({ active, result }) {
+ return (
+ <Link className={active ? 'active search-result' : 'search-result'} to={result.page.slug}>
+ <SearchResultTitle result={result} />
+ <SearchResultText result={result} />
+ </Link>
+ );
+}
+
+export function SearchResultTitle({ result }) {
+ let titleWithMarks;
+
+ const titleHighlights = result.highlights.title;
+ if (titleHighlights && titleHighlights.length > 0) {
+ const { title } = result.page;
+ const tokens = highlightMarks(
+ title,
+ titleHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))
+ );
+ titleWithMarks = <SearchResultTokens tokens={tokens} />;
+ } else {
+ titleWithMarks = result.page.title;
+ }
+
+ return <div className="search-result">{titleWithMarks}</div>;
+}
+
+export function SearchResultText({ result }) {
+ const textHighlights = result.highlights.text;
+ if (textHighlights && textHighlights.length > 0) {
+ const { text } = result.page;
+ const tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] })));
+ return (
+ <div className="note">
+ <SearchResultTokens tokens={cutWords(tokens)} />
+ </div>
+ );
+ } else {
+ return null;
+ }
+}
+
+export function SearchResultTokens({ tokens }) {
+ return (
+ <span>
+ {tokens.map((token, index) => (
+ <span key={index}>{token.marked ? <mark key={index}>{token.text}</mark> : token.text}</span>
+ ))}
+ </span>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/components/Sidebar.js b/server/sonar-docs/src/layouts/components/Sidebar.js
new file mode 100644
index 00000000000..cf68d376f42
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/Sidebar.js
@@ -0,0 +1,128 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 React from 'react';
+import Link from 'gatsby-link';
+import { fromPairs } from 'lodash';
+import { sortNodes } from '../utils';
+import CategoryLink from './CategoryLink';
+import VersionSelect from './VersionSelect';
+import Search from './Search';
+import SearchEntryResult from './SearchEntryResult';
+
+export default class Sidebar extends React.PureComponent {
+ state = { loaded: false, results: [], versions: [] };
+
+ componentDidMount() {
+ this.loadVersions();
+ }
+
+ loadVersions() {
+ fetch('/DocsVersions.json').then(response =>
+ response.json().then(json => {
+ this.setState({ loaded: true, versions: json });
+ })
+ );
+ }
+
+ getPagesHierarchy() {
+ const categories = sortNodes(
+ this.props.pages.filter(p => p.fields.slug.split('/').length === 3)
+ );
+ const pages = this.props.pages.filter(p => p.fields.slug.split('/').length > 3);
+ const categoriesObject = fromPairs(categories.map(c => [c.fields.slug, { ...c, pages: [] }]));
+ pages.forEach(page => {
+ const parentSlug = page.fields.slug
+ .split('/')
+ .slice(0, 2)
+ .join('/');
+ categoriesObject[parentSlug + '/'].pages.push(page);
+ });
+ return categoriesObject;
+ }
+
+ renderResults = () => {
+ return (
+ <div>
+ {this.state.results.map(result => (
+ <SearchEntryResult
+ active={
+ (this.props.location.pathname === result.page.slug && result.page.slug === '/') ||
+ (result.page.slug !== '/' && this.props.location.pathname.endsWith(result.page.slug))
+ }
+ key={result.page.id}
+ result={result}
+ />
+ ))}
+ </div>
+ );
+ };
+
+ handleSearch = results => {
+ this.setState({ results });
+ };
+
+ render() {
+ const nodes = this.getPagesHierarchy();
+ const isOnCurrentVersion =
+ this.state.versions.find(v => v.value === this.props.version) !== undefined;
+ return (
+ <div className="page-sidebar">
+ <div className="sidebar-header">
+ <Link to="/">
+ <img
+ alt="Continuous Code Quality"
+ css={{ verticalAlign: 'top', margin: 0 }}
+ width="160"
+ src="/images/SonarQubeIcon.svg"
+ title="Continuous Code Quality"
+ />
+ </Link>
+ <VersionSelect
+ location={this.props.location}
+ version={this.props.version}
+ versions={this.state.versions}
+ />
+
+ {this.state.loaded &&
+ !isOnCurrentVersion && (
+ <div className="alert alert-warning">
+ This is an archived version of the doc for{' '}
+ <b>SonarQube version {this.props.version}</b>. <a href="/">See Documentation</a> for
+ current functionnality.
+ </div>
+ )}
+ </div>
+ <div className="page-indexes">
+ <Search pages={this.props.pages} onResultsChange={this.handleSearch} />
+ {this.state.results.length > 0 && this.renderResults()}
+ {this.state.results.length === 0 &&
+ Object.keys(nodes).map(key => (
+ <CategoryLink
+ key={key}
+ headers={this.props.headers}
+ node={nodes[key]}
+ location={this.props.location}
+ />
+ ))}
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-docs/src/layouts/components/SubpageLink.js b/server/sonar-docs/src/layouts/components/SubpageLink.js
new file mode 100644
index 00000000000..1d4746fb73f
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/SubpageLink.js
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import Link from 'gatsby-link';
+import HeadingsLink from './HeadingsLink';
+
+export default function SubpageLink({ node, headers, displayHeading }) {
+ return (
+ <div>
+ <h3>
+ <Link className={displayHeading ? 'active' : ''} to={node.fields.slug}>
+ {node.frontmatter.title}
+ </Link>
+ </h3>
+ {displayHeading && <HeadingsLink headers={headers} />}
+ </div>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/components/VersionSelect.js b/server/sonar-docs/src/layouts/components/VersionSelect.js
new file mode 100644
index 00000000000..3028c9dcc39
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/VersionSelect.js
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import fetch from 'isomorphic-fetch';
+import ChevronDownIcon from './icons/ChevronDownIcon';
+import ChevronUpIcon from './icons/ChevronUpIcon';
+import OutsideClickHandler from './OutsideClickHandler';
+
+export default class VersionSelect extends React.PureComponent {
+ state = { open: false };
+
+ handleClick = () => {
+ this.setState(state => ({ open: !state.open }));
+ };
+
+ handleClickOutside = () => {
+ this.setState({ open: false });
+ };
+
+ render() {
+ const { versions } = this.props;
+ const hasVersions = versions.length > 1;
+ const isOnCurrentVersion =
+ !hasVersions || versions.find(v => v.value === this.props.version) !== undefined;
+ return (
+ <div className="version-select">
+ <button onClick={this.handleClick}>
+ Docs <span className={isOnCurrentVersion ? 'current' : ''}>{this.props.version}</span>
+ {hasVersions && !this.state.open && <ChevronDownIcon size={10} />}
+ {hasVersions && this.state.open && <ChevronUpIcon size={10} />}
+ </button>
+ {this.state.open &&
+ hasVersions && (
+ <OutsideClickHandler onClickOutside={this.handleClickOutside}>
+ <ul>
+ {versions.map(version => {
+ return (
+ <li key={version.value}>
+ <a href={version.current ? '/' : '/' + version.value}>
+ <span className={version.current ? 'current' : ''}>{version.value}</span>
+ </a>
+ </li>
+ );
+ })}
+ </ul>
+ </OutsideClickHandler>
+ )}
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-docs/src/layouts/components/icons/AlertWarnIcon.js b/server/sonar-docs/src/layouts/components/icons/AlertWarnIcon.js
new file mode 100644
index 00000000000..02633abcdde
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/icons/AlertWarnIcon.js
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import Icon from './Icon';
+
+export default function AlertWarnIcon({ className, fill = '#ed7d20', size }) {
+ return (
+ <Icon className={className} size={size}>
+ <path
+ d="M8 1.143q1.866 0 3.442.92t2.496 2.496.92 3.442-.92 3.442-2.496 2.496-3.442.92-3.442-.92-2.496-2.496-.92-3.442.92-3.442 2.496-2.496T8 1.143zm1.143 11.134v-1.696q0-.125-.08-.21t-.196-.085H7.153q-.116 0-.205.089t-.089.205v1.696q0 .116.089.205t.205.089h1.714q.116 0 .196-.085t.08-.21zm-.018-3.072l.161-5.545q0-.107-.089-.161-.089-.071-.214-.071H7.019q-.125 0-.214.071-.089.054-.089.161l.152 5.545q0 .089.089.156t.214.067h1.652q.125 0 .21-.067t.094-.156z"
+ style={{ fill }}
+ />
+ </Icon>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/components/icons/ChevronDownIcon.js b/server/sonar-docs/src/layouts/components/icons/ChevronDownIcon.js
new file mode 100644
index 00000000000..e401334a24b
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/icons/ChevronDownIcon.js
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import Icon from './Icon';
+
+export default function ChevronDownIcon({ className, fill = 'currentColor', size }) {
+ return (
+ <Icon className={className} size={size}>
+ <path
+ d="M3.2,5.6c0-0.1,0-0.2,0.1-0.3c0.2-0.2,0.5-0.2,0.6,0l4.1,4.1l4.1-4.1c0.2-0.2,0.5-0.2,0.6,0 c0.2,0.2,0.2,0.5,0,0.6c0,0,0,0,0,0l0,0l-4.5,4.5c-0.2,0.2-0.5,0.2-0.6,0l0,0L3.3,5.9C3.2,5.9,3.2,5.7,3.2,5.6z"
+ style={{ fill }}
+ />
+ </Icon>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/components/icons/ChevronUpIcon.js b/server/sonar-docs/src/layouts/components/icons/ChevronUpIcon.js
new file mode 100644
index 00000000000..26393e8a35c
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/icons/ChevronUpIcon.js
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import Icon from './Icon';
+
+export default function ChevronUpIcon({ className, fill = 'currentColor', size }) {
+ return (
+ <Icon className={className} size={size}>
+ <path
+ d="M13,10c0,0.1,0,0.2-0.1,0.3c-0.2,0.2-0.5,0.2-0.6,0L8.1,6.2L4,10.3c-0.2,0.2-0.5,0.2-0.6,0 c-0.2-0.2-0.2-0.5,0-0.6c0,0,0,0,0,0l0,0l4.5-4.5c0.2-0.2,0.5-0.2,0.6,0l0,0l4.4,4.4C13,9.7,13,9.8,13,10z"
+ style={{ fill }}
+ />
+ </Icon>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/components/icons/DownloadIcon.js b/server/sonar-docs/src/layouts/components/icons/DownloadIcon.js
new file mode 100644
index 00000000000..afd2d92009c
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/icons/DownloadIcon.js
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import Icon from './Icon';
+
+export default function DownloadIcon({ className, fill = 'currentColor', size }) {
+ return (
+ <Icon className={className} size={size} viewBox="0 0 48 48">
+ <path
+ style={{ fill }}
+ d="M45.68 22.86a1.31 1.31 0 0 0-1.32 1.32v12a5.91 5.91 0 0 1-5.9 5.91H9.54a5.91 5.91 0 0 1-5.9-5.91V24A1.32 1.32 0 0 0 1 24v12.16a8.56 8.56 0 0 0 8.54 8.55h28.92A8.56 8.56 0 0 0 47 36.16v-12a1.32 1.32 0 0 0-1.32-1.3z"
+ />
+ <path
+ d="M23.07 34.24a1.36 1.36 0 0 0 .93.39 1.32 1.32 0 0 0 .93-.39l8.37-8.38A1.32 1.32 0 0 0 31.44 24l-6.12 6.13V3.39a1.32 1.32 0 0 0-2.64 0v26.74L16.55 24a1.32 1.32 0 0 0-1.86 1.86z"
+ style={{ fill }}
+ />
+ </Icon>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/components/icons/Icon.js b/server/sonar-docs/src/layouts/components/icons/Icon.js
new file mode 100644
index 00000000000..3e7673e48fa
--- /dev/null
+++ b/server/sonar-docs/src/layouts/components/icons/Icon.js
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+
+export default function Icon({
+ children,
+ className,
+ size = 16,
+ style,
+ height = size,
+ width = size,
+ viewBox = '0 0 16 16',
+ ...other
+}) {
+ return (
+ <svg
+ className={className}
+ height={height}
+ style={{
+ fillRule: 'evenodd',
+ clipRule: 'evenodd',
+ strokeLinejoin: 'round',
+ strokeMiterlimit: 1.41421,
+ ...style
+ }}
+ version="1.1"
+ viewBox={viewBox}
+ width={width}
+ xmlSpace="preserve"
+ xmlnsXlink="http://www.w3.org/1999/xlink"
+ {...other}>
+ {children}
+ </svg>
+ );
+}
diff --git a/server/sonar-docs/src/layouts/index.js b/server/sonar-docs/src/layouts/index.js
index 200d4210c8c..2fc755141ce 100644
--- a/server/sonar-docs/src/layouts/index.js
+++ b/server/sonar-docs/src/layouts/index.js
@@ -1,42 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 React from 'react';
+import Sidebar from './components/Sidebar';
+import DownloadIcon from './components/icons/DownloadIcon';
+import Footer from './components/Footer';
+import HeaderListProvider from './components/HeaderListProvider';
-const headerHeight = 48;
-
-const containerCss = {
- minWidth: 320,
- maxWidth: 800,
- marginLeft: 'auto',
- marginRight: 'auto',
- paddingLeft: 16,
- paddingRight: 16
-};
+const version = process.env.GATSBY_DOCS_VERSION || '1.0';
export default function Layout(props) {
return (
- <div>
- <header css={{ height: headerHeight, backgroundColor: '#262626' }}>
- <div
- css={{
- display: 'flex',
- alignItems: 'center',
- alignContent: 'center',
- height: headerHeight,
- ...containerCss
- }}>
- <a href="/">
- <img
- alt="Continuous Code Quality"
- css={{ verticalAlign: 'top', margin: 0 }}
- height="30"
- src="https://next.sonarqube.com/sonarqube/images/logo.svg?v=6.6"
- title="Continuous Code Quality"
- width="83"
- />
- </a>
- </div>
- </header>
-
- <div css={containerCss}>{props.children()}</div>
+ <div className="main-container">
+ <div className="blue-bar" />
+ <HeaderListProvider>
+ {({ headers }) => (
+ <div className="layout-page">
+ <div className="page-sidebar-inner">
+ <Sidebar
+ headers={headers}
+ location={props.location}
+ pages={props.data.allMarkdownRemark.edges
+ .map(e => e.node)
+ .filter(n => !n.fields.slug.startsWith('/tooltips'))
+ .filter(
+ n =>
+ !n.frontmatter.scope ||
+ n.frontmatter.scope === 'sonarqube' ||
+ n.frontmatter.scope === 'static'
+ )}
+ searchIndex={props.data.siteSearchIndex}
+ version={version}
+ />
+ </div>
+ <div className="page-main">
+ <div className="useful-links-block">
+ <div className="useful-link-title">Download</div>
+ <a href="https://www.sonarqube.org/" rel="noopener noreferrer" target="_blank">
+ <DownloadIcon /> SonarQube
+ </a>
+ <div className="useful-link-title">Get Help</div>
+ <a
+ href="https://community.sonarsource.com/"
+ rel="noopener noreferrer"
+ target="_blank">
+ <img src="/images/community-icon.svg" alt="Community" /> Community
+ </a>
+ <div className="useful-link-title">Stay Connected</div>
+ <a href="https://twitter.com/SonarQube" rel="noopener noreferrer" target="_blank">
+ <img src="/images/tw-icon-small.svg" alt="Twitter" /> Twitter
+ </a>
+ <a
+ href="https://www.sonarsource.com/resources/product-news/"
+ rel="noopener noreferrer"
+ target="_blank">
+ <img src="/images/sq-icon-small.svg" alt="Product News" /> Product News
+ </a>
+ </div>
+ <div className="page-container">{props.children()}</div>
+ <Footer />
+ </div>
+ </div>
+ )}
+ </HeaderListProvider>
</div>
);
}
+
+export const query = graphql`
+ query IndexQuery {
+ allMarkdownRemark {
+ edges {
+ node {
+ id
+ headings {
+ depth
+ value
+ }
+ frontmatter {
+ title
+ order
+ scope
+ }
+ fields {
+ slug
+ }
+ html
+ }
+ }
+ }
+ }
+`;
diff --git a/server/sonar-docs/src/layouts/utils.js b/server/sonar-docs/src/layouts/utils.js
new file mode 100644
index 00000000000..9fdae25b2fd
--- /dev/null
+++ b/server/sonar-docs/src/layouts/utils.js
@@ -0,0 +1,103 @@
+import { sortBy } from 'lodash';
+
+export function sortNodes(nodes) {
+ return nodes.sort((a, b) => {
+ if (a.frontmatter.order) {
+ return b.frontmatter.order ? a.frontmatter.order - b.frontmatter.order : 1;
+ }
+ return a.frontmatter.title < b.frontmatter.title ? -1 : 1;
+ });
+}
+
+const WORDS = 6;
+
+function cutLeadingWords(str) {
+ let words = 0;
+ for (let i = str.length - 1; i >= 0; i--) {
+ if (/\s/.test(str[i])) {
+ words++;
+ }
+ if (words === WORDS) {
+ return i > 0 ? `...${str.substring(i + 1)}` : str;
+ }
+ }
+ return str;
+}
+
+function cutTrailingWords(str) {
+ let words = 0;
+ for (let i = 0; i < str.length; i++) {
+ if (/\s/.test(str[i])) {
+ words++;
+ }
+ if (words === WORDS) {
+ return i < str.length - 1 ? `${str.substring(0, i)}...` : str;
+ }
+ }
+ return str;
+}
+
+export function cutWords(tokens) {
+ const result = [];
+ let length = 0;
+
+ const highlightPos = tokens.findIndex(token => token.marked);
+ if (highlightPos > 0) {
+ const text = cutLeadingWords(tokens[highlightPos - 1].text);
+ result.push({ text, marked: false });
+ length += text.length;
+ }
+
+ result.push(tokens[highlightPos]);
+ length += tokens[highlightPos].text.length;
+
+ for (let i = highlightPos + 1; i < tokens.length; i++) {
+ if (length + tokens[i].text.length > 100) {
+ const text = cutTrailingWords(tokens[i].text);
+ result.push({ text, marked: false });
+ return result;
+ } else {
+ result.push(tokens[i]);
+ length += tokens[i].text.length;
+ }
+ }
+
+ return result;
+}
+
+export function highlightMarks(str, marks) {
+ const sortedMarks = sortBy(
+ [
+ ...marks.map(mark => ({ pos: mark.from, start: true })),
+ ...marks.map(mark => ({ pos: mark.to, start: false }))
+ ],
+ mark => mark.pos,
+ mark => Number(!mark.start)
+ );
+
+ const cuts = [];
+ let start = 0;
+ let balance = 0;
+
+ for (const mark of sortedMarks) {
+ if (mark.start) {
+ if (balance === 0 && start !== mark.pos) {
+ cuts.push({ text: str.substring(start, mark.pos), marked: false });
+ start = mark.pos;
+ }
+ balance++;
+ } else {
+ balance--;
+ if (balance === 0 && start !== mark.pos) {
+ cuts.push({ text: str.substring(start, mark.pos), marked: true });
+ start = mark.pos;
+ }
+ }
+ }
+
+ if (start < str.length - 1) {
+ cuts.push({ text: str.substr(start), marked: false });
+ }
+
+ return cuts;
+}
diff --git a/server/sonar-docs/src/pages/analysis/index.md b/server/sonar-docs/src/pages/analysis/index.md
index 795d07c2ecd..e6603fe0a8c 100644
--- a/server/sonar-docs/src/pages/analysis/index.md
+++ b/server/sonar-docs/src/pages/analysis/index.md
@@ -28,8 +28,8 @@ SonarQube can perform analysis on 20+ different languages. The outcome of this a
* A static analysis of compiled code can be performed for certain languages (.class files in Java, .dll files in C#, etc.)
* A dynamic analysis of code can be performed on certain languages.
-## Will _all_ files be analyzed?
-By default, only files that are recognized by a language analyzer are loaded into the project during analysis. For example if your SonarQube instance had only SonarJava SonarJS on board, all .java and .js files would be loaded, but .xml files would be ignored.
+## Will all files be analyzed?
+By default, only files that are recognized by a language analyzer are loaded into the project during analysis. For example if your SonarQube instance had only SonarJava SonarJS on board, all .java and .js files would be loaded, but .xml files would be ignored.
## What happens during analysis?
During analysis, data is requested from the server, the files provided to the analysis are analyzed, and the resulting data is sent back to the server at the end in the form of a report, which is then analyzed asynchronously server-side.
diff --git a/server/sonar-docs/src/pages/branches/short-lived-branches.md b/server/sonar-docs/src/pages/branches/short-lived-branches.md
index d84f56a0ba0..6b0c5ea4008 100644
--- a/server/sonar-docs/src/pages/branches/short-lived-branches.md
+++ b/server/sonar-docs/src/pages/branches/short-lived-branches.md
@@ -29,7 +29,7 @@ Modified files are determined based on the checksum of each file on the sonar.br
## New Code Period
-The ephemeral nature of short-lived branches means no explicit New Code Period is necessary; it's all new code.
+The ephemeral nature of short-lived branches means the New Code Period is implicit; everything changed in the branch is new code.
## Settings and Quality Profiles on Branches
diff --git a/server/sonar-docs/src/pages/user-account.md b/server/sonar-docs/src/pages/user-account.md
new file mode 100644
index 00000000000..43feacfed3f
--- /dev/null
+++ b/server/sonar-docs/src/pages/user-account.md
@@ -0,0 +1,19 @@
+---
+title: User Account
+---
+
+As a <!-- sonarqube -->SonarQube<!-- /sonarqube --><!-- sonarcloud -->SonarCloud<!-- /sonarcloud --> user you have your own space where you can see the things that are relevant to you:
+
+## Home Page
+
+It gives you a summary of:
+
+* your Groups
+* your SCM accounts
+
+## Security
+
+In addition to being able to change your password, if your instance is not using a 3rd party authentication mechanism such as LDAP or any OAuth provider (GitHub, Google Account, ...), you can manage your own [authentication tokens](/user-token).
+
+You can create as many Token as you want. Once a Token is created, you can use it to publish analysis to a project where you have the [execute analysis](/security/authorization) permission.
+
diff --git a/server/sonar-docs/src/templates/page.css b/server/sonar-docs/src/templates/page.css
index 3b7180f4baf..ddc3612e66d 100644
--- a/server/sonar-docs/src/templates/page.css
+++ b/server/sonar-docs/src/templates/page.css
@@ -1,8 +1,461 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.
+ */
+html,
+body,
+body > div,
+.main-container {
+ height: 100%;
+}
+
+.blue-bar {
+ background: #4c9bd6;
+ height: 5px;
+ position: fixed;
+ width: 100%;
+ z-index: 100;
+}
+
+.layout-page {
+ align-items: stretch;
+ display: flex;
+ flex-grow: 1;
+ height: 100%;
+ width: 100%;
+}
+
+.page-sidebar {
+ background-color: #f9f9fb;
+ flex-grow: 0;
+ flex-shrink: 0;
+ height: 100%;
+ padding: 26px;
+ padding-bottom: 0;
+ width: 320px;
+ display: flex;
+ flex-direction: column;
+}
+
+.page-sidebar-inner {
+ background-color: #f9f9fb;
+ height: 100%;
+ overflow: auto;
+ position: fixed;
+}
+
+.sidebar-header {
+ border-bottom: 1px solid #cfd3d7;
+ padding-bottom: 10px;
+ margin-right: -6px;
+}
+
+.page-sidebar .alert {
+ margin: 16px 0;
+}
+
+.page-main {
+ background: white;
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ margin-left: 330px;
+ min-height: 100%;
+ min-width: 740px;
+ padding: 20px 20px 0 20px;
+ z-index: 50;
+}
+
+.useful-links-block {
+ width: 200px;
+ right: 32px;
+ top: 32px;
+ position: fixed;
+ background: #f9f9fb;
+ padding: 0 22px 22px 22px;
+ border-radius: 3px;
+}
+
+.useful-link-title {
+ font-size: 12px;
+ margin-top: 16px;
+ margin-bottom: 4px;
+}
+
+.useful-links-block a {
+ color: #070706;
+ display: block;
+ font-size: 14px;
+ text-decoration: inherit;
+ line-height: 25px;
+ transition: all 0.2s ease;
+ margin: -4px;
+ padding: 4px;
+ border-radius: 3px;
+}
+
+.useful-links-block a:hover {
+ background-color: #e8eff5;
+}
+
+.useful-links-block a svg,
+.useful-links-block a img {
+ margin-right: 6px;
+ margin-left: 4px;
+ margin-bottom: 0;
+ height: 16px;
+ transform: translateY(2px);
+}
+
+@media only screen and (max-width: 1100px) {
+ .useful-links-block {
+ display: none;
+ }
+ .page-container {
+ max-width: 100% !important;
+ }
+}
+
+.search-input {
+ border: 1px solid #cfd3d7;
+ border-radius: 2px;
+ width: calc(100% - 10px);
+ margin-left: 10px;
+ margin-bottom: 10px;
+ padding: 0 10px;
+ font-size: 14px;
+ line-height: 30px;
+ outline: none;
+}
+
+.search-input:focus,
+.search-input:hover {
+ border: 1px solid #8a8c8f;
+}
+
+a.search-result {
+ color: #444;
+ font-weight: normal;
+ text-decoration: none;
+ display: block;
+ padding: 8px;
+ margin-right: -10px;
+}
+
+a.search-result.active,
+a.search-result.active mark {
+ color: #2679af;
+}
+
+a.search-result:hover {
+ background: #e8eff5;
+ border-radius: 3px;
+}
+
+a.search-result mark {
+ color: #444;
+ font-weight: 700;
+ background: 0 0;
+}
+
+a.search-result .note {
+ margin: 6px 0;
+ font-size: 12px;
+}
+
+.page-indexes {
+ padding: 16px 20px 50px 16px;
+ margin: 10px -26px 0 -26px;
+ flex-grow: 1;
+ overflow: auto;
+}
+
+.page-indexes > div > a.search-result {
+ margin: 0 -4px 0 4px;
+}
+
+.page-indexes h2 {
+ color: #2d3032;
+ font-size: 16px;
+ font-weight: bold;
+ line-height: 35px;
+ margin: 0;
+ padding: 0 10px;
+ margin-right: -10px;
+ border-radius: 3px;
+ transition: all 0.2s ease;
+}
+
+.page-indexes h2:hover {
+ background-color: #e8eff5;
+}
+
+.page-indexes h2.active {
+ color: #2679af;
+}
+
+.page-indexes h2 a,
+.page-indexes h3 a {
+ color: inherit;
+ text-decoration: inherit;
+ display: block;
+}
+
+.page-indexes h2 svg {
+ float: right;
+ transform: translateY(9px);
+}
+
+.sub-menu {
+ padding: 0 0 10px 22px;
+}
+
+.sub-menu h3 {
+ font-size: 14px;
+ line-height: 26px;
+ margin: 0;
+}
+
+.sub-menu a:hover,
+.sub-menu a.active {
+ color: #2679af;
+}
+
+.headings-container {
+ background: #e8eff4;
+ margin: 4px -20px 8px -36px;
+ padding: 4px 0;
+}
+
+.headings-container:before {
+ border-bottom: 5px solid #e8eff4;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ content: '\A';
+ height: 0;
+ left: 50px;
+ margin-top: -8px;
+ position: absolute;
+ width: 0;
+}
+
+.headings-container ul {
+ margin: 0;
+ padding: 8px 6px 8px 18px;
+}
+
+.headings-container li {
+ color: #2679af;
+ list-style-position: inside;
+ list-style-type: circle;
+ margin: 0;
+ line-height: 16px;
+ padding: 4px 26px 4px 58px;
+}
+
+.sub-menu .headings-container li {
+ padding-left: 38px;
+}
+
+.headings-container a {
+ display: block;
+ margin-top: -15px;
+ font-size: 13px;
+ color: #2d3032;
+ text-decoration: inherit;
+ margin-left: -4px;
+ padding-left: 20px;
+}
+
+.headings-container a.active,
+.headings-container a:hover {
+ color: #2679af;
+}
+
+.page-container {
+ max-width: calc(100% - 220px);
+ min-width: 320px;
+ padding-left: 16px;
+ padding-right: 16px;
+ font-size: 15px;
+ flex-grow: 1;
+}
+
+.page-container h1 {
+ font-size: 26px;
+ font-weight: 700;
+}
+
+.page-container h2 {
+ font-size: 21px;
+ font-weight: 700;
+}
+
+.page-container h3 {
+ font-size: 18px;
+ font-weight: 700;
+}
+
+.page-container h2,
+.page-container h3 {
+ margin-top: 32px;
+ margin-bottom: 14px;
+}
+
+.page-container p {
+ margin-bottom: 14px;
+}
+
+.page-container ul {
+ margin-bottom: 14px;
+ padding-left: 16px;
+}
+
+.page-container li {
+ margin-bottom: 6px;
+}
+
+.page-container li p {
+ margin-bottom: 0;
+}
+
+.page-container li ul {
+ margin-top: 8px;
+}
+
+.page-container pre {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+.page-footer {
+ border-top: 1px solid #ced2d6;
+ font-size: 13px;
+ margin: 0 auto;
+ margin: 10px 10px 0 10px;
+ padding: 12px 0;
+}
+.page-footer img {
+ float: right;
+ margin-left: 10px;
+}
+
+.version-select {
+ position: relative;
+ font-size: 19px;
+ margin-top: 12px;
+ max-width: calc(100% - 160px);
+ text-align: right;
+ float: right;
+ border-radius: 4px;
+}
+
+.version-select > button {
+ padding: 2px 4px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ outline: none;
+}
+
+.version-select:hover {
+ background: #e8eff5;
+}
+
+.version-select ul {
+ z-index: 1000;
+ position: absolute;
+ display: block;
+ top: 100%;
+ margin-top: 4px;
+ right: 0;
+ border: 1px solid #cfd3d7;
+ background: white;
+ text-align: left;
+ border-radius: 4px;
+}
+
+.version-select ul li {
+ list-style: none;
+ font-size: 15px;
+ margin: 0;
+ padding: 4px 16px;
+ text-align: center;
+ transition: all 0.2s ease;
+}
+
+.version-select ul li:hover {
+ background: #f3f3f3;
+}
+
+.version-select ul li span {
+ color: #b58a13;
+ font-weight: 700;
+}
+
+.version-select ul li span.current {
+ color: #499cd2;
+}
+
+.version-select ul li a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+.version-select span {
+ color: #b58a13;
+ font-weight: bold;
+ padding-right: 4px;
+}
+
+.version-select span.current {
+ color: #499cd2;
+}
+
+.targetted-heading {
+ border-left: 4px solid #499cd2;
+ margin-left: -10px;
+ padding-left: 6px;
+}
+
.alert {
display: block;
margin: 0 -1em 1.5rem;
padding: 1em;
- border: 1px solid #777;
+ border: 1px solid #3e7fb7;
+ background-color: #edf6fc;
+ color: #000;
+ border-radius: 3px;
+}
+
+.alert a {
+ padding-bottom: 1px;
+ text-decoration: none;
+ transition: border-bottom-color 0.2s ease 0s;
+ border-bottom: 1px solid rgba(62, 127, 183, 0.3);
+ color: #3e7fb7;
+ font-weight: bold;
+}
+
+.alert a:hover,
+.alert a:focus {
+ border-bottom: 1px solid #0d476c;
+ color: #0d476c;
}
.alert > p {
@@ -10,51 +463,128 @@
}
.alert-danger {
- border-color: #ebccd1;
- background-color: #f2dede;
- color: #a94442;
+ border-color: #d75a4a;
+ background-color: #fcedec;
+}
+
+.alert-danger a {
+ border-bottom: 1px solid rgba(215, 90, 74, 0.3);
+ color: #d75a4a;
+}
+
+.alert-danger a:hover,
+.alert-danger a:focus {
+ border-bottom: 1px solid #a82f1f;
+ color: #a82f1f;
}
.alert-warning {
- border-color: #faebcc;
- background-color: #fcf8e3;
- color: #8a6d3b;
+ border-color: #c99916;
+ background: #ffefbc url(../images/alerts/danger.svg) no-repeat;
+ background-position: 10px 10px;
+ background-size: 24px;
+ padding-left: 40px;
}
-.alert-info {
- border-color: #bce8f1;
- background-color: #d9edf7;
- color: #666666;
+.alert-warning a {
+ border-bottom: 1px solid rgba(201, 153, 22, 0.3);
+ color: #c99916;
+}
+
+.alert-warning a:hover,
+.alert-warning a:focus {
+ border-bottom: 1px solid #9b7713;
+ color: #9b7713;
}
.alert-success {
- border-color: #d6e9c6;
- background-color: #dff0d8;
- color: #3c763d;
+ border-color: #3ec92c;
+ background-color: #ecfcf2;
+}
+
+.alert-success a {
+ border-bottom: 1px solid rgba(62, 201, 44, 0.3);
+ color: #3ec92c;
+}
+
+.alert-success a:hover,
+.alert-success a:focus {
+ border-bottom: 1px solid #138b04;
+ color: #138b04;
+}
+
+.alert svg {
+ vertical-align: middle;
+ transform: translateY(-1px);
+}
+
+.page-sidebar .alert {
+ margin-top: 16px;
+ font-size: 12px;
+}
+
+.page-container a,
+.page-footer a {
+ text-decoration: none;
+ color: #347cab;
+ border-bottom: 1px solid rgba(52, 124, 171, 0.3);
+ transition: all 0.2s ease 0s;
+}
+
+.page-container a:hover,
+.page-container a:focus,
+.page-footer a:hover,
+.page-footer a:focus {
+ border-bottom: 1px solid #0d476c;
+ color: #0d476c;
+}
+
+.page-container p {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
+ 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
ul > ul {
margin-bottom: 0 !important;
}
+.page-container pre {
+ border: 1px solid #e6e6e6;
+ border-radius: 2px;
+ background-color: #f9f9fb;
+ padding: 12px;
+ margin: 0 0 1.5rem;
+ line-height: 16px;
+}
+
+.page-container pre code {
+ font-size: 12px;
+}
+
.collapse {
border: 1px solid #e6e6e6;
border-radius: 2px;
- background-color: #f3f3f3;
- padding: 8px;
- margin: 0 -1em 1.5rem;
+ background-color: #f9f9fb;
+ padding: 12px;
+ margin: 0 0 1.5rem;
}
.collapse > a:first-child {
background: url(../images/open.svg) no-repeat 0 50%;
padding-left: 20px;
display: block;
- color: #236a97;
+ color: #4c9bd6;
display: block;
cursor: pointer;
margin-bottom: 0.5rem;
font-size: 16px;
text-decoration: none;
+ border-bottom: none;
+ transition: all 0.2s ease 0s;
+}
+
+.collapse > a:first-child:hover {
+ color: #195f8d;
}
.collapse.close > a:first-child {
diff --git a/server/sonar-docs/src/templates/page.js b/server/sonar-docs/src/templates/page.js
index d7f9859a945..75b02494ed3 100644
--- a/server/sonar-docs/src/templates/page.js
+++ b/server/sonar-docs/src/templates/page.js
@@ -19,6 +19,7 @@
*/
import React from 'react';
import Helmet from 'react-helmet';
+import HeaderList from '../layouts/components/HeaderList';
import './page.css';
export default class Page extends React.PureComponent {
@@ -46,19 +47,17 @@ export default class Page extends React.PureComponent {
}
);
- if (
- page.headings &&
- page.headings.length > 0 &&
- page.html.match(/<h[1-9]>Table Of Contents<\/h[1-9]>/i)
- ) {
- htmlWithInclusions = generateTableOfContents(htmlWithInclusions, page.headings);
- }
-
+ const realHeadingsList = removeExtraHeadings(page.html, page.headings);
+ htmlWithInclusions = removeTableOfContents(htmlWithInclusions);
+ htmlWithInclusions = createAnchorForHeadings(htmlWithInclusions, realHeadingsList);
htmlWithInclusions = replaceDynamicLinks(htmlWithInclusions);
return (
<div css={{ paddingTop: 24, paddingBottom: 24 }}>
- <Helmet title={page.frontmatter.title} />
+ <Helmet title={page.frontmatter.title}>
+ <html lang="en" />
+ </Helmet>
+ <HeaderList headers={realHeadingsList} />
<h1>{page.frontmatter.title}</h1>
<div
css={{
@@ -101,39 +100,72 @@ export const query = graphql`
`;
function replaceDynamicLinks(content) {
+ const version = process.env.GATSBY_DOCS_VERSION || '';
+ const usePrefix = process.env.GATSBY_USE_PREFIX === '1';
+ if (usePrefix && version !== '') {
+ content = content.replace(
+ /\<a href="(?!#)(?!http)(.*)"\>(.*)\<\/a\>/gim,
+ `<a href="/${version}$1">$2</a>`
+ );
+ }
+
+ // Make outside link open in a new tab
+ content = content.replace(
+ /\<a href="http(.*)"\>(.*)\<\/a\>/gim,
+ '<a href="http$1" target="_blank">$2</a>'
+ );
+
+ // Add trailing slash to local link
+ content = content.replace(
+ /\<a href="(?!http)(.*)(?!\/)"\>(.*)\<\/a\>/gim,
+ '<a href="$1/">$2</a>'
+ );
+
return content.replace(
- /\<a href="\/#(?:sonarqube|sonarcloud|sonarqube-admin)#.*"\>(.*)\<\/a\>/gim,
- '$1'
+ /\<a href="(.*)\/#(?:sonarqube|sonarcloud|sonarqube-admin)#.*"\>(.*)\<\/a\>/gim,
+ '$2'
);
}
-function generateTableOfContents(content, headings) {
- let html = '<h2>Table Of Contents</h2>';
- let depth = headings[0].depth - 1;
- for (let i = 1; i < headings.length; i++) {
- // Do not include title from collapsible content
- if (
- content.match(new RegExp(`\<div class="collapse"\>\<h2\>${headings[i].value}\<\/h2\>`, 'gi'))
- ) {
- continue;
- }
+/**
+ * For the sidebar table of content, we do not want headers for sonarcloud,
+ * collapsable container title, of table of contents headers.
+ */
+function removeExtraHeadings(content, headings) {
+ return headings
+ .filter(heading => content.indexOf(`<div class="collapse"><h2>${heading.value}</h2>`) < 0)
+ .filter(heading => !heading.value.match(/Table of content/i))
+ .filter(heading => {
+ const regex = new RegExp(
+ '<!-- sonarcloud -->[\\s\\S]*<h2>' + heading.value + '<\\/h2>[\\s\\S]*<!-- /sonarcloud -->',
+ 'gim'
+ );
+ return !content.match(regex);
+ });
+}
- while (headings[i].depth > depth) {
- html += '<ul>';
- depth++;
- }
- while (headings[i].depth < depth) {
- html += '</ul>';
- depth--;
+function removeSonarCloudHeadings(content, headings) {
+ return headings.filter(
+ heading => content.indexOf(`<div class="collapse"><h2>${heading.value}</h2>`) < 0
+ );
+}
+
+function createAnchorForHeadings(content, headings) {
+ let counter = 1;
+ headings.map(h => {
+ if (h.depth == 2) {
+ content = content.replace(
+ `<h${h.depth}>${h.value}</h${h.depth}>`,
+ `<h${h.depth} id="header-${counter}">${h.value}</h${h.depth}>`
+ );
+ counter++;
}
- html += `<li><a href="#header-${i}">${headings[i].value}</a></li>`;
- content = content.replace(
- new RegExp(`<h${headings[i].depth}>${headings[i].value}</h${headings[i].depth}>`, 'gi'),
- `<h${headings[i].depth} id="header-${i}">${headings[i].value}</h${headings[i].depth}>`
- );
- }
- html += '</ul>';
- return content.replace(/<h[1-9]>Table Of Contents<\/h[1-9]>/i, html);
+ });
+ return content;
+}
+
+function removeTableOfContents(content) {
+ return content.replace(/<h[1-9]>Table Of Contents<\/h[1-9]>/i, '');
}
function cutSonarCloudContent(content) {
diff --git a/server/sonar-docs/src/utils/typography.js b/server/sonar-docs/src/utils/typography.js
index f439c6fe2e6..8dd671b42b7 100644
--- a/server/sonar-docs/src/utils/typography.js
+++ b/server/sonar-docs/src/utils/typography.js
@@ -24,10 +24,10 @@ const fontFamily = 'Roboto';
const typography = new Typography({
bodyFontFamily: [fontFamily, 'serif'],
headerFontFamily: [fontFamily, 'serif'],
- baseFontSize: '18px',
- bodyWeight: '300',
- headerWeight: '300',
- googleFonts: [{ name: fontFamily, styles: ['300,700'] }],
+ baseFontSize: '15px',
+ bodyWeight: '400',
+ headerWeight: '400',
+ googleFonts: [{ name: fontFamily, styles: ['400,700'] }],
overrideStyles: () => ({
a: {
color: '#439ccd'