aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-docs/src/templates/page.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-docs/src/templates/page.tsx')
-rw-r--r--server/sonar-docs/src/templates/page.tsx187
1 files changed, 187 insertions, 0 deletions
diff --git a/server/sonar-docs/src/templates/page.tsx b/server/sonar-docs/src/templates/page.tsx
new file mode 100644
index 00000000000..c5d2ecd6c11
--- /dev/null
+++ b/server/sonar-docs/src/templates/page.tsx
@@ -0,0 +1,187 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 Helmet from 'react-helmet';
+import { graphql } from 'gatsby';
+import HeaderList from '../components/HeaderList';
+import { MarkdownRemark, MarkdownRemarkConnection, MarkdownHeading } from '../@types/graphql-types';
+
+interface Props {
+ data: {
+ allMarkdownRemark: Pick<MarkdownRemarkConnection, 'edges'>;
+ markdownRemark: Pick<MarkdownRemark, 'html' | 'headings' | 'frontmatter'>;
+ };
+ location: Location;
+}
+
+export default class Page extends React.PureComponent<Props> {
+ baseUrl = '';
+
+ componentDidMount() {
+ if (window) {
+ this.baseUrl = window.location.origin + '/';
+ }
+ const collapsables = document.getElementsByClassName('collapse');
+
+ for (let i = 0; i < collapsables.length; i++) {
+ collapsables[i].classList.add('close');
+ const firstChild = collapsables[i].firstElementChild;
+ if (firstChild) {
+ firstChild.outerHTML = firstChild.outerHTML
+ .replace(/<h2/gi, '<a href="#"')
+ .replace(/<\/h2>/gi, '</a>');
+ firstChild.addEventListener('click', (event: Event & { currentTarget: HTMLElement }) => {
+ event.preventDefault();
+ if (event.currentTarget.parentElement) {
+ event.currentTarget.parentElement.classList.toggle('close');
+ }
+ });
+ }
+ }
+ }
+
+ render() {
+ const page = this.props.data.markdownRemark;
+ const version = process.env.GATSBY_DOCS_VERSION || '';
+ const mainTitle = 'SonarQube Docs';
+ const pageTitle = page.frontmatter && page.frontmatter.title;
+
+ let htmlPageContent = page.html || '';
+
+ const realHeadingsList = removeExtraHeadings(htmlPageContent, page.headings || []);
+
+ htmlPageContent = removeTableOfContents(htmlPageContent);
+ htmlPageContent = createAnchorForHeadings(htmlPageContent, realHeadingsList);
+ htmlPageContent = replaceDynamicLinks(htmlPageContent);
+ htmlPageContent = replaceImageLinks(htmlPageContent);
+ htmlPageContent = replaceInstanceTag(htmlPageContent);
+
+ return (
+ <>
+ <Helmet title={pageTitle ? `${pageTitle} | ${mainTitle}` : mainTitle}>
+ <html lang="en" />
+ <link href={`/${version}/favicon.ico`} rel="icon" />
+ <link
+ href={this.baseUrl + this.props.location.pathname.replace(version, 'latest')}
+ rel="canonical"
+ />
+ <script type="text/javascript">{`
+ (function(window,document) {
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window, document,'script','https://www.google-analytics.com/analytics.js','ga');
+ ga('create', 'UA-1880045-11' , 'auto');
+ ga('send', 'pageview');
+ })(window,document);
+ `}</script>
+ </Helmet>
+ <HeaderList headers={realHeadingsList} />
+ <h1>{pageTitle || mainTitle}</h1>
+ <div className="markdown-content" dangerouslySetInnerHTML={{ __html: htmlPageContent }} />
+ </>
+ );
+ }
+}
+
+export const query = graphql`
+ query($slug: String!) {
+ allMarkdownRemark {
+ edges {
+ node {
+ html
+ fields {
+ slug
+ }
+ }
+ }
+ }
+ markdownRemark(fields: { slug: { eq: $slug } }) {
+ html
+ headings {
+ depth
+ value
+ }
+ frontmatter {
+ title
+ }
+ }
+ }
+`;
+
+function replaceInstanceTag(content: string) {
+ return content.replace(/{instance}/gi, 'SonarQube');
+}
+
+function replaceImageLinks(content: string) {
+ const version = process.env.GATSBY_DOCS_VERSION || '';
+ if (version !== '') {
+ content = content.replace(/<img src="\/images\/(.*)"/gim, `<img src="/${version}/images/$1"`);
+ }
+ return content;
+}
+
+function replaceDynamicLinks(content: string) {
+ // Make outside link open in a new tab
+ content = content.replace(
+ /<a href="http(.*)">(.*)<\/a>/gim,
+ '<a href="http$1" target="_blank">$2</a>'
+ );
+
+ return content.replace(
+ /<a href="(.*)\/#(?:sonarqube|sonarcloud|sonarqube-admin)#.*">(.*)<\/a>/gim,
+ '$2'
+ );
+}
+
+/**
+ * For the sidebar table of content, we do not want headers for sonarcloud,
+ * collapsable container title, of table of contents headers.
+ */
+function removeExtraHeadings(content: string, headings: MarkdownHeading[]) {
+ return headings
+ .filter(heading => content.indexOf(`<div class="collapse"><h2>${heading.value}</h2>`) < 0)
+ .filter(heading => !heading.value || !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);
+ });
+}
+
+function createAnchorForHeadings(content: string, headings: MarkdownHeading[]) {
+ let counter = 1;
+ headings.forEach(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++;
+ }
+ });
+ return content;
+}
+
+function removeTableOfContents(content: string) {
+ return content.replace(/<h[1-9]>Table Of Contents<\/h[1-9]>/i, '');
+}