"extends": "sonarqube",
"rules": {
- "import/extensions": ["error", "never", { "json": "always" }]
+ "import/extensions": ["error", "never", { "json": "always", "md": "always" }]
}
}
<PROJECT_ROOT>/node_modules/webassemblyjs
<PROJECT_ROOT>/node/.*
<PROJECT_ROOT>/.vscode/.*
+<PROJECT_ROOT>/src/main/js/apps/overview/qualityGate/QualityGate.js
[include]
+++ /dev/null
-/*
- * 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.
- */
-const fs = require('fs');
-const path = require('path');
-const { getFrontMatter } = require('../../src/main/js/helpers/markdown');
-
-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;
-};
-
-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);
-};
* 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 parseDirectory = require('./parse-directory');
-const fetchMatter = require('./fetch-matter');
+const glob = require('glob-promise');
module.exports = function(source) {
this.cacheable();
const root = path.resolve(path.dirname(this.resourcePath), config.root);
this.addContextDependency(root);
- parseDirectory(root)
- .then(files => fetchMatter(root, files))
+ glob(root + '/**/*.md')
+ .then(files => files.map(file => file.substr(root.length + 1)))
+ .then(files =>
+ files.map(file => ({
+ path: file.slice(0, -3),
+ content: handleIncludes(fs.readFileSync(root + '/' + file, 'utf8'), root)
+ }))
+ )
.then(result => `module.exports = ${JSON.stringify(result)};`)
.then(success)
.catch(failure);
};
+
+/**
+ * @param {string} content
+ * @param {string} root
+ * @returns {string}
+ */
+function handleIncludes(content, root) {
+ return content.replace(/@include (.+)/, (match, p) => {
+ const filePath = path.join(root, '..', `${p}.md`);
+ return fs.readFileSync(filePath, 'utf8');
+ });
+}
+++ /dev/null
-/*
- * 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.
- */
-const glob = require('glob-promise');
-
-module.exports = root => {
- return glob(root + '/**/*.md').then(files => files.map(file => file.substr(root.length + 1)));
-};
"intl-relativeformat": "2.1.0",
"keymaster": "1.6.2",
"lodash": "4.17.10",
+ "lunr": "2.3.0",
"prop-types": "15.6.1",
"react": "16.2.0",
"react-day-picker": "7.1.8",
"react-test-renderer": "16.2.0",
"remark": "9.0.0",
"remark-react": "4.0.3",
+ "strip-markdown": "3.0.1",
"style-loader": "0.21.0",
"ts-jest": "22.4.6",
"ts-loader": "4.3.0",
"coveragePathIgnorePatterns": ["<rootDir>/node_modules", "<rootDir>/tests"],
"moduleFileExtensions": ["ts", "tsx", "js", "json"],
"moduleNameMapper": {
- "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
+ "^.+\\.(md|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/config/jest/FileStub.js",
"^.+\\.css$": "<rootDir>/config/jest/CSSStub.js"
},
--- /dev/null
+/*
+ * 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.
+ */
+declare module 'lunr' {
+ export interface Lunr {
+ add(doc: any): void;
+
+ field(field: string, options?: { boost?: number }): void;
+
+ ref(field: string): void;
+
+ metadataWhitelist?: string[];
+ }
+
+ export interface LunrInit {
+ (this: Lunr): void;
+ }
+
+ export interface LunrMatch {
+ ref: string;
+ score: number;
+ matchData: { metadata: any };
+ }
+
+ export interface LunrIndex {
+ search(query: string): LunrMatch[];
+ }
+
+ function lunr(initializer: LunrInit): LunrIndex;
+
+ export default lunr;
+}
--- /dev/null
+/*
+ * 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.
+ */
+declare module '*.md' {
+ const value: string;
+ export default value;
+}
--- /dev/null
+/*
+ * 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.
+ */
+declare module 'strip-markdown' {
+ export default function stripMarkdown(): any;
+}
fill={theme.gray80}
/>
<span className="note">{displayName}</span>
- <DocTooltip className="spacer-left" doc="branches/no-branch-support">
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/branches/no-branch-support.md')}>
<PlusCircleIcon fill={theme.gray71} size={12} />
</DocTooltip>
</div>
<div className="navbar-context-branches">
<BranchIcon branchLike={currentBranchLike} className="little-spacer-right" />
<span className="note">{displayName}</span>
- <DocTooltip className="spacer-left" doc="branches/single-branch">
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/branches/single-branch.md')}>
<PlusCircleIcon fill={theme.blue} size={12} />
</DocTooltip>
</div>
exports[`renders no branch support popup 1`] = `
<DocTooltip
className="spacer-left"
- doc="branches/no-branch-support"
+ doc={Promise {}}
>
<PlusCircleIcon
fill="#b4b4b4"
exports[`renders single branch popup 1`] = `
<DocTooltip
className="spacer-left"
- doc="branches/single-branch"
+ doc={Promise {}}
>
<PlusCircleIcon
fill="#4b9fd5"
mark {
background: none;
+ color: var(--baseFontColor);
font-weight: bold;
}
<header className="modal-head">
<h2>
{translate('my_account.create_organization')}
- <DocTooltip className="spacer-left" doc="organizations/organization" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/organization.md')}
+ />
</h2>
</header>
onClick={this.handleHeaderClick}
open={this.props.open}
values={this.getTextValue()}>
- <DocTooltip className="spacer-left" doc="rules/rules-quality-profiles" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/rules/rules-quality-profiles.md')}
+ />
</FacetHeader>
{this.props.open && <FacetItemsList>{profiles.map(this.renderItem)}</FacetItemsList>}
onClick={onClick}>
{translate('delete')}
</Button>
- <DocTooltip className="spacer-left" doc="rules/custom-rule-removal" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/rules/custom-rule-removal.md')}
+ />
</>
)}
</ConfirmButton>
{translate('coding_rules.show_template')}
</Link>
{')'}
- <DocTooltip className="little-spacer-left" doc="rules/custom-rules" />
+ <DocTooltip
+ className="little-spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/rules/custom-rules.md')}
+ />
</li>
);
};
renderTextName={this.renderName}
singleSelection={true}
values={value !== undefined ? [String(value)] : []}>
- <DocTooltip className="spacer-left" doc="rules/rule-templates" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/rules/rule-templates.md')}
+ />
</Facet>
);
}
import * as React from 'react';
import Helmet from 'react-helmet';
import { Link } from 'react-router';
-import Menu from './Menu';
+import Sidebar from './Sidebar';
+import getPages from '../pages';
import NotFound from '../../../app/components/NotFound';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
-import { getFrontMatter } from '../../../helpers/markdown';
import { isSonarCloud } from '../../../helpers/system';
import '../styles.css';
params: { splat?: string };
}
-interface State {
- content?: string;
- loading: boolean;
- notFound: boolean;
-}
-
-export default class App extends React.PureComponent<Props, State> {
+export default class App extends React.PureComponent<Props> {
mounted = false;
-
- state: State = { loading: false, notFound: false };
+ pages = getPages();
componentDidMount() {
- this.mounted = true;
- this.fetchContent(this.props.params.splat || 'index');
-
const footer = document.getElementById('footer');
if (footer) {
footer.classList.add('page-footer-with-sidebar', 'documentation-footer');
}
}
- componentWillReceiveProps(nextProps: Props) {
- const newSplat = nextProps.params.splat || 'index';
- if (newSplat !== this.props.params.splat) {
- this.setState({ content: undefined });
- this.fetchContent(newSplat);
- }
- }
-
componentWillUnmount() {
- this.mounted = false;
-
const footer = document.getElementById('footer');
if (footer) {
footer.classList.remove('page-footer-with-sidebar', 'documentation-footer');
}
}
- fetchContent = (path: string) => {
- this.setState({ loading: true });
- import(`Docs/pages/${path === '' ? 'index' : path}.md`).then(
- ({ default: content }) => {
- if (this.mounted) {
- const { scope } = getFrontMatter(content || '');
- if (scope === 'sonarcloud' && !isSonarCloud()) {
- this.setState({ loading: false, notFound: true });
- } else {
- this.setState({ content, loading: false, notFound: false });
- }
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false, notFound: true });
- }
- }
- );
- };
+ render() {
+ const { splat = 'index' } = this.props.params;
+ const page = this.pages.find(p => p.relativeName === splat);
+ const mainTitle = translate('documentation.page');
- renderContent() {
- if (this.state.notFound) {
- return <NotFound withContainer={false} />;
+ if (!page) {
+ return (
+ <>
+ <Helmet title={mainTitle}>
+ <meta content="noindex nofollow" name="robots" />
+ </Helmet>
+ <NotFound withContainer={false} />
+ </>
+ );
}
- return (
- <div className="boxed-group">
- <DocMarkdownBlock
- className="documentation-content cut-margins boxed-group-inner"
- content={this.state.content}
- displayH1={true}
- />
- </div>
- );
- }
+ const isIndex = splat === 'index';
- render() {
- const pageTitle = getFrontMatter(this.state.content || '').title;
- const mainTitle = translate('documentation.page');
- const isIndex = !this.props.params.splat || this.props.params.splat === '';
return (
<div className="layout-page">
- <Helmet title={isIndex || this.state.notFound ? mainTitle : `${pageTitle} - ${mainTitle}`}>
+ <Helmet title={isIndex ? mainTitle : `${page.title} - ${mainTitle}`}>
{!isSonarCloud() && <meta content="noindex nofollow" name="robots" />}
</Helmet>
+
<ScreenPositionHelper className="layout-page-side-outer">
{({ top }) => (
<div className="layout-page-side" style={{ top }}>
<h1>{translate('documentation.page')}</h1>
</Link>
</div>
- <Menu splat={this.props.params.splat} />
+ <Sidebar pages={this.pages} splat={splat} />
</div>
</div>
</div>
<div className="layout-page-main">
<div className="layout-page-main-inner documentation-layout-inner">
- <DeferredSpinner loading={this.state.loading}>{this.renderContent()}</DeferredSpinner>
+ <div className="boxed-group">
+ <DocMarkdownBlock
+ className="documentation-content cut-margins boxed-group-inner"
+ content={page.content}
+ displayH1={true}
+ />
+ </div>
</div>
</div>
</div>
import * as React from 'react';
import { Link } from 'react-router';
import * as classNames from 'classnames';
-import OpenCloseIcon from '../../../components/icons-components/OpenCloseIcon';
+import { sortBy } from 'lodash';
import {
- activeOrChildrenActive,
- DocumentationEntry,
getEntryChildren,
+ DocumentationEntry,
+ activeOrChildrenActive,
getEntryRoot
} from '../utils';
-import * as Docs from '../documentation.directory-loader';
-import { isSonarCloud } from '../../../helpers/system';
-
-const pages = (Docs as any) as DocumentationEntry[];
+import OpenCloseIcon from '../../../components/icons-components/OpenCloseIcon';
interface Props {
- splat?: string;
+ pages: DocumentationEntry[];
+ splat: string;
}
+type EntryWithChildren = DocumentationEntry & { children?: DocumentationEntry[] };
+
export default class Menu extends React.PureComponent<Props> {
- getMenuEntriesHierarchy = (root?: string): Array<DocumentationEntry> => {
- const instancePages = isSonarCloud()
- ? pages
- : pages.filter(page => page.scope !== 'sonarcloud');
- const toplevelEntries = getEntryChildren(instancePages, root);
- toplevelEntries.forEach(entry => {
- const entryRoot = getEntryRoot(entry.relativeName);
- entry.children = entryRoot !== '' ? this.getMenuEntriesHierarchy(entryRoot) : [];
- });
- return toplevelEntries.sort((a, b) => parseInt(a.order, 10) - parseInt(b.order, 10));
+ getMenuEntriesHierarchy = (root?: string): EntryWithChildren[] => {
+ const topLevelEntries = getEntryChildren(this.props.pages, root);
+ return sortBy(
+ topLevelEntries.map(entry => {
+ const entryRoot = getEntryRoot(entry.relativeName);
+ const children = entryRoot !== '' ? this.getMenuEntriesHierarchy(entryRoot) : [];
+ return { ...entry, children };
+ }),
+ entry => entry.order
+ );
};
- renderEntry = (entry: DocumentationEntry, depth: number): React.ReactNode => {
+ renderEntry = (entry: EntryWithChildren, depth: number): React.ReactNode => {
const active = entry.relativeName === this.props.splat;
const opened = activeOrChildrenActive(this.props.splat || '', entry);
const offset = 10 + 25 * depth;
+ const { children = [] } = entry;
return (
<React.Fragment key={entry.relativeName}>
<Link
style={{ paddingLeft: offset }}
to={'/documentation/' + entry.relativeName}>
<h3 className="list-group-item-heading">
- {entry.children.length > 0 && (
- <OpenCloseIcon className="little-spacer-right" open={opened} />
- )}
+ {children.length > 0 && <OpenCloseIcon className="little-spacer-right" open={opened} />}
{entry.title}
</h3>
</Link>
- {opened && entry.children.map(entry => this.renderEntry(entry, depth + 1))}
+ {opened && children.map(entry => this.renderEntry(entry, depth + 1))}
</React.Fragment>
);
};
render() {
- return (
- <div className="api-documentation-results panel">
- <div className="list-group">
- {this.getMenuEntriesHierarchy().map(entry => this.renderEntry(entry, 0))}
- </div>
- </div>
- );
+ return <>{this.getMenuEntriesHierarchy().map(entry => this.renderEntry(entry, 0))}</>;
}
}
--- /dev/null
+/*
+ * 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 (
+ <Link
+ className={classNames('list-group-item', { active })}
+ to={'/documentation/' + result.page.relativeName}>
+ <SearchResultTitle result={result} />
+ <SearchResultText result={result} />
+ </Link>
+ );
+}
+
+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 = <SearchResultTokens tokens={tokens} />;
+ } else {
+ titleWithMarks = result.page.title;
+ }
+
+ return (
+ <h3 className="list-group-item-heading" style={{ fontWeight: 'normal' }}>
+ {titleWithMarks}
+ </h3>
+ );
+}
+
+export function SearchResultText({ result }: { result: SearchResult }) {
+ 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
+}: {
+ tokens: Array<{ text: string; marked: boolean }>;
+}) {
+ return (
+ <>
+ {tokens.map((token, index) => (
+ <React.Fragment key={index}>
+ {token.marked ? <mark key={index}>{token.text}</mark> : token.text}
+ </React.Fragment>
+ ))}
+ </>
+ );
+}
--- /dev/null
+/*
+ * 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<Props> {
+ 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 => (
+ <SearchResultEntry
+ active={result.page.relativeName === this.props.splat}
+ key={result.page.relativeName}
+ result={result}
+ />
+ ))}
+ </>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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<Props, State> {
+ state: State = { query: '' };
+
+ handleSearch = (query: string) => {
+ this.setState({ query });
+ };
+
+ render() {
+ return (
+ <>
+ <SearchBox
+ className="big-spacer-top spacer-bottom"
+ minLength={2}
+ onChange={this.handleSearch}
+ placeholder="Search for pages or keywords"
+ value={this.state.query}
+ />
+ <div className="api-documentation-results panel">
+ <div className="list-group">
+ {this.state.query ? (
+ <SearchResults
+ pages={this.props.pages}
+ query={this.state.query}
+ splat={this.props.splat}
+ />
+ ) : (
+ <Menu pages={this.props.pages} splat={this.props.splat} />
+ )}
+ </div>
+ </div>
+ </>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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(<Menu pages={pages} splat="lorem/origin" />)).toMatchSnapshot();
+});
--- /dev/null
+/*
+ * 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(<SearchResultEntry active={true} result={{ page, highlights: {} }} />)
+ ).toMatchSnapshot();
+ });
+});
+
+describe('SearchResultText', () => {
+ it('should render with highlights', () => {
+ expect(
+ shallow(<SearchResultText result={{ page, highlights: { text: [[12, 9]] } }} />)
+ ).toMatchSnapshot();
+ });
+
+ it('should render without highlights', () => {
+ expect(shallow(<SearchResultText result={{ page, highlights: {} }} />)).toMatchSnapshot();
+ });
+});
+
+describe('SearchResultTitle', () => {
+ it('should render with highlights', () => {
+ expect(
+ shallow(<SearchResultTitle result={{ page, highlights: { title: [[0, 6]] } }} />)
+ ).toMatchSnapshot();
+ });
+
+ it('should render not without highlights', () => {
+ expect(shallow(<SearchResultTitle result={{ page, highlights: {} }} />)).toMatchSnapshot();
+ });
+});
+
+describe('SearchResultTokens', () => {
+ it('should render', () => {
+ expect(
+ shallow(
+ <SearchResultTokens
+ tokens={[
+ { marked: false, text: 'Foobar is a ' },
+ { marked: true, text: 'universal' },
+ {
+ marked: false,
+ text: ' variable understood to represent whatever is being discussed.'
+ }
+ ]}
+ />
+ )
+ ).toMatchSnapshot();
+ });
+});
--- /dev/null
+/*
+ * 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(<SearchResults pages={pages} query="from" splat="foobar" />);
+ expect(wrapper).toMatchSnapshot();
+ expect(lunr).toBeCalled();
+ expect((wrapper.instance() as SearchResults).index.search).toBeCalledWith('from~1 from*');
+});
--- /dev/null
+/*
+ * 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 Sidebar from '../Sidebar';
+
+function createPage(title: string, relativeName: string, text = '') {
+ return { relativeName, title, order: -1, text, content: text };
+}
+
+const pages = [
+ createPage('Lorem Ipsum', 'lorem/index'),
+ createPage('Where does Foobar come from?', 'foobar')
+];
+
+it('should render menu', () => {
+ expect(shallow(<Sidebar pages={pages} splat="foobar" />)).toMatchSnapshot();
+});
+
+it('should search', () => {
+ const wrapper = shallow(<Sidebar pages={pages} splat="foobar" />);
+ wrapper.find('SearchBox').prop<Function>('onChange')('foo');
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render hierarchical menu 1`] = `
+<React.Fragment>
+ <React.Fragment
+ key="lorem/index"
+ >
+ <Link
+ className="list-group-item"
+ onlyActiveOnIndex={false}
+ style={
+ Object {
+ "paddingLeft": 10,
+ }
+ }
+ to="/documentation/lorem/index"
+ >
+ <h3
+ className="list-group-item-heading"
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={true}
+ />
+ Lorem Ipsum
+ </h3>
+ </Link>
+ <React.Fragment
+ key="lorem/origin"
+ >
+ <Link
+ className="list-group-item active"
+ onlyActiveOnIndex={false}
+ style={
+ Object {
+ "paddingLeft": 35,
+ }
+ }
+ to="/documentation/lorem/origin"
+ >
+ <h3
+ className="list-group-item-heading"
+ >
+ Where does it come from?
+ </h3>
+ </Link>
+ </React.Fragment>
+ </React.Fragment>
+ <React.Fragment
+ key="foobar"
+ >
+ <Link
+ className="list-group-item"
+ onlyActiveOnIndex={false}
+ style={
+ Object {
+ "paddingLeft": 10,
+ }
+ }
+ to="/documentation/foobar"
+ >
+ <h3
+ className="list-group-item-heading"
+ >
+ Where does Foobar come from?
+ </h3>
+ </Link>
+ </React.Fragment>
+</React.Fragment>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SearchResultEntry should render 1`] = `
+<Link
+ className="list-group-item active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/documentation/foo/bar"
+>
+ <SearchResultTitle
+ result={
+ Object {
+ "highlights": Object {},
+ "page": Object {
+ "content": "",
+ "order": -1,
+ "relativeName": "foo/bar",
+ "text": "Foobar is a universal variable understood to represent whatever is being discussed.",
+ "title": "Foobar",
+ },
+ }
+ }
+ />
+ <SearchResultText
+ result={
+ Object {
+ "highlights": Object {},
+ "page": Object {
+ "content": "",
+ "order": -1,
+ "relativeName": "foo/bar",
+ "text": "Foobar is a universal variable understood to represent whatever is being discussed.",
+ "title": "Foobar",
+ },
+ }
+ }
+ />
+</Link>
+`;
+
+exports[`SearchResultText should render with highlights 1`] = `
+<div
+ className="note"
+>
+ <SearchResultTokens
+ tokens={
+ Array [
+ Object {
+ "marked": false,
+ "text": "Foobar is a ",
+ },
+ Object {
+ "marked": true,
+ "text": "universal",
+ },
+ Object {
+ "marked": false,
+ "text": " variable understood to represent whatever is being discussed.",
+ },
+ ]
+ }
+ />
+</div>
+`;
+
+exports[`SearchResultText should render without highlights 1`] = `""`;
+
+exports[`SearchResultTitle should render not without highlights 1`] = `
+<h3
+ className="list-group-item-heading"
+ style={
+ Object {
+ "fontWeight": "normal",
+ }
+ }
+>
+ Foobar
+</h3>
+`;
+
+exports[`SearchResultTitle should render with highlights 1`] = `
+<h3
+ className="list-group-item-heading"
+ style={
+ Object {
+ "fontWeight": "normal",
+ }
+ }
+>
+ <SearchResultTokens
+ tokens={
+ Array [
+ Object {
+ "marked": true,
+ "text": "Foobar",
+ },
+ ]
+ }
+ />
+</h3>
+`;
+
+exports[`SearchResultTokens should render 1`] = `
+<React.Fragment>
+ <React.Fragment
+ key="0"
+ >
+ Foobar is a
+ </React.Fragment>
+ <React.Fragment
+ key="1"
+ >
+ <mark
+ key="1"
+ >
+ universal
+ </mark>
+ </React.Fragment>
+ <React.Fragment
+ key="2"
+ >
+ variable understood to represent whatever is being discussed.
+ </React.Fragment>
+</React.Fragment>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should search 1`] = `
+<React.Fragment>
+ <SearchResultEntry
+ active={false}
+ key="lorem/origin"
+ result={
+ Object {
+ "highlights": Object {
+ "text": Array [
+ Array [
+ 121,
+ 4,
+ ],
+ ],
+ "title": Array [
+ Array [
+ 19,
+ 5,
+ ],
+ ],
+ },
+ "page": Object {
+ "content": "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.",
+ "order": -1,
+ "relativeName": "lorem/origin",
+ "text": "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.",
+ "title": "Where does it come from?",
+ },
+ }
+ }
+ />
+ <SearchResultEntry
+ active={true}
+ key="foobar"
+ result={
+ Object {
+ "highlights": Object {
+ "title": Array [
+ Array [
+ 23,
+ 4,
+ ],
+ ],
+ },
+ "page": Object {
+ "content": "Foobar is a universal variable understood to represent whatever is being discussed.",
+ "order": -1,
+ "relativeName": "foobar",
+ "text": "Foobar is a universal variable understood to represent whatever is being discussed.",
+ "title": "Where does Foobar come from?",
+ },
+ }
+ }
+ />
+</React.Fragment>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render menu 1`] = `
+<React.Fragment>
+ <SearchBox
+ className="big-spacer-top spacer-bottom"
+ minLength={2}
+ onChange={[Function]}
+ placeholder="Search for pages or keywords"
+ value=""
+ />
+ <div
+ className="api-documentation-results panel"
+ >
+ <div
+ className="list-group"
+ >
+ <Menu
+ pages={
+ Array [
+ Object {
+ "content": "",
+ "order": -1,
+ "relativeName": "lorem/index",
+ "text": "",
+ "title": "Lorem Ipsum",
+ },
+ Object {
+ "content": "",
+ "order": -1,
+ "relativeName": "foobar",
+ "text": "",
+ "title": "Where does Foobar come from?",
+ },
+ ]
+ }
+ splat="foobar"
+ />
+ </div>
+ </div>
+</React.Fragment>
+`;
+
+exports[`should search 1`] = `
+<React.Fragment>
+ <SearchBox
+ className="big-spacer-top spacer-bottom"
+ minLength={2}
+ onChange={[Function]}
+ placeholder="Search for pages or keywords"
+ value="foo"
+ />
+ <div
+ className="api-documentation-results panel"
+ >
+ <div
+ className="list-group"
+ >
+ <SearchResults
+ pages={
+ Array [
+ Object {
+ "content": "",
+ "order": -1,
+ "relativeName": "lorem/index",
+ "text": "",
+ "title": "Lorem Ipsum",
+ },
+ Object {
+ "content": "",
+ "order": -1,
+ "relativeName": "foobar",
+ "text": "",
+ "title": "Where does Foobar come from?",
+ },
+ ]
+ }
+ query="foo"
+ splat="foobar"
+ />
+ </div>
+ </div>
+</React.Fragment>
+`;
--- /dev/null
+/*
+ * 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');
+}
*/
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;
* 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;
}
);
});
}
+
+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;
+}
* 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;
export default function EditionBox({ edition, ncloc, serverId, currentEdition }: Props) {
return (
<div className="boxed-group boxed-group-inner marketplace-edition">
- <DocInclude path={'/tooltips/editions/' + edition.key} />
+ {edition.key === 'datacenter' && <DocMarkdownBlock content={tooltipDCE} />}
+ {edition.key === 'developer' && <DocMarkdownBlock content={tooltipDE} />}
+ {edition.key === 'enterprise' && <DocMarkdownBlock content={tooltipEE} />}
<div className="marketplace-edition-action spacer-top">
<a
href={getEditionUrl(edition, { ncloc, serverId, sourceEdition: currentEdition })}
<div
className="boxed-group boxed-group-inner marketplace-edition"
>
- <DocInclude
- path="/tooltips/editions/developer"
- />
+ <LazyLoader />
<div
className="marketplace-edition-action spacer-top"
>
memberLogins={this.props.memberLogins}
organization={organization}
/>
- <DocTooltip className="spacer-left" doc="organizations/add-organization-member" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/add-organization-member.md')}
+ />
</div>
)}
</MembersPageHeader>
/>
<DocTooltip
className="spacer-left"
- doc="organizations/add-organization-member"
+ doc={Promise {}}
/>
</div>
</MembersPageHeader>
{onSonarCloud &&
isPaidOrganization(organization) &&
hasPrivateAccess(currentUser, organization, userOrganizations) && (
- <DocTooltip className="spacer-right" doc="organizations/subscription-paid-plan">
+ <DocTooltip
+ className="spacer-right"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/subscription-paid-plan.md')}>
<div className="outline-badge">{translate('organization.paid_plan.badge')}</div>
</DocTooltip>
)}
<h2 className="overview-title">
{translate('overview.quality_gate')}
{this.state.loading && <i className="spinner spacer-left" />}
- <DocTooltip className="spacer-left" doc="quality-gates/project-homepage-quality-gate" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/project-homepage-quality-gate.md')}
+ />
{status != null && <Level className="big-spacer-left" level={status} />}
</h2>
<div className="overview-quality-gate" id="overview-quality-gate">
<div className="display-flex-center">
<h2 className="overview-title">{translate('overview.quality_gate')}</h2>
- <DocTooltip className="spacer-left" doc="quality-gates/project-homepage-quality-gate" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/project-homepage-quality-gate.md')}
+ />
<Level className="big-spacer-left" level={level} />
</div>
/>
<DocTooltip
className="spacer-left"
- doc="quality-gates/project-homepage-quality-gate"
+ doc={Promise {}}
/>
</h2>
</div>
overview.quality_gate
<DocTooltip
className="spacer-left"
- doc="quality-gates/project-homepage-quality-gate"
+ doc={Promise {}}
/>
<Level
className="big-spacer-left"
</h2>
<DocTooltip
className="spacer-left"
- doc="quality-gates/project-homepage-quality-gate"
+ doc={Promise {}}
/>
<Level
className="big-spacer-left"
<header className="page-header">
<div className="page-title display-flex-center">
<h1>{translate('project_quality_gate.page')}</h1>
- <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-projects" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/quality-gate-projects.md')}
+ />
</div>
<div className="page-description">{translate('project_quality_gate.page.description')}</div>
</header>
</h1>
<DocTooltip
className="spacer-left"
- doc="quality-gates/quality-gate-projects"
+ doc={Promise {}}
/>
</div>
<div
<header className="page-header">
<div className="page-title display-flex-center">
<h1>{translate('project_quality_profiles.page')}</h1>
- <DocTooltip className="spacer-left" doc="quality-profiles/quality-profile-projects" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/quality-profile-projects.md')}
+ />
</div>
<div className="page-description">
{translate('project_quality_profiles.page.description')}
</h1>
<DocTooltip
className="spacer-left"
- doc="quality-profiles/quality-profile-projects"
+ doc={Promise {}}
/>
</div>
<div
</div>
);
- return <DocTooltip doc="quality-gates/built-in-quality-gate">{badge}</DocTooltip>;
+ return (
+ <DocTooltip
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/built-in-quality-gate.md')}>
+ {badge}
+ </DocTooltip>
+ );
}
)}
<header className="display-flex-center spacer-bottom">
<h3>{translate('quality_gates.conditions')}</h3>
- <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-conditions" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/quality-gate-conditions.md')}
+ />
</header>
<div className="big-spacer-bottom">{translate('quality_gates.introduction')}</div>
<div className="quality-gate-section" id="quality-gate-projects">
<header className="display-flex-center spacer-bottom">
<h3>{translate('quality_gates.projects')}</h3>
- <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-projects" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/quality-gate-projects.md')}
+ />
</header>
{isDefault ? (
translate('quality_gates.projects_for_default')
<div className="display-flex-center">
<h1 className="page-title">{translate('quality_gates.page')}</h1>
- <DocTooltip className="spacer-left" doc="quality-gates/quality-gate" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/quality-gate.md')}
+ />
</div>
</header>
);
);
if (tooltip) {
- return <DocTooltip doc="quality-profiles/built-in-quality-profile">{badge}</DocTooltip>;
+ return (
+ <DocTooltip
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/built-in-quality-profile.md')}>
+ {badge}
+ </DocTooltip>
+ );
}
return badge;
}
{translate('quality_profiles.list.projects')}
<DocTooltip
className="table-cell-doc"
- doc="quality-profiles/quality-profile-projects"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/quality-profile-projects.md')}
/>
</th>
<th className="text-right nowrap">{translate('quality_profiles.list.rules')}</th>
if (profile.isDefault) {
return (
- <DocTooltip doc="quality-profiles/default-quality-profile">
+ <DocTooltip
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/default-quality-profile.md')}>
<span className="badge">{translate('default')}</span>
</DocTooltip>
);
onCheck={this.handleCheck}>
<label className="little-spacer-left" htmlFor={'showCWE'}>
{translate('security_reports.cwe.show')}
- <DocTooltip className="spacer-left" doc="security-reports/cwe" />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/security-reports/cwe.md')}
+ />
</label>
</Checkbox>
</div>
security_reports.cwe.show
<DocTooltip
className="spacer-left"
- doc="security-reports/cwe"
+ doc={Promise {}}
/>
</label>
</Checkbox>
security_reports.cwe.show
<DocTooltip
className="spacer-left"
- doc="security-reports/cwe"
+ doc={Promise {}}
/>
</label>
</Checkbox>
security_reports.cwe.show
<DocTooltip
className="spacer-left"
- doc="security-reports/cwe"
+ doc={Promise {}}
/>
</label>
</Checkbox>
security_reports.cwe.show
<DocTooltip
className="spacer-left"
- doc="security-reports/cwe"
+ doc={Promise {}}
/>
</label>
</Checkbox>
security_reports.cwe.show
<DocTooltip
className="spacer-left"
- doc="security-reports/cwe"
+ doc={Promise {}}
/>
</label>
</Checkbox>
stepTitle={
<span>
{translate('onboarding.organization.header')}
- <DocTooltip className="little-spacer-left" doc="organizations/organization" />
+ <DocTooltip
+ className="little-spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/organization.md')}
+ />
</span>
}
/>
onboarding.organization.header
<DocTooltip
className="little-spacer-left"
- doc="organizations/organization"
+ doc={Promise {}}
/>
</span>
}
onboarding.organization.header
<DocTooltip
className="little-spacer-left"
- doc="organizations/organization"
- >
- <HelpTooltip
- className="little-spacer-left"
- onShow={[Function]}
- overlay={
- <div
- className="abs-width-300"
- >
- <LazyLoader
- className="cut-margins"
- isTooltip={true}
- />
- </div>
- }
- >
- <div
- className="help-tooltip little-spacer-left"
- >
- <Tooltip
- mouseLeaveDelay={0.25}
- onShow={[Function]}
- overlay={
- <div
- className="abs-width-300"
- >
- <LazyLoader
- className="cut-margins"
- isTooltip={true}
- />
- </div>
- }
- >
- <TooltipInner
- mouseEnterDelay={0.1}
- mouseLeaveDelay={0.25}
- onShow={[Function]}
- overlay={
- <div
- className="abs-width-300"
- >
- <LazyLoader
- className="cut-margins"
- isTooltip={true}
- />
- </div>
- }
- >
- <span
- className="display-inline-flex-center"
- onMouseEnter={[Function]}
- onMouseLeave={[Function]}
- >
- <HelpIcon
- fill="#b4b4b4"
- size={12}
- >
- <Icon
- size={12}
- >
- <svg
- height={12}
- style={
- Object {
- "clipRule": "evenodd",
- "fillRule": "evenodd",
- "strokeLinejoin": "round",
- "strokeMiterlimit": "1.41421",
- }
- }
- version="1.1"
- viewBox="0 0 16 16"
- width={12}
- xmlSpace="preserve"
- xmlnsXlink="http://www.w3.org/1999/xlink"
- >
- <g
- transform="matrix(0.0364583,0,0,0.0364583,1,-0.166667)"
- >
- <path
- d="M224,344L224,296C224,293.667 223.25,291.75 221.75,290.25C220.25,288.75 218.333,288 216,288L168,288C165.667,288 163.75,288.75 162.25,290.25C160.75,291.75 160,293.667 160,296L160,344C160,346.333 160.75,348.25 162.25,349.75C163.75,351.25 165.667,352 168,352L216,352C218.333,352 220.25,351.25 221.75,349.75C223.25,348.25 224,346.333 224,344ZM288,176C288,161.333 283.375,147.75 274.125,135.25C264.875,122.75 253.333,113.083 239.5,106.25C225.667,99.417 211.5,96 197,96C156.5,96 125.583,113.75 104.25,149.25C101.75,153.25 102.417,156.75 106.25,159.75L139.25,184.75C140.417,185.75 142,186.25 144,186.25C146.667,186.25 148.75,185.25 150.25,183.25C159.083,171.917 166.25,164.25 171.75,160.25C177.417,156.25 184.583,154.25 193.25,154.25C201.25,154.25 208.375,156.417 214.625,160.75C220.875,165.083 224,170 224,175.5C224,181.833 222.333,186.917 219,190.75C215.667,194.583 210,198.333 202,202C191.5,206.667 181.875,213.875 173.125,223.625C164.375,233.375 160,243.833 160,255L160,264C160,266.333 160.75,268.25 162.25,269.75C163.75,271.25 165.667,272 168,272L216,272C218.333,272 220.25,271.25 221.75,269.75C223.25,268.25 224,266.333 224,264C224,260.833 225.792,256.708 229.375,251.625C232.958,246.542 237.5,242.417 243,239.25C248.333,236.25 252.417,233.875 255.25,232.125C258.083,230.375 261.917,227.458 266.75,223.375C271.583,219.292 275.292,215.292 277.875,211.375C280.458,207.458 282.792,202.417 284.875,196.25C286.958,190.083 288,183.333 288,176ZM384,224C384,258.833 375.417,290.958 358.25,320.375C341.083,349.792 317.792,373.083 288.375,390.25C258.958,407.417 226.833,416 192,416C157.167,416 125.042,407.417 95.625,390.25C66.208,373.083 42.917,349.792 25.75,320.375C8.583,290.958 0,258.833 0,224C0,189.167 8.583,157.042 25.75,127.625C42.917,98.208 66.208,74.917 95.625,57.75C125.042,40.583 157.167,32 192,32C226.833,32 258.958,40.583 288.375,57.75C317.792,74.917 341.083,98.208 358.25,127.625C375.417,157.042 384,189.167 384,224Z"
- style={
- Object {
- "fill": "#b4b4b4",
- }
- }
- />
- </g>
- </svg>
- </Icon>
- </HelpIcon>
- </span>
- </TooltipInner>
- </Tooltip>
- </div>
- </HelpTooltip>
- </DocTooltip>
+ doc={Promise {}}
+ />
</span>
</h2>
</div>
);
if (onSonarCloud && organization) {
- let docUrl = `project/visibility-${visibility}`;
- if (visibility === Visibility.Public) {
- if (icon) {
- docUrl += '-paid-org';
- }
- if (organization.canAdmin) {
- docUrl += '-admin';
- }
- }
-
return (
<DocTooltip
className={className}
- doc={docUrl}
+ doc={getDoc(visibility, icon, organization)}
overlayProps={{ ...tooltipProps, organization: organization.key }}>
{badge}
</DocTooltip>
};
export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(PrivacyBadge);
+
+function getDoc(visibility: Visibility, icon: JSX.Element | null, organization: Organization) {
+ let doc;
+ if (visibility === Visibility.Private) {
+ doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-private.md');
+ } else if (icon) {
+ if (organization.canAdmin) {
+ doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-public-paid-org-admin.md');
+ } else {
+ doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-public-paid-org.md');
+ }
+ } else if (organization.canAdmin) {
+ doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-public-admin.md');
+ } else {
+ doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-public.md');
+ }
+ return doc;
+}
exports[`renders public 1`] = `
<DocTooltip
- doc="project/visibility-public"
+ doc={Promise {}}
overlayProps={
Object {
"organization": "foo",
exports[`renders public with icon 1`] = `
<DocTooltip
- doc="project/visibility-public-paid-org-admin"
+ doc={Promise {}}
overlayProps={
Object {
"organization": "foo",
+++ /dev/null
-/*
- * 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 { lazyLoad } from '../lazyLoad';
-
-const DocMarkdownBlock = lazyLoad(() => import('./DocMarkdownBlock'));
-
-interface Props {
- className?: string;
- path: string;
-}
-
-interface State {
- content?: string;
-}
-
-export default class DocInclude extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = {};
-
- componentDidMount() {
- this.mounted = true;
- this.fetchContent();
- }
-
- componentWillReceiveProps(nextProps: Props) {
- if (nextProps.path !== this.props.path) {
- this.setState({ content: undefined });
- }
- }
-
- componentDidUpdate(prevProps: Props) {
- if (prevProps.path !== this.props.path) {
- this.fetchContent();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- fetchContent = () => {
- // even if `this.props.path` starts with `/`,
- // it is important to keep `Docs/` in the string to let webpack correctly resolve imports
- import(`Docs/${this.props.path.substr(1)}.md`).then(
- ({ default: content }) => {
- if (this.mounted) {
- this.setState({ content });
- }
- },
- () => {}
- );
- };
-
- render() {
- return <DocMarkdownBlock className={this.props.className} content={this.state.content} />;
- }
-}
import reactRenderer from 'remark-react';
import remarkToc from 'remark-toc';
import DocLink from './DocLink';
-import DocParagraph from './DocParagraph';
import DocImg from './DocImg';
import DocTooltipLink from './DocTooltipLink';
-import { separateFrontMatter } from '../../helpers/markdown';
-import { isSonarCloud } from '../../helpers/system';
+import { separateFrontMatter, filterContent } from '../../helpers/markdown';
import { scrollToElement } from '../../helpers/scrolling';
interface Props {
{displayH1 && <h1>{parsed.frontmatter.title}</h1>}
{
remark()
- // .use(remarkInclude)
.use(remarkToc, { maxDepth: 3 })
.use(reactRenderer, {
remarkReactComponents: {
a: isTooltip
? withChildProps(DocTooltipLink, childProps)
: withChildProps(DocLink, { onAnchorClick: this.handleAnchorClick }),
- // used to handle `@include`
- p: DocParagraph,
// use custom img tag to render documentation images
img: DocImg
},
return <WrappedComponent customProps={childProps} {...props} />;
};
}
-
-function filterContent(content: string) {
- const beginning = isSonarCloud() ? '<!-- sonarqube -->' : '<!-- sonarcloud -->';
- const ending = isSonarCloud() ? '<!-- /sonarqube -->' : '<!-- /sonarcloud -->';
-
- let newContent = content;
- let start = newContent.indexOf(beginning);
- let end = newContent.indexOf(ending);
- while (start !== -1 && end !== -1) {
- newContent = newContent.substring(0, start) + newContent.substring(end + ending.length);
- start = newContent.indexOf(beginning);
- end = newContent.indexOf(ending);
- }
-
- return newContent;
-}
+++ /dev/null
-/*
- * 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 DocInclude from './DocInclude';
-
-const INCLUDE = '@include';
-
-export default function DocParagraph(props: React.HTMLAttributes<HTMLParagraphElement>) {
- if (Array.isArray(props.children) && props.children.length === 1) {
- const child = props.children[0];
- if (typeof child === 'string' && child.startsWith(INCLUDE)) {
- const includePath = child.substr(INCLUDE.length + 1);
- return <DocInclude path={includePath} />;
- }
- }
-
- return <p {...props} />;
-}
interface Props {
className?: string;
children?: React.ReactNode;
- /** Key of the documentation chunk */
- doc: string;
+ // Use as `import(/* webpackMode: "eager" */ 'Docs/tooltips/foo/bar.md')`
+ doc: Promise<{ default: string }>;
overlayProps?: { [k: string]: string };
}
interface State {
content?: string;
- loading: boolean;
open: boolean;
}
export default class DocTooltip extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = { loading: false, open: false };
+ state: State = { open: false };
componentDidMount() {
- this.mounted = true;
+ this.props.doc.then(
+ ({ default: content }) => {
+ this.setState({ content });
+ },
+ () => {}
+ );
document.addEventListener('scroll', this.close, true);
}
- componentWillReceiveProps(nextProps: Props) {
- if (nextProps.doc !== this.props.doc) {
- this.setState({ content: undefined, loading: false, open: false });
- }
- }
-
componentWillUnmount() {
- this.mounted = false;
document.removeEventListener('scroll', this.close, true);
}
- fetchContent = () => {
- this.setState({ loading: true });
- import(`Docs/tooltips/${this.props.doc}.md`).then(
- ({ default: content }) => {
- if (this.mounted) {
- this.setState({ content, loading: false });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
- };
-
close = () => {
this.setState({ open: false });
};
- renderOverlay() {
- return (
- <div className="abs-width-300">
- {this.state.loading ? (
- <i className="spinner" />
- ) : (
- <DocMarkdownBlock
- childProps={this.props.overlayProps}
- className="cut-margins"
- content={this.state.content}
- isTooltip={true}
- />
- )}
- </div>
- );
- }
-
render() {
- return (
+ return this.state.content ? (
<HelpTooltip
className={this.props.className}
- onShow={this.fetchContent}
- overlay={this.renderOverlay()}>
+ overlay={
+ <div className="abs-width-300">
+ <DocMarkdownBlock
+ childProps={this.props.overlayProps}
+ className="cut-margins"
+ content={this.state.content}
+ isTooltip={true}
+ />
+ </div>
+ }>
{this.props.children}
</HelpTooltip>
+ ) : (
+ this.props.children || null
);
}
}
import * as React from 'react';
import { shallow } from 'enzyme';
import DocTooltip from '../DocTooltip';
+import { waitAndUpdate } from '../../../helpers/testUtils';
-jest.useFakeTimers();
-
-it('should render', () => {
- const wrapper = shallow(<DocTooltip doc="foo/bar" />);
- wrapper.setState({ content: 'this is *bold* text', open: true, loading: true });
+it('should render', async () => {
+ const wrapper = shallow(<DocTooltip doc={Promise.resolve({ default: 'this is *bold* text' })} />);
expect(wrapper).toMatchSnapshot();
- wrapper.setState({ loading: false });
+ await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
-
-it('should reset state when receiving new doc', () => {
- const wrapper = shallow(<DocTooltip doc="foo/bar" />);
- wrapper.setState({ content: 'this is *bold* text', open: true });
- wrapper.setProps({ doc: 'baz' });
- expect(wrapper.state()).toEqual({ content: undefined, loading: false, open: false });
-});
<React.Fragment
key="h-1"
>
- <DocParagraph
+ <p
key="h-2"
>
some
- </DocParagraph>
+ </p>
- <DocParagraph
+ <p
key="h-3"
>
sonarqube
- </DocParagraph>
+ </p>
- <DocParagraph
+ <p
key="h-4"
>
long
- </DocParagraph>
+ </p>
- <DocParagraph
+ <p
key="h-5"
>
multiline
- </DocParagraph>
+ </p>
- <DocParagraph
+ <p
key="h-6"
>
text
- </DocParagraph>
+ </p>
</React.Fragment>
</div>
`;
<React.Fragment
key="h-1"
>
- <DocParagraph
+ <p
key="h-2"
>
some
- </DocParagraph>
+ </p>
- <DocParagraph
+ <p
key="h-3"
>
sonarcloud
- </DocParagraph>
+ </p>
- <DocParagraph
+ <p
key="h-4"
>
text
- </DocParagraph>
+ </p>
</React.Fragment>
</div>
`;
<React.Fragment
key="h-1"
>
- <DocParagraph
+ <p
key="h-2"
>
this is
bold
</em>
text
- </DocParagraph>
+ </p>
</React.Fragment>
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render 1`] = `
-<HelpTooltip
- onShow={[Function]}
- overlay={
- <div
- className="abs-width-300"
- >
- <i
- className="spinner"
- />
- </div>
- }
-/>
-`;
+exports[`should render 1`] = `""`;
exports[`should render 2`] = `
<HelpTooltip
- onShow={[Function]}
overlay={
<div
className="abs-width-300"
export function getFrontMatter(content: string): FrontMatter;
export function separateFrontMatter(content: string): { content: string; frontmatter: FrontMatter };
+
+/** Removes SonarQube/SonarCloud only content */
+export function filterContent(content: string): string;
*/
// keep this file in JavaScript, because it is used by a webpack loader
-module.exports = { getFrontMatter, separateFrontMatter };
+module.exports = { getFrontMatter, separateFrontMatter, filterContent };
function getFrontMatter(content) {
const lines = content.split('\n');
}
return data;
}
+
+function filterContent(content) {
+ const { isSonarCloud } = require('../helpers/system');
+ const beginning = isSonarCloud() ? '<!-- sonarqube -->' : '<!-- sonarcloud -->';
+ const ending = isSonarCloud() ? '<!-- /sonarqube -->' : '<!-- /sonarcloud -->';
+
+ let newContent = content;
+ let start = newContent.indexOf(beginning);
+ let end = newContent.indexOf(ending);
+ while (start !== -1 && end !== -1) {
+ newContent = newContent.substring(0, start) + newContent.substring(end + ending.length);
+ start = newContent.indexOf(beginning);
+ end = newContent.indexOf(ending);
+ }
+
+ return newContent;
+}
"sourceMap": true,
"baseUrl": ".",
"paths": {
+ "Docs/*": ["../sonar-docs/src/*"],
"*": ["./src/main/js/@types/*"]
}
},
pseudomap "^1.0.2"
yallist "^2.1.2"
+lunr@2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.0.tgz#4d7c0ca12bdd1e0447b0c131b91420929740c88f"
+
macaddress@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+strip-markdown@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-markdown/-/strip-markdown-3.0.1.tgz#bf4f1c04d03720ae76a01a111cef941d5255cf88"
+
style-loader@0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.21.0.tgz#68c52e5eb2afc9ca92b6274be277ee59aea3a852"