- );
+ return <>{this.getMenuEntriesHierarchy().map(entry => this.renderEntry(entry, 0))}>;
}
}
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx b/server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx
new file mode 100644
index 00000000000..613c390d788
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx
@@ -0,0 +1,97 @@
+/*
+ * 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 classNames from 'classnames';
+import { Link } from 'react-router';
+import { highlightMarks, cutWords, DocumentationEntry } from '../utils';
+
+export interface SearchResult {
+ page: DocumentationEntry;
+ highlights: { [field: string]: [number, number][] };
+}
+
+interface Props {
+ active: boolean;
+ result: SearchResult;
+}
+
+export default function SearchResultEntry({ active, result }: Props) {
+ return (
+
+
+
+
+ );
+}
+
+export function SearchResultTitle({ result }: { result: SearchResult }) {
+ let titleWithMarks: React.ReactNode;
+
+ 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 = ;
+ } else {
+ titleWithMarks = result.page.title;
+ }
+
+ return (
+
+ );
+ } else {
+ return null;
+ }
+}
+
+export function SearchResultTokens({
+ tokens
+}: {
+ tokens: Array<{ text: string; marked: boolean }>;
+}) {
+ return (
+ <>
+ {tokens.map((token, index) => (
+
+ {token.marked ? {token.text} : token.text}
+
+ ))}
+ >
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/SearchResults.tsx b/server/sonar-web/src/main/js/apps/documentation/components/SearchResults.tsx
new file mode 100644
index 00000000000..30cfdc9fd94
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/SearchResults.tsx
@@ -0,0 +1,78 @@
+/*
+ * 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 lunr, { LunrIndex } from 'lunr';
+import SearchResultEntry, { SearchResult } from './SearchResultEntry';
+import { DocumentationEntry } from '../utils';
+
+interface Props {
+ pages: DocumentationEntry[];
+ query: string;
+ splat: string;
+}
+
+export default class SearchResults extends React.PureComponent {
+ index: LunrIndex;
+
+ constructor(props: Props) {
+ super(props);
+ this.index = lunr(function() {
+ this.ref('relativeName');
+ this.field('title', { boost: 10 });
+ this.field('text');
+
+ this.metadataWhitelist = ['position'];
+
+ props.pages.forEach(page => this.add(page));
+ });
+ }
+
+ render() {
+ const { query } = this.props;
+ const results = this.index
+ .search(`${query}~1 ${query}*`)
+ .map(match => {
+ const page = this.props.pages.find(page => page.relativeName === match.ref);
+ const highlights: { [field: string]: [number, number][] } = {};
+
+ Object.keys(match.matchData.metadata).forEach(term => {
+ Object.keys(match.matchData.metadata[term]).forEach(fieldName => {
+ const { position: positions } = match.matchData.metadata[term][fieldName];
+ highlights[fieldName] = [...(highlights[fieldName] || []), ...positions];
+ });
+ });
+
+ return { page, highlights };
+ })
+ .filter(result => result.page) as SearchResult[];
+
+ return (
+ <>
+ {results.map(result => (
+
+ ))}
+ >
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/Sidebar.tsx b/server/sonar-web/src/main/js/apps/documentation/components/Sidebar.tsx
new file mode 100644
index 00000000000..24a9a6feb8b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/Sidebar.tsx
@@ -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 Menu from './Menu';
+import SearchResults from './SearchResults';
+import { DocumentationEntry } from '../utils';
+import SearchBox from '../../../components/controls/SearchBox';
+
+interface Props {
+ pages: DocumentationEntry[];
+ splat: string;
+}
+
+interface State {
+ query: string;
+}
+
+export default class Sidebar extends React.PureComponent {
+ state: State = { query: '' };
+
+ handleSearch = (query: string) => {
+ this.setState({ query });
+ };
+
+ render() {
+ return (
+ <>
+
+
+
+ {this.state.query ? (
+
+ ) : (
+
+ )}
+
+
+ >
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Menu-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Menu-test.tsx
new file mode 100644
index 00000000000..2f71253a2d3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Menu-test.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import Menu from '../Menu';
+
+function createPage(title: string, relativeName: string, text = '') {
+ return { relativeName, title, order: -1, text, content: text };
+}
+
+const pages = [
+ createPage(
+ 'Lorem Ipsum',
+ 'lorem/index',
+ "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
+ ),
+ createPage(
+ 'Where does it come from?',
+ 'lorem/origin',
+ 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words.'
+ ),
+ createPage(
+ 'Where does Foobar come from?',
+ 'foobar',
+ 'Foobar is a universal variable understood to represent whatever is being discussed.'
+ )
+];
+
+it('should render hierarchical menu', () => {
+ expect(shallow()).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResultEntry-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResultEntry-test.tsx
new file mode 100644
index 00000000000..370c39b376f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResultEntry-test.tsx
@@ -0,0 +1,85 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import SearchResultEntry, {
+ SearchResultText,
+ SearchResultTitle,
+ SearchResultTokens
+} from '../SearchResultEntry';
+
+const page = {
+ content: '',
+ order: -1,
+ relativeName: 'foo/bar',
+ text: 'Foobar is a universal variable understood to represent whatever is being discussed.',
+ title: 'Foobar'
+};
+
+describe('SearchResultEntry', () => {
+ it('should render', () => {
+ expect(
+ shallow()
+ ).toMatchSnapshot();
+ });
+});
+
+describe('SearchResultText', () => {
+ it('should render with highlights', () => {
+ expect(
+ shallow()
+ ).toMatchSnapshot();
+ });
+
+ it('should render without highlights', () => {
+ expect(shallow()).toMatchSnapshot();
+ });
+});
+
+describe('SearchResultTitle', () => {
+ it('should render with highlights', () => {
+ expect(
+ shallow()
+ ).toMatchSnapshot();
+ });
+
+ it('should render not without highlights', () => {
+ expect(shallow()).toMatchSnapshot();
+ });
+});
+
+describe('SearchResultTokens', () => {
+ it('should render', () => {
+ expect(
+ shallow(
+
+ )
+ ).toMatchSnapshot();
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResults-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResults-test.tsx
new file mode 100644
index 00000000000..d4151e45220
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResults-test.tsx
@@ -0,0 +1,66 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import lunr from 'lunr';
+import SearchResults from '../SearchResults';
+
+jest.mock('lunr', () => ({
+ default: jest.fn(() => ({
+ search: jest.fn(() => [
+ {
+ ref: 'lorem/origin',
+ matchData: {
+ metadata: { from: { title: { position: [[19, 5]] }, text: { position: [[121, 4]] } } }
+ }
+ },
+ { ref: 'foobar', matchData: { metadata: { from: { title: { position: [[23, 4]] } } } } }
+ ])
+ }))
+}));
+
+function createPage(title: string, relativeName: string, text = '') {
+ return { relativeName, title, order: -1, text, content: text };
+}
+
+const pages = [
+ createPage(
+ 'Lorem Ipsum',
+ 'lorem/index',
+ "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
+ ),
+ createPage(
+ 'Where does it come from?',
+ 'lorem/origin',
+ 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words.'
+ ),
+ createPage(
+ 'Where does Foobar come from?',
+ 'foobar',
+ 'Foobar is a universal variable understood to represent whatever is being discussed.'
+ )
+];
+
+it('should search', () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+ expect(lunr).toBeCalled();
+ expect((wrapper.instance() as SearchResults).index.search).toBeCalledWith('from~1 from*');
+});
diff --git a/server/sonar-web/config/documentation-loader/fetch-matter.js b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Sidebar-test.tsx
similarity index 51%
rename from server/sonar-web/config/documentation-loader/fetch-matter.js
rename to server/sonar-web/src/main/js/apps/documentation/components/__tests__/Sidebar-test.tsx
index 806332edb2a..6fc596b466f 100644
--- a/server/sonar-web/config/documentation-loader/fetch-matter.js
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Sidebar-test.tsx
@@ -17,29 +17,26 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-const fs = require('fs');
-const path = require('path');
-const { getFrontMatter } = require('../../src/main/js/helpers/markdown');
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Sidebar from '../Sidebar';
-const compare = (a, b) => {
- if (a.order === b.order) return a.title.localeCompare(b.title);
- if (a.order === -1) return 1;
- if (b.order === -1) return -1;
- return a.order - b.order;
-};
+function createPage(title: string, relativeName: string, text = '') {
+ return { relativeName, title, order: -1, text, content: text };
+}
-module.exports = (root, files) => {
- return files
- .map(file => {
- const content = fs.readFileSync(root + '/' + file, 'utf8');
- const headerData = getFrontMatter(content);
- return {
- name: path.basename(file).slice(0, -3),
- relativeName: file.slice(0, -3),
- title: headerData.title || file,
- order: headerData.order || -1,
- scope: headerData.scope && headerData.scope.toLowerCase()
- };
- })
- .sort(compare);
-};
+const pages = [
+ createPage('Lorem Ipsum', 'lorem/index'),
+ createPage('Where does Foobar come from?', 'foobar')
+];
+
+it('should render menu', () => {
+ expect(shallow()).toMatchSnapshot();
+});
+
+it('should search', () => {
+ const wrapper = shallow();
+ wrapper.find('SearchBox').prop('onChange')('foo');
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Menu-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Menu-test.tsx.snap
new file mode 100644
index 00000000000..dbc7ad1e5a0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Menu-test.tsx.snap
@@ -0,0 +1,70 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render hierarchical menu 1`] = `
+
+
+
+
+
+ Lorem Ipsum
+
+
+
+
+
+ Where does it come from?
+
+
+
+
+
+
+
+ Where does Foobar come from?
+
+
+
+
+`;
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap
new file mode 100644
index 00000000000..7a2b86c3590
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap
@@ -0,0 +1,125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SearchResultEntry should render 1`] = `
+
+
+
+
+`;
+
+exports[`SearchResultText should render with highlights 1`] = `
+
+
+
+`;
+
+exports[`SearchResultText should render without highlights 1`] = `""`;
+
+exports[`SearchResultTitle should render not without highlights 1`] = `
+
+ Foobar
+
+`;
+
+exports[`SearchResultTitle should render with highlights 1`] = `
+
+
+
+`;
+
+exports[`SearchResultTokens should render 1`] = `
+
+
+ Foobar is a
+
+
+
+ universal
+
+
+
+ variable understood to represent whatever is being discussed.
+
+
+`;
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResults-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResults-test.tsx.snap
new file mode 100644
index 00000000000..69473caa244
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResults-test.tsx.snap
@@ -0,0 +1,58 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should search 1`] = `
+
+
+
+
+`;
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Sidebar-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Sidebar-test.tsx.snap
new file mode 100644
index 00000000000..c1527eb3f9c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Sidebar-test.tsx.snap
@@ -0,0 +1,84 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render menu 1`] = `
+
+
+
+
+
+
+
+
+`;
+
+exports[`should search 1`] = `
+
+
+
+
+
+
+
+
+`;
diff --git a/server/sonar-web/src/main/js/apps/documentation/pages.ts b/server/sonar-web/src/main/js/apps/documentation/pages.ts
new file mode 100644
index 00000000000..cb4eb61c9c1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/documentation/pages.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 remark from 'remark';
+import strip from 'strip-markdown';
+import { DocumentationEntry } from './utils';
+import * as Docs from './documentation.directory-loader';
+import { separateFrontMatter, filterContent } from '../../helpers/markdown';
+import { isSonarCloud } from '../../helpers/system';
+
+export default function getPages(): DocumentationEntry[] {
+ return Docs.map((file: any) => {
+ const parsed = separateFrontMatter(file.content);
+ const content = filterContent(parsed.content);
+
+ const text = remark()
+ .use(strip)
+ .processSync(content)
+ .contents.replace(/\n+/, ' ')
+ .trim();
+
+ return {
+ relativeName: file.path,
+ title: parsed.frontmatter.title,
+ order: Number(parsed.frontmatter.order || -1),
+ scope:
+ parsed.frontmatter.scope && parsed.frontmatter.scope.toLowerCase() === 'sonarcloud'
+ ? ('sonarcloud' as 'sonarcloud')
+ : undefined,
+ text,
+ content: file.content
+ };
+ }).filter((page: DocumentationEntry) => isSonarCloud() || page.scope !== 'sonarcloud');
+}
diff --git a/server/sonar-web/src/main/js/apps/documentation/routes.ts b/server/sonar-web/src/main/js/apps/documentation/routes.ts
index 704e4739073..617422a5ce5 100644
--- a/server/sonar-web/src/main/js/apps/documentation/routes.ts
+++ b/server/sonar-web/src/main/js/apps/documentation/routes.ts
@@ -19,14 +19,8 @@
*/
import { lazyLoad } from '../../components/lazyLoad';
-const routes = [
- {
- indexRoute: { component: lazyLoad(() => import('./components/App')) }
- },
- {
- path: '**',
- indexRoute: { component: lazyLoad(() => import('./components/App')) }
- }
-];
+const App = lazyLoad(() => import('./components/App'));
+
+const routes = [{ indexRoute: { component: App } }, { path: '**', indexRoute: { component: App } }];
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/documentation/utils.ts b/server/sonar-web/src/main/js/apps/documentation/utils.ts
index 114c670ee6e..854f09bce68 100644
--- a/server/sonar-web/src/main/js/apps/documentation/utils.ts
+++ b/server/sonar-web/src/main/js/apps/documentation/utils.ts
@@ -17,12 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { sortBy } from 'lodash';
+
export interface DocumentationEntry {
- children: DocumentationEntry[];
- name: string;
- order: string;
+ content: string;
+ order: number;
relativeName: string;
scope?: 'sonarcloud';
+ text: string;
title: string;
}
@@ -50,3 +52,96 @@ export function getEntryChildren(entries: DocumentationEntry[], root?: string) {
);
});
}
+
+const WORDS = 6;
+
+function cutLeadingWords(str: string) {
+ 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: string) {
+ 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: Array<{ text: string; marked: boolean }>) {
+ const result: Array<{ text: string; marked: boolean }> = [];
+ 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: string, marks: Array<{ from: number; to: number }>) {
+ 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: Array<{ text: string; marked: boolean }> = [];
+ 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-web/src/main/js/apps/marketplace/components/EditionBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
index 699d99a114c..1d59ca8d8e6 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
@@ -18,9 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import tooltipDCE from 'Docs/tooltips/editions/datacenter.md';
+import tooltipDE from 'Docs/tooltips/editions/developer.md';
+import tooltipEE from 'Docs/tooltips/editions/enterprise.md';
import { Edition, getEditionUrl, EditionKey } from '../utils';
-import DocInclude from '../../../components/docs/DocInclude';
import { translate } from '../../../helpers/l10n';
+import { lazyLoad } from '../../../components/lazyLoad';
+
+const DocMarkdownBlock = lazyLoad(() => import('../../../components/docs/DocMarkdownBlock'));
interface Props {
currentEdition?: EditionKey;
@@ -32,7 +37,9 @@ interface Props {
export default function EditionBox({ edition, ncloc, serverId, currentEdition }: Props) {
return (