diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2022-10-19 22:59:57 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-10-24 20:04:04 +0000 |
commit | eb249087f989c12a7e25c72a91a8eb7cd3ef0a9d (patch) | |
tree | 4b2cddd7eb1efdddf62978eceebc3b50d9093f95 /server | |
parent | 3e5e886cab8c75cc587f4978510343e29bc33d95 (diff) | |
download | sonarqube-eb249087f989c12a7e25c72a91a8eb7cd3ef0a9d.tar.gz sonarqube-eb249087f989c12a7e25c72a91a8eb7cd3ef0a9d.zip |
SONAR-17221 Documentation links change to static documentation site
- Remove markdown injection
- Redirection for old documentation link to the static site
- Drop the static documentation app
Diffstat (limited to 'server')
153 files changed, 686 insertions, 6784 deletions
diff --git a/server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js b/server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js index b4bd3125731..7b3f96553b5 100644 --- a/server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js +++ b/server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js @@ -45,7 +45,7 @@ it('should have at least one instance of all possible frontmatter fields', () => /* eslint-disable no-console */ it('should have valid links in trees files', () => { - const trees = ['SonarQubeNavigationTree.json', 'StaticNavigationTree.json']; + const trees = ['StaticNavigationTree.json']; let hasErrors = false; trees.forEach(file => { const tree = JSON.parse(fs.readFileSync(path.join(rootPath, '..', 'static', file), 'utf8')); @@ -65,27 +65,6 @@ it('should have valid links in trees files', () => { expect(hasErrors).toBe(false); }); -it('should have valid links in suggestions file', () => { - const file = 'EmbedDocsSuggestions.json'; - const suggestions = JSON.parse(fs.readFileSync(path.join(rootPath, file), 'utf8')); - const hasErrors = Object.keys(suggestions).some(key => { - return suggestions[key].some(suggestion => { - if (!suggestion.link.startsWith('/documentation/')) { - console.log( - `[${suggestion.link}] should starts with "/documentation/" or be a valid url, in ${file}` - ); - return true; - } - if (!urlExists(parsedFiles, suggestion.link.replace('/documentation', ''))) { - console.log(`[${suggestion.link}] is not a valid link, in ${file}`); - return true; - } - return false; - }); - }); - expect(hasErrors).toBe(false); -}); - function collectErrors() { let urlLists = []; let hasErrors = false; diff --git a/server/sonar-docs/src/pages/setup/upgrade-notes.md b/server/sonar-docs/src/pages/setup/upgrade-notes.md index d5dcfe21fe3..f568017cb11 100644 --- a/server/sonar-docs/src/pages/setup/upgrade-notes.md +++ b/server/sonar-docs/src/pages/setup/upgrade-notes.md @@ -3,6 +3,14 @@ title: Release Upgrade Notes url: /setup/upgrade-notes/ --- +## Release 9.8 Upgrade notes +**Dropping the embedded documentation** +SonarQube no longer ships with an embedded version of the documentation. The documentation will now only be available at [docs.sonarqube.org](https://docs.sonarqube.org/). ([SONAR-17221](https://sonarsource.atlassian.net/browse/SONAR-17221)) +* All in-app links will now point to the documentation website. +* External links that pointed to `/documentation` will be correctly redirected to the corresponding page on the documentation website. + +[Full release notes](https://sonarsource.atlassian.net/issues/?jql=project%20%3D%2010139%20AND%20fixVersion%20%3D%2013884) + ## Release 9.7 Upgrade notes **Change in the database connection pool** The database connection pool has been replaced for better performance. The `sonar.jdbc.maxIdle`, `sonar.jdbc.minEvictableIdleTimeMillis` and `sonar.jdbc.timeBetweenEvictionRunsMillis` properties no longer have any effect and should be removed from the configuration. Also, the JMX information that is provided to monitor the connection pool has evolved. See the [Monitoring documentation](/instance-administration/monitoring/) for more information. ([SONAR-17200](https://sonarsource.atlassian.net/browse/SONAR-17200)). diff --git a/server/sonar-docs/static/SonarQubeNavigationTree.json b/server/sonar-docs/static/SonarQubeNavigationTree.json deleted file mode 100644 index 0e47a473fbb..00000000000 --- a/server/sonar-docs/static/SonarQubeNavigationTree.json +++ /dev/null @@ -1,222 +0,0 @@ -[ - { - "title": "Requirements", - "children": [ - "/requirements/requirements/", - "/requirements/hardware-recommendations/" - ] - }, - { - "title": "Analyzing Source Code", - "children": [ - "/analysis/overview/", - { - "title": "Scanners", - "children": [ - "/analysis/scan/sonarscanner-for-gradle/", - "/analysis/scan/sonarscanner-for-msbuild/", - "/analysis/scan/sonarscanner-for-maven/", - "/analysis/scan/sonarscanner-for-azure-devops/", - "/analysis/scan/sonarscanner-for-jenkins/", - "/analysis/scan/sonarscanner-for-ant/", - "/analysis/scan/sonarscanner/" - ] - }, - "/analysis/analysis-parameters/", - { - "title": "Languages", - "children": [ - "/analysis/languages/overview/", - "/analysis/languages/abap/", - "/analysis/languages/apex/", - "/analysis/languages/csharp/", - "/analysis/languages/cfamily/", - "/analysis/languages/cobol/", - "/analysis/languages/flex/", - "/analysis/languages/go/", - "/analysis/languages/html/", - "/analysis/languages/iac/", - "/analysis/languages/java/", - "/analysis/languages/javascript/", - "/analysis/languages/kotlin/", - "/analysis/languages/php/", - "/analysis/languages/pli/", - "/analysis/languages/plsql/", - "/analysis/languages/python/", - "/analysis/languages/rpg/", - "/analysis/languages/ruby/", - "/analysis/languages/scala/", - "/analysis/languages/swift/", - "/analysis/languages/tsql/", - "/analysis/languages/vbnet/", - "/analysis/languages/vb6/", - "/analysis/languages/xml/" - ] - }, - { - "title": "Test Coverage", - "children": [ - "/analysis/coverage/", - "/analysis/test-coverage/java-test-coverage/", - "/analysis/test-coverage/javascript-typescript-test-coverage/", - "/analysis/test-coverage/dotnet-test-coverage/", - "/analysis/test-coverage/python-test-coverage/", - "/analysis/test-coverage/php-test-coverage/", - "/analysis/test-coverage/c-family-test-coverage/", - "/analysis/generic-test/", - "/analysis/test-coverage/test-coverage-parameters/", - "/analysis/test-coverage/test-execution-parameters/" - ] - }, - { - "title": "Importing External Issues", - "children": [ - "/analysis/external-issues/", - "/analysis/generic-issue/" - ] - }, - "/analysis/background-tasks/", - "/analysis/pull-request/", - { - "title": "Branches", - "children": [ - "/branches/overview/", - "/branches/branches-faq/" - ] - }, - { - "title": "CI Integration", - "children": [ - "/analysis/ci-integration-overview/", - "/analysis/jenkins/", - "/analysis/codemagic/" - ] - }, - "/analysis/scm-integration/", - "/analysis/security_configuration/", - "/analysis/scanner-environment/" - ] - }, - { - "title": "DevOps Platform Integration", - "children": [ - "/analysis/github-integration/", - "/analysis/gitlab-integration/", - { - "title": "Bitbucket Integration", - "children": [ - "/analysis/bitbucket-integration/", - "/analysis/bitbucket-cloud-integration/" - ] - }, - "/analysis/azuredevops-integration/" - ] - }, - { - "title": "User Guide", - "children": [ - "/user-guide/concepts/", - "/user-guide/clean-as-you-code/", - "/user-guide/connected-mode/", - "/user-guide/metric-definitions/", - "/user-guide/quality-gates/", - { - "title": "Rules", - "children": [ - "/user-guide/rules/", - "/user-guide/security-rules/", - "/user-guide/built-in-rule-tags/" - ] - }, - { - "title": "User Account", - "children": [ - "/user-guide/user-account/", - "/user-guide/user-token/" - ] - }, - "/user-guide/issues/", - "/user-guide/security-hotspots/", - "/user-guide/portfolios/", - "/user-guide/applications/", - "/user-guide/security-reports/", - "/user-guide/activity-history/", - "/user-guide/project-page/", - "/user-guide/keyboard-shortcuts/" - ] - }, - { - "title": "Project Administration", - "children": [ - "/project-administration/project-existence/", - "/project-administration/managing-project-history/", - "/project-administration/narrowing-the-focus/", - "/project-administration/new-code-period/", - "/project-administration/managing-portfolios/", - "/project-administration/managing-applications/", - "/project-administration/portfolio-pdf-configuration/", - "/project-administration/project-settings/", - "/project-administration/webhooks/" - ] - }, - { - "title": "Instance Administration", - "children": [ - "/instance-administration/quality-profiles/", - "/instance-administration/security/", - { - "title": "Authentication", - "children": [ - "/instance-administration/authentication/overview/", - "/instance-administration/authentication/http-header/", - "/instance-administration/authentication/ldap/", - { - "title": "SAML", - "children": [ - "/instance-administration/authentication/saml/overview/", - "/instance-administration/authentication/saml/azuread/", - "/instance-administration/authentication/saml/keycloak/", - "/instance-administration/authentication/saml/okta/" - ] - }, - "/instance-administration/authentication/github/", - "/instance-administration/authentication/gitlab/", - "/instance-administration/authentication/bitbucket-cloud/" - ] - }, - "/instance-administration/look-and-feel/", - "/instance-administration/marketplace/", - "/instance-administration/housekeeping/", - "/instance-administration/audit-logs/", - "/instance-administration/system-info/", - "/instance-administration/notifications/", - "/instance-administration/license-manager/", - "/instance-administration/monitoring/", - "/instance-administration/project-move/", - "/instance-administration/compute-engine-performance/", - "/instance-administration/db-copy/", - "/instance-administration/backup-restore/", - "/instance-administration/telemetry/" - ] - }, - { - "title": "Extension Guide", - "children": [ - "/extend/web-api/", - "/extend/adding-coding-rules/", - { - "title": "Developing a plugin", - "children": [ - "/extend/developing-plugin/", - "/extend/new-languages/", - "/extend/executable-lines/", - "/extend/extend-web-app/", - "/extend/adding-scm/" - ] - }, - "/extend/i18n/", - "/extend/contributing/" - ] - }, - "/faq/" -] diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index d4b7e7592bd..30230f70b88 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -22,7 +22,6 @@ "formik": "2.2.9", "lodash": "4.17.21", "lunr": "2.3.9", - "mdast-util-toc": "5.0.2", "react": "16.13.0", "react-day-picker": "7.4.10", "react-dom": "16.13.0", @@ -34,14 +33,6 @@ "react-select": "4.3.1", "react-virtualized": "9.22.3", "regenerator-runtime": "0.13.9", - "rehype-raw": "4.0.2", - "rehype-react": "5.0.0", - "rehype-slug": "3.0.0", - "remark": "11.0.2", - "remark-custom-blocks": "2.5.1", - "remark-react": "7.0.1", - "remark-rehype": "6.0.0", - "unist-util-visit": "2.0.2", "valid-url": "1.0.9" }, "devDependencies": { diff --git a/server/sonar-web/src/main/js/@types/rehype-raw.d.ts b/server/sonar-web/src/main/js/@types/rehype-raw.d.ts deleted file mode 100644 index 3fad41c4560..00000000000 --- a/server/sonar-web/src/main/js/@types/rehype-raw.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 'rehype-raw' { - export default function rehypeRaw(): any; -} diff --git a/server/sonar-web/src/main/js/@types/rehype-react.d.ts b/server/sonar-web/src/main/js/@types/rehype-react.d.ts deleted file mode 100644 index 50a919d9677..00000000000 --- a/server/sonar-web/src/main/js/@types/rehype-react.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 'rehype-react' { - export default function rehypeReact(): any; -} diff --git a/server/sonar-web/src/main/js/@types/rehype-slug.d.ts b/server/sonar-web/src/main/js/@types/rehype-slug.d.ts deleted file mode 100644 index 857e1bbcbd0..00000000000 --- a/server/sonar-web/src/main/js/@types/rehype-slug.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 'rehype-slug' { - export default function rehypeSlug(): any; -} diff --git a/server/sonar-web/src/main/js/@types/remark-custom-blocks.d.ts b/server/sonar-web/src/main/js/@types/remark-custom-blocks.d.ts deleted file mode 100644 index 759096d37b8..00000000000 --- a/server/sonar-web/src/main/js/@types/remark-custom-blocks.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 'remark-custom-blocks' { - export default function customBlock(): any; -} diff --git a/server/sonar-web/src/main/js/@types/remark-react.d.ts b/server/sonar-web/src/main/js/@types/remark-react.d.ts deleted file mode 100644 index 780409f9aae..00000000000 --- a/server/sonar-web/src/main/js/@types/remark-react.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 'remark-react' { - interface Options { - /** `h()` */ - createElement?: (...args: any[]) => JSX.Element; - /** Key prefix. */ - prefix?: string; - /** Components. */ - remarkReactComponents?: any; - /** Sanitation schema. */ - sanitize?: any; - } - - export default function remarkReact(options?: Options): JSX.Element; -} diff --git a/server/sonar-web/src/main/js/@types/remark-rehype.d.ts b/server/sonar-web/src/main/js/@types/remark-rehype.d.ts deleted file mode 100644 index 85a2bb14e97..00000000000 --- a/server/sonar-web/src/main/js/@types/remark-rehype.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 'remark-rehype' { - export default function remarkRehype(): any; -} diff --git a/server/sonar-web/src/main/js/@types/remark.d.ts b/server/sonar-web/src/main/js/@types/remark.d.ts deleted file mode 100644 index f7beee505c3..00000000000 --- a/server/sonar-web/src/main/js/@types/remark.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 'remark' { - export default function remark(): any; -} diff --git a/server/sonar-web/src/main/js/@types/unist-util-visit.d.ts b/server/sonar-web/src/main/js/@types/unist-util-visit.d.ts deleted file mode 100644 index 9002a105615..00000000000 --- a/server/sonar-web/src/main/js/@types/unist-util-visit.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 'unist-util-visit' { - export default function visit( - ast: any, - visitor: (node: { type: string; value: string }) => void - ): void; -} diff --git a/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts index 9f02909bc9f..4ac206dbf5d 100644 --- a/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts @@ -19,34 +19,46 @@ */ import { cloneDeep } from 'lodash'; import { HousekeepingPolicy } from '../../apps/audit-logs/utils'; +import { BranchParameters } from '../../types/branch-like'; import { SettingsKey, SettingValue } from '../../types/settings'; import { getValue } from '../settings'; export default class SettingsServiceMock { - settingValue?: SettingValue; - defaultValues: SettingValue = { - key: SettingsKey.AuditHouseKeeping, - value: HousekeepingPolicy.Weekly - }; + settingValues: SettingValue[]; + defaultValues: SettingValue[] = [ + { + key: SettingsKey.AuditHouseKeeping, + value: HousekeepingPolicy.Weekly + } + ]; constructor() { - this.settingValue = cloneDeep(this.defaultValues); - (getValue as jest.Mock).mockImplementation(this.getValuesHandler); + this.settingValues = cloneDeep(this.defaultValues); + (getValue as jest.Mock).mockImplementation(this.handleGetValues); } - getValuesHandler = () => { - return Promise.resolve(this.settingValue); + handleGetValues = (data: { key: string; component?: string } & BranchParameters) => { + const setting = this.settingValues.find(s => s.key === data.key); + + return this.reply(setting); }; - unsetHousekeepingPolicy() { - this.settingValue = undefined; + emptySettings() { + this.settingValues = []; } setYearlyHousekeepingPolicy() { - this.settingValue = { key: 'test', value: HousekeepingPolicy.Yearly }; + const auditSetting = this.settingValues.find(s => s.key === SettingsKey.AuditHouseKeeping); + if (auditSetting) { + auditSetting.value = HousekeepingPolicy.Yearly; + } } resetSettingvalues = () => { - this.settingValue = cloneDeep(this.defaultValues); + this.settingValues = cloneDeep(this.defaultValues); }; + + reply<T>(response: T): Promise<T> { + return Promise.resolve(cloneDeep(response)); + } } diff --git a/server/sonar-web/src/main/js/app/components/DocumentationRedirect.tsx b/server/sonar-web/src/main/js/app/components/DocumentationRedirect.tsx new file mode 100644 index 00000000000..3f4206d868a --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/DocumentationRedirect.tsx @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { Helmet } from 'react-helmet-async'; +import { useLocation } from 'react-router-dom'; +import Link from '../../components/common/Link'; +import { getUrlForDoc } from '../../helpers/docs'; +import withAppStateContext, { WithAppStateContextProps } from './app-state/withAppStateContext'; + +const PAUSE_REDIRECT = 1; + +function DocumentationRedirect({ appState }: WithAppStateContextProps) { + const location = useLocation(); + const url = getUrlForDoc(appState.version, location.pathname.replace(/^\/documentation/, '')); + + return ( + <> + <Helmet> + <meta httpEquiv="refresh" content={`${PAUSE_REDIRECT}; url='${url}'`} /> + </Helmet> + <div className="global-loading"> + <div className="display-flex-center"> + <i className="spinner global-loading-spinner" /> + <span className="spacer-left global-loading-text">Redirecting...</span> + </div> + <div className="display-flex-justify-content spacer-top large"> + <Link to={url}>Click here if you're not being redirected automatically</Link> + </div> + </div> + </> + ); +} + +export default withAppStateContext(DocumentationRedirect); diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx index 7631e5345a6..3ed1bec60b8 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import DocLink from '../../components/common/DocLink'; import InstanceMessage from '../../components/common/InstanceMessage'; import Link from '../../components/common/Link'; import { Alert } from '../../components/ui/Alert'; @@ -74,7 +75,7 @@ export function GlobalFooter({ hideLoggedInInfo, appState }: GlobalFooterProps) </a> </li> <li className="page-footer-menu-item"> - <Link to="/documentation">{translate('footer.documentation')}</Link> + <DocLink to="/">{translate('footer.documentation')}</DocLink> </li> <li className="page-footer-menu-item"> <a diff --git a/server/sonar-web/src/main/js/app/components/__tests__/DocumentationRedirect-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/DocumentationRedirect-test.tsx new file mode 100644 index 00000000000..9053428f7d2 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/__tests__/DocumentationRedirect-test.tsx @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 { screen } from '@testing-library/react'; +import * as React from 'react'; +import { Route } from 'react-router-dom'; +import { mockAppState } from '../../../helpers/testMocks'; +import { renderAppRoutes } from '../../../helpers/testReactTestingUtils'; +import DocumentationRedirect from '../DocumentationRedirect'; + +it('should redirect to static doc for specific version', async () => { + renderDocumentationRedirect('land', '9.7.1234'); + + expect(await screen.findByRole('link')).toHaveAttribute( + 'href', + 'https://docs.sonarqube.org/9.7/land' + ); +}); + +it('should redirect to static doc for latest version', async () => { + renderDocumentationRedirect('land', '9.7-SNAPSHOT'); + + expect(await screen.findByRole('link')).toHaveAttribute( + 'href', + 'https://docs.sonarqube.org/latest/land' + ); +}); + +function renderDocumentationRedirect(navigatge: string, version?: string) { + renderAppRoutes( + `documentation/${navigatge}`, + () => <Route path="/documentation/*" element={<DocumentationRedirect />} />, + { appState: mockAppState({ version }) } + ); +} diff --git a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx index 4d47866e21e..79daabad3ac 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx @@ -90,14 +90,6 @@ it('should render only the children', async () => { getWrapper({ appState: mockAppState({ canAdmin: false }), currentUser: { ...LOGGED_IN_USER }, - location: mockLocation({ pathname: '/documentation/' }) - }) - ); - - await shouldNotHaveModals( - getWrapper({ - appState: mockAppState({ canAdmin: false }), - currentUser: { ...LOGGED_IN_USER }, location: mockLocation({ pathname: '/create-organization' }) }) ); diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap index 93db6c4ba6f..d9f37661358 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap @@ -44,11 +44,11 @@ exports[`should display the sq version 1`] = ` <li className="page-footer-menu-item" > - <ForwardRef(Link) - to="/documentation" + <withAppStateContext(DocLink) + to="/" > footer.documentation - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </li> <li className="page-footer-menu-item" @@ -108,11 +108,11 @@ exports[`should not render the only logged in information 1`] = ` <li className="page-footer-menu-item" > - <ForwardRef(Link) - to="/documentation" + <withAppStateContext(DocLink) + to="/" > footer.documentation - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </li> <li className="page-footer-menu-item" @@ -173,11 +173,11 @@ exports[`should render the only logged in information 1`] = ` <li className="page-footer-menu-item" > - <ForwardRef(Link) - to="/documentation" + <withAppStateContext(DocLink) + to="/" > footer.documentation - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </li> <li className="page-footer-menu-item" diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx index 7c51dd97fa7..3cbee80e9e5 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx @@ -96,7 +96,8 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) { links={[ { href: 'https://redirect.sonarsource.com/editions/developer.html', - label: translate('learn_more') + label: translate('learn_more'), + doc: false } ]} title={ @@ -119,17 +120,18 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) { data-test="only-one-branch-like" links={[ { - href: '/documentation/branches/overview/', + href: '/branches/overview/', label: translate('branch_like_navigation.only_one_branch.documentation') }, { - href: '/documentation/analysis/pull-request/', + href: '/analysis/pull-request/', label: translate('branch_like_navigation.only_one_branch.pr_analysis') }, { href: `/tutorials?id=${component.key}`, label: translate('branch_like_navigation.tutorial_for_ci'), - inPlace: true + inPlace: true, + doc: false } ]} title={translate('branch_like_navigation.only_one_branch.title')}> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap index eb009ecfeee..513f07e8cfb 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap @@ -127,6 +127,7 @@ exports[`projects should render correctly when branches support is disabled: alm links={ Array [ Object { + "doc": false, "href": "https://redirect.sonarsource.com/editions/developer.html", "label": "learn_more", }, @@ -169,6 +170,7 @@ exports[`projects should render correctly when branches support is disabled: alm links={ Array [ Object { + "doc": false, "href": "https://redirect.sonarsource.com/editions/developer.html", "label": "learn_more", }, @@ -211,6 +213,7 @@ exports[`projects should render correctly when branches support is disabled: def links={ Array [ Object { + "doc": false, "href": "https://redirect.sonarsource.com/editions/developer.html", "label": "learn_more", }, @@ -278,14 +281,15 @@ exports[`projects should render correctly when there is only one branchlike 1`] links={ Array [ Object { - "href": "/documentation/branches/overview/", + "href": "/branches/overview/", "label": "branch_like_navigation.only_one_branch.documentation", }, Object { - "href": "/documentation/analysis/pull-request/", + "href": "/analysis/pull-request/", "label": "branch_like_navigation.only_one_branch.pr_analysis", }, Object { + "doc": false, "href": "/tutorials?id=my-project", "inPlace": true, "label": "branch_like_navigation.tutorial_for_ci", diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index 93cc1be5009..44f1ac28530 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -29,7 +29,6 @@ import ChangeAdminPasswordApp from '../../apps/change-admin-password/ChangeAdmin import codeRoutes from '../../apps/code/routes'; import codingRulesRoutes from '../../apps/coding-rules/routes'; import componentMeasuresRoutes from '../../apps/component-measures/routes'; -import documentationRoutes from '../../apps/documentation/routes'; import groupsRoutes from '../../apps/groups/routes'; import { globalIssuesRoutes, projectIssuesRoutes } from '../../apps/issues/routes'; import maintenanceRoutes from '../../apps/maintenance/routes'; @@ -73,6 +72,7 @@ import { } from '../components/available-features/AvailableFeaturesContext'; import ComponentContainer from '../components/ComponentContainer'; import CurrentUserContextProvider from '../components/current-user/CurrentUserContextProvider'; +import DocumentationRedirect from '../components/DocumentationRedirect'; import GlobalAdminPageExtension from '../components/extensions/GlobalAdminPageExtension'; import GlobalPageExtension from '../components/extensions/GlobalPageExtension'; import PortfolioPage from '../components/extensions/PortfolioPage'; @@ -142,10 +142,6 @@ function renderRedirects() { {renderRedirect({ from: '/admin', to: '/admin/settings' })} {renderRedirect({ from: '/background_tasks', to: '/admin/background_tasks' })} - {renderRedirect({ - from: '/documentation/analysis/languages/vb', - to: '/documentation/analysis/languages/vbnet/' - })} {renderRedirect({ from: '/groups', to: '/admin/groups' })} {renderRedirect({ from: '/extension/governance/portfolios', to: '/portfolios' })} {renderRedirect({ from: '/permission_templates', to: '/admin/permission_templates' })} @@ -163,6 +159,7 @@ function renderRedirects() { {renderRedirect({ from: '/users', to: '/admin/users' })} {renderRedirect({ from: '/onboarding', to: '/projects/create' })} {renderRedirect({ from: '/markdown/help', to: '/formatting/help' })} + <Route path="/documentation/*" element={<DocumentationRedirect />} /> </> ); } @@ -265,8 +262,6 @@ export default function startReactApp( {codingRulesRoutes()} - {documentationRoutes()} - <Route path="extension/:pluginKey/:extensionKey" element={<GlobalPageExtension />} diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx index 80128101cbe..813ede23e2d 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx @@ -141,7 +141,7 @@ it('should not render if governance is not enable', () => { }); it('should show right option when keeping log for month', async () => { - handler.unsetHousekeepingPolicy(); + handler.emptySettings(); renderAuditLogs(); expect(await ui.pageTitle.find()).toBeInTheDocument(); expect(ui.todayRadio.get()).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx index dd4cbe6f060..020395e1918 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Link from '../../../components/common/Link'; +import DocLink from '../../../components/common/DocLink'; import { translate } from '../../../helpers/l10n'; import Workers from './Workers'; @@ -37,12 +37,9 @@ export default function Header(props: Props) { )} <p className="page-description"> {translate('background_tasks.page.description')} - <Link - className="spacer-left" - target="_blank" - to={{ pathname: '/documentation/analysis/background-tasks/' }}> + <DocLink className="spacer-left" to="/analysis/background-tasks/"> {translate('learn_more')} - </Link> + </DocLink> </p> </header> ); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx index 76a12c9d80f..a78cbf07a81 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx @@ -175,7 +175,7 @@ export default class ProfileFacet extends React.PureComponent<Props> { content={translate('coding_rules.facet.qprofile.help')} links={[ { - href: '/documentation/instance-administration/quality-profiles/', + href: '/instance-administration/quality-profiles/', label: translate('coding_rules.facet.qprofile.link') } ]} diff --git a/server/sonar-web/src/main/js/apps/documentation/__tests__/pages-test.ts b/server/sonar-web/src/main/js/apps/documentation/__tests__/pages-test.ts deleted file mode 100644 index 263144f12bc..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/__tests__/pages-test.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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. - */ -/* eslint-disable no-console */ -import { filterContent, ParsedContent } from '../../../helpers/markdown'; -import { mockDocumentationMarkdown } from '../../../helpers/testMocks'; -import { Dict } from '../../../types/types'; - -jest.mock('remark', () => () => ({ - parse: jest.fn().mockReturnValue({}) -})); - -jest.mock( - 'unist-util-visit', - () => (_: any, cb: (node: { type: string; value: string }) => void) => { - cb({ type: 'text', value: 'Text content' }); - } -); - -jest.mock('../../../helpers/markdown', () => { - const markdown = jest.requireActual('../../../helpers/markdown'); - return { ...markdown, filterContent: jest.fn().mockImplementation(markdown.filterContent) }; -}); - -const lorem = { - url: 'analysis/languages/lorem', - title: 'toto doc', - key: 'toto', - content: 'TOTO CONTENT' -}; -const foo = { - url: `analysis/languages/foo`, - title: 'foo doc', - key: 'foo' -}; -const loremDoc = mockDocumentationMarkdown(lorem); -const fooDoc = mockDocumentationMarkdown(foo); - -jest.mock('../documentation.directory-loader', () => [ - { content: loremDoc, path: lorem.url }, - { content: fooDoc, path: foo.url } -]); - -it('should correctly parse files', () => { - const pages = getPages(); - expect(pages.length).toBe(2); - - expect(pages[0]).toMatchObject({ - relativeName: lorem.url, - url: `/${lorem.url}/`, - title: lorem.title, - navTitle: undefined, - order: -1, - scope: undefined, - content: lorem.content - }); - - expect(pages[1]).toMatchObject({ - relativeName: foo.url, - url: `/${foo.url}/`, - title: foo.title, - navTitle: undefined, - order: -1, - scope: undefined - }); -}); - -it('should correctly handle overrides (replace & add)', () => { - const overrideFooDoc = { - content: 'OVERRIDE_FOO', - title: 'OVERRIDE_TITLE_FOO', - key: foo.key - }; - const newDoc = { - content: 'TATA', - title: 'TATA_TITLE', - key: 'tata' - }; - - const overrides: Dict<ParsedContent> = {}; - overrides[foo.url] = { frontmatter: overrideFooDoc, content: overrideFooDoc.content }; - overrides[`analysis/languages/${newDoc.key}`] = { frontmatter: newDoc, content: newDoc.content }; - const pages = getPages(overrides); - - expect(pages.length).toBe(3); - expect(pages[1].content).toBe(overrideFooDoc.content); - expect(pages[1].title).toBe(overrideFooDoc.title); - expect(pages[2].content).toBe(newDoc.content); - expect(pages[2].title).toBe(newDoc.title); -}); - -it('should not break the whole doc when one page cannot be parsed', () => { - const originalConsoleError = console.error; - console.error = jest.fn(); - - (filterContent as jest.Mock).mockImplementationOnce(() => { - throw Error('Parse page error'); - }); - const pages = getPages(); - expect(pages.length).toBe(2); - expect(pages[0].content).toBe(''); - expect(console.error).toHaveBeenCalledTimes(1); - - console.error = originalConsoleError; -}); - -function getPages(overrides: Dict<ParsedContent> = {}) { - // This allows the use of out-of-scope data inside jest.mock - // Usually, it is impossible as jest.mock'ed module is hoisted on the top of the file - return jest.requireActual('../pages').default(overrides); -} diff --git a/server/sonar-web/src/main/js/apps/documentation/components/App.tsx b/server/sonar-web/src/main/js/apps/documentation/components/App.tsx deleted file mode 100644 index 7834ae8612a..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/App.tsx +++ /dev/null @@ -1,237 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 navigationTreeSonarQube from 'Docs/../static/SonarQubeNavigationTree.json'; -import { DocNavigationItem } from 'Docs/@types/types'; -import * as React from 'react'; -import { Helmet } from 'react-helmet-async'; -import { useLocation, useParams } from 'react-router-dom'; -import { getInstalledPlugins } from '../../../api/plugins'; -import { getPluginStaticFileContent } from '../../../api/static'; -import NotFound from '../../../app/components/NotFound'; -import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; -import Link from '../../../components/common/Link'; -import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; -import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { ParsedContent, separateFrontMatter } from '../../../helpers/markdown'; -import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages'; -import { isDefined } from '../../../helpers/types'; -import { InstalledPlugin, PluginType } from '../../../types/plugins'; -import { Dict } from '../../../types/types'; -import { getUrlsList } from '../navTreeUtils'; -import getPages from '../pages'; -import '../styles.css'; -import { DocumentationEntry } from '../utils'; -import Sidebar from './Sidebar'; - -interface Props { - params: { splat?: string }; - location: { hash: string }; -} - -interface State { - loading: boolean; - pages: DocumentationEntry[]; - tree: DocNavigationItem[]; -} - -const LANGUAGES_BASE_URL = 'analysis/languages'; - -export class App extends React.PureComponent<Props, State> { - mounted = false; - state: State = { - loading: false, - pages: [], - tree: [] - }; - - componentDidMount() { - this.mounted = true; - addSideBarClass(); - - this.setState({ loading: true }); - - const tree = (navigationTreeSonarQube as any).default as DocNavigationItem[]; - - this.getLanguagePluginsDocumentation(tree).then( - overrides => { - if (this.mounted) { - this.setState({ - loading: false, - pages: getPages(overrides), - tree - }); - } - }, - () => { - if (this.mounted) { - this.setState({ - loading: false - }); - } - } - ); - } - - componentWillUnmount() { - this.mounted = false; - removeSideBarClass(); - } - - getLanguagePluginsDocumentation = async (tree: DocNavigationItem[]) => { - const plugins = await getInstalledPlugins(PluginType.Bundled).catch( - () => [] as InstalledPlugin[] - ); - - const pluginsWithDoc = await Promise.all( - plugins.map(plugin => { - if (plugin.documentationPath) { - const matchArray = /^static\/(.*)/.exec(plugin.documentationPath); - - if (matchArray && matchArray.length > 1) { - return getPluginStaticFileContent(plugin.key, matchArray[1]).then( - content => ({ ...plugin, content }), - () => undefined - ); - } - } - - return undefined; - }) - ); - - const regex = new RegExp(`/${LANGUAGES_BASE_URL}/\\w+/$`); - const overridablePaths = getUrlsList(tree).filter( - path => regex.test(path) && path !== `/${LANGUAGES_BASE_URL}/overview/` - ); - - const parsedContent: Dict<ParsedContent> = {}; - - pluginsWithDoc.filter(isDefined).forEach(plugin => { - const parsed = separateFrontMatter(plugin.content); - - if (plugin.issueTrackerUrl) { - // Inject issue tracker link - let issueTrackerLink = '## Issue Tracker'; - issueTrackerLink += '\r\n'; - issueTrackerLink += `Check the [issue tracker](${plugin.issueTrackerUrl}) for this language.`; - parsed.content = `${parsed.content}\r\n${issueTrackerLink}`; - } - - if ( - parsed?.frontmatter?.key && - overridablePaths.includes(`/${LANGUAGES_BASE_URL}/${parsed.frontmatter.key}/`) - ) { - parsedContent[`${LANGUAGES_BASE_URL}/${parsed.frontmatter.key}`] = parsed; - } - }); - - return parsedContent; - }; - - render() { - const { loading, pages, tree } = this.state; - const { - params: { splat = '' }, - location: { hash } - } = this.props; - - if (loading) { - return ( - <div className="page page-limited"> - <DeferredSpinner /> - </div> - ); - } - - const page = pages.find(p => p.url === `/${splat}`); - const mainTitle = translate('documentation.page_title.sonarqube'); - const isIndex = splat === 'index'; - - if (!page) { - return ( - <> - <Helmet title={mainTitle}> - <meta content="noindex nofollow" name="robots" /> - </Helmet> - <A11ySkipTarget anchor="documentation_main" /> - <NotFound withContainer={false} /> - </> - ); - } - - return ( - <div className="layout-page"> - <Helmet - defer={false} - title={isIndex || !page.title ? mainTitle : `${page.title} | ${mainTitle}`}> - <meta content="noindex nofollow" name="robots" /> - </Helmet> - - <ScreenPositionHelper className="layout-page-side-outer"> - {({ top }) => ( - <div className="layout-page-side" style={{ top }}> - <div className="layout-page-side-inner"> - <div className="layout-page-filters"> - <div className="documentation-page-header"> - <A11ySkipTarget - anchor="documentation_menu" - label={translate('documentation.skip_to_nav')} - weight={10} - /> - - <Link to="/documentation/"> - <h1>{translate('documentation.page')}</h1> - </Link> - </div> - <Sidebar navigation={tree} pages={pages} splat={splat} /> - </div> - </div> - </div> - )} - </ScreenPositionHelper> - - <div className="layout-page-main"> - <div className="layout-page-main-inner"> - <div className="boxed-group"> - <A11ySkipTarget anchor="documentation_main" /> - - <DocMarkdownBlock - className="documentation-content cut-margins boxed-group-inner" - content={page.content} - stickyToc={true} - title={page.title} - scrollToHref={hash} - /> - </div> - </div> - </div> - </div> - ); - } -} - -export default function AppWrapper() { - const params = useParams(); - const location = useLocation(); - - return <App params={{ splat: params['*'] }} location={location} />; -} diff --git a/server/sonar-web/src/main/js/apps/documentation/components/Menu.tsx b/server/sonar-web/src/main/js/apps/documentation/components/Menu.tsx deleted file mode 100644 index 87b432afa9c..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/Menu.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { DocNavigationItem } from 'Docs/@types/types'; -import * as React from 'react'; -import { - getOpenChainFromPath, - isDocsNavigationBlock, - isDocsNavigationExternalLink -} from '../navTreeUtils'; -import { DocumentationEntry, getNodeFromUrl } from '../utils'; -import MenuBlock from './MenuBlock'; -import { MenuExternalLink } from './MenuExternalLink'; -import { MenuItem } from './MenuItem'; - -interface Props { - navigation: DocNavigationItem[]; - pages: DocumentationEntry[]; - splat: string; -} - -interface State { - openChain: DocNavigationItem[]; -} - -export default class Menu extends React.PureComponent<Props, State> { - constructor(props: Props) { - super(props); - this.state = { - openChain: getOpenChainFromPath(this.props.splat, this.props.navigation) - }; - } - - componentWillReceiveProps(nextProps: Props) { - if (this.props.splat !== nextProps.splat) { - this.setState({ openChain: getOpenChainFromPath(nextProps.splat, nextProps.navigation) }); - } - } - - render() { - const { openChain } = this.state; - return ( - <> - {this.props.navigation.map(item => { - if (isDocsNavigationBlock(item)) { - return ( - <MenuBlock - block={item} - key={item.title} - openByDefault={openChain.includes(item)} - openChain={openChain} - pages={this.props.pages} - splat={this.props.splat} - title={item.title} - /> - ); - } - if (isDocsNavigationExternalLink(item)) { - return <MenuExternalLink key={item.title} title={item.title} url={item.url} />; - } - return ( - <MenuItem - key={item} - node={getNodeFromUrl(this.props.pages, item)} - splat={this.props.splat} - /> - ); - })} - </> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/documentation/components/MenuBlock.tsx b/server/sonar-web/src/main/js/apps/documentation/components/MenuBlock.tsx deleted file mode 100644 index d1687c28859..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/MenuBlock.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 classNames from 'classnames'; -import { DocNavigationItem, DocsNavigationBlock } from 'Docs/@types/types'; -import * as React from 'react'; -import { ButtonLink } from '../../../components/controls/buttons'; -import OpenCloseIcon from '../../../components/icons/OpenCloseIcon'; -import { isDocsNavigationBlock } from '../navTreeUtils'; -import { DocumentationEntry, getNodeFromUrl } from '../utils'; -import { MenuItem } from './MenuItem'; - -interface Props { - block: DocsNavigationBlock; - depth?: number; - openByDefault: boolean; - openChain: DocNavigationItem[]; - pages: DocumentationEntry[]; - splat: string; - title: string; -} - -interface State { - open: boolean; -} - -export default class MenuBlock extends React.PureComponent<Props, State> { - state: State; - - constructor(props: Props) { - super(props); - this.state = { - open: props.openByDefault !== undefined ? props.openByDefault : false - }; - } - - handleClick = () => { - this.setState(prevState => ({ - open: !prevState.open - })); - }; - - renderMenuItems = (block: DocsNavigationBlock): React.ReactNode => { - const { depth = 0, openChain, pages, splat } = this.props; - return block.children.map(item => { - if (typeof item === 'string') { - return ( - <MenuItem depth={depth + 1} key={item} node={getNodeFromUrl(pages, item)} splat={splat} /> - ); - } else if (isDocsNavigationBlock(item)) { - return ( - <MenuBlock - block={item} - depth={depth + 1} - key={item.title} - openByDefault={openChain.includes(item)} - openChain={openChain} - pages={pages} - splat={splat} - title={item.title} - /> - ); - } else { - return null; - } - }); - }; - - render() { - const { block, depth = 0, title } = this.props; - const { open } = this.state; - const maxDepth = Math.min(depth, 3); - return ( - <> - <ButtonLink - className={classNames('list-group-item', { [`depth-${maxDepth}`]: depth > 0 })} - onClick={this.handleClick}> - <h3 className="list-group-item-heading"> - <OpenCloseIcon className="little-spacer-right" open={open} /> - {title} - </h3> - </ButtonLink> - {open && this.renderMenuItems(block)} - </> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/documentation/components/MenuExternalLink.tsx b/server/sonar-web/src/main/js/apps/documentation/components/MenuExternalLink.tsx deleted file mode 100644 index 3d2bef9ac06..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/MenuExternalLink.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 DetachIcon from '../../../components/icons/DetachIcon'; - -interface Props { - title: string; - url: string; -} - -export function MenuExternalLink({ title, url }: Props) { - return ( - <a href={url} key={title} target="_blank" rel="noopener noreferrer"> - <h3 className="list-group-item-heading"> - <DetachIcon className="spacer-right" /> - {title} - </h3> - </a> - ); -} diff --git a/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx b/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx deleted file mode 100644 index e3fcf7f5498..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 classNames from 'classnames'; -import * as React from 'react'; -import Link from '../../../components/common/Link'; -import { testPathAgainstUrl } from '../navTreeUtils'; -import { DocumentationEntry } from '../utils'; - -interface Props { - depth?: number; - node: DocumentationEntry | undefined; - splat: string; -} - -export function MenuItem({ depth = 0, node, splat }: Props) { - if (!node) { - return null; - } - - const active = testPathAgainstUrl(node.url, splat); - const maxDepth = Math.min(depth, 3); - const title = node.navTitle || node.title; - - return ( - <Link - className={classNames('list-group-item', { active, [`depth-${maxDepth}`]: depth > 0 })} - key={node.url} - to={'/documentation' + node.url}> - <h3 className="list-group-item-heading" title={title}> - {title} - </h3> - </Link> - ); -} 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 deleted file mode 100644 index f563d5300aa..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 classNames from 'classnames'; -import * as React from 'react'; -import Link from '../../../components/common/Link'; -import { Dict } from '../../../types/types'; -import { cutWords, DocumentationEntry, highlightMarks } from '../utils'; - -export interface SearchResult { - exactMatch?: boolean; - highlights: Dict<[number, number][]>; - longestTerm: string; - page: DocumentationEntry; - query: string; -} - -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.url}> - <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; - const { text } = result.page; - let tokens: { - text: string; - marked: boolean; - }[] = []; - - if (result.exactMatch) { - const pageText = result.page.text.toLowerCase(); - const highlights: { from: number; to: number }[] = []; - let start = 0; - let index = pageText.indexOf(result.query, start); - let loopCount = 0; - - while (index > -1 && loopCount < 10) { - loopCount++; - highlights.push({ from: index, to: index + result.query.length }); - start = index + 1; - index = pageText.indexOf(result.query, start); - } - - if (highlights.length) { - tokens = highlightMarks(text, highlights); - } - } - - if (tokens.length === 0 && textHighlights && textHighlights.length > 0) { - tokens = highlightMarks( - text, - textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] })) - ); - } - - if (tokens.length) { - 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> - ))} - </> - ); -} 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 deleted file mode 100644 index 784909c3e40..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/SearchResults.tsx +++ /dev/null @@ -1,170 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { Dict, DocNavigationItem } from 'Docs/@types/types'; -import { sortBy } from 'lodash'; -import lunr, { LunrBuilder, LunrIndex, LunrToken } from 'lunr'; -import * as React from 'react'; -import { isDefined } from '../../../helpers/types'; -import { getUrlsList } from '../navTreeUtils'; -import { DocumentationEntry } from '../utils'; -import SearchResultEntry from './SearchResultEntry'; - -interface Props { - navigation: DocNavigationItem[]; - 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.use(tokenContextPlugin); - this.ref('relativeName'); - this.field('title', { boost: 10 }); - this.field('text'); - - this.metadataWhitelist = ['position', 'tokenContext']; - - const urlsList = getUrlsList(props.navigation); - props.pages.filter(page => urlsList.includes(page.url)).forEach(page => this.add(page)); - }); - } - - search(query: string) { - return this.index - .search( - query - .replace(/[\^\-+:~*]/g, '') - .split(/\s+/) - .map(s => `${s}~1 ${s}*`) - .join(' ') - ) - .map(match => { - const page = this.props.pages.find(p => p.relativeName === match.ref); - - // This should never happen, but provide this check for type safety. - if (!page) { - return undefined; - } - - const highlights: Dict<[number, number][]> = {}; - let longestTerm = ''; - let exactMatch = false; - - // Loop over all matching terms/tokens. - Object.keys(match.matchData.metadata).forEach(term => { - // Remember the longest term that matches the query as close as possible. - if (query.includes(term.toLowerCase()) && longestTerm.length < term.length) { - longestTerm = term; - } - - Object.keys(match.matchData.metadata[term]).forEach(fieldName => { - const { position: positions, tokenContext: tokenContexts } = match.matchData.metadata[ - term - ][fieldName]; - - highlights[fieldName] = [...(highlights[fieldName] || []), ...positions]; - - // Check if we have an *exact match*. - if (!exactMatch && tokenContexts) { - tokenContexts.forEach((tokenContext: string) => { - if (!exactMatch && tokenContext.includes(query)) { - exactMatch = true; - } - }); - } - }); - }); - - return { exactMatch, highlights, longestTerm, page, query }; - }) - .filter(isDefined); - } - - render() { - const query = this.props.query?.toLowerCase(); - - if (!query) { - return null; - } - - const results = this.search(query); - - // Re-order results by the length of the longest matched term and by exact - // match (if applicable). The longer the matched term is, the higher the - // chance the result is more relevant. - const sortedResults = sortBy( - // Sort by longest term. - sortBy(results, result => -result.longestTerm.length), - // Sort by exact match. - result => result.exactMatch && -1 - ); - - return ( - <> - {sortedResults.map(result => ( - <SearchResultEntry - active={result.page.relativeName === this.props.splat} - key={result.page.relativeName} - result={result} - /> - ))} - </> - ); - } -} - -// Lunr doesn't support exact multiple-term matching. Meaning "foo bar" will not -// boost a sentence like "Foo bar baz" more than "Baz bar foo". In order to -// provide more accurate results, we store the token context, to see if we can -// perform an "exact match". Unfortunately, we cannot extend the search logic, -// only the tokenizer at *index time*. This is why we store the context as -// meta-data, and post-process the matches before rendering (see above). For -// performance reasons, we only add 2 extra tokens, one in front, one after. -// This means we support "exact macthing" for up to 3 terms. More search terms -// would fallback to the regular matching algorithm, which is OK: the more terms -// searched for, the better the standard algorithm will perform anyway. In the -// end, the best would be for Lunr to support multi-term matching, as extending -// the search algorithm for this would be way too complicated. -export function tokenContextPluginCallback(token: LunrToken, index: number, tokens: LunrToken[]) { - const prevToken = tokens[index - 1] || ''; - const nextToken = tokens[index + 1] || ''; - token.metadata['tokenContext'] = [prevToken.toString(), token.toString(), nextToken.toString()] - .filter(s => s.length) - .join(' ') - .toLowerCase(); - return token; -} - -let tokenContextPluginRegistered = false; - -export function tokenContextPlugin(builder: LunrBuilder) { - if (!tokenContextPluginRegistered) { - (lunr as any).Pipeline.registerFunction(tokenContextPluginCallback, 'tokenContext'); - tokenContextPluginRegistered = true; - } - - builder.pipeline.before((lunr as any).stemmer, tokenContextPluginCallback); - builder.metadataWhitelist.push('tokenContext'); -} 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 deleted file mode 100644 index b581b04f3e2..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/Sidebar.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { DocNavigationItem } from 'Docs/@types/types'; -import * as React from 'react'; -import SearchBox from '../../../components/controls/SearchBox'; -import { DocumentationEntry } from '../utils'; -import Menu from './Menu'; -import SearchResults from './SearchResults'; - -interface Props { - navigation: DocNavigationItem[]; - 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: query.trim() }); - }; - - 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="documentation-results panel"> - <div className="list-group"> - <SearchResults - navigation={this.props.navigation} - pages={this.props.pages} - query={this.state.query} - splat={this.props.splat} - /> - - {!this.state.query && ( - <Menu - navigation={this.props.navigation} - pages={this.props.pages} - splat={this.props.splat} - /> - )} - </div> - </div> - </> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx deleted file mode 100644 index e19bb2e155e..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { addSideBarClass, removeSideBarClass } from '../../../../helpers/pages'; -import { request } from '../../../../helpers/request'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { InstalledPlugin } from '../../../../types/plugins'; -import getPages from '../../pages'; -import { App } from '../App'; - -jest.mock('../../../../components/common/ScreenPositionHelper'); - -jest.mock('Docs/../static/SonarQubeNavigationTree.json', () => [ - { - title: 'SonarQube', - children: [ - '/lorem/ipsum/', - '/analysis/languages/csharp/', - { - title: 'Child category', - children: [ - '/lorem/ipsum/dolor', - { - title: 'Grandchild category', - children: ['/lorem/ipsum/sit'] - }, - '/lorem/ipsum/amet' - ] - } - ] - } -]); - -jest.mock('../../../../helpers/pages', () => ({ - addSideBarClass: jest.fn(), - removeSideBarClass: jest.fn() -})); - -jest.mock('../../../../helpers/request', () => { - const { mockDocumentationMarkdown } = jest.requireActual('../../../../helpers/testMocks'); - return { - request: jest.fn(() => ({ - submit: jest.fn().mockResolvedValue({ - status: 200, - text: jest.fn().mockResolvedValue(mockDocumentationMarkdown({ key: 'csharp' })) - }) - })) - }; -}); - -jest.mock('../../pages', () => { - const { mockDocumentationEntry } = jest.requireActual('../../../../helpers/testMocks'); - return jest - .fn() - .mockReturnValue([ - mockDocumentationEntry(), - mockDocumentationEntry({ url: '/analysis/languages/csharp/' }) - ]); -}); - -jest.mock('../../../../api/plugins', () => ({ - getInstalledPlugins: jest.fn().mockResolvedValue([ - { - key: 'csharp', - documentationPath: 'static/documentation.md', - issueTrackerUrl: 'csharp_plugin_issue_tracker_url' - }, - { key: 'vbnet', documentationPath: 'Sstatic/documentation.md' }, - { key: 'vbnett', documentationPath: undefined } - ] as InstalledPlugin[]) -})); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -it('should render correctly for SonarQube', async () => { - const wrapper = shallowRender(); - expect(wrapper.find('DeferredSpinner').exists()).toBe(true); - expect(addSideBarClass).toHaveBeenCalled(); - - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('ScreenPositionHelper').dive()).toMatchSnapshot(); - - wrapper.unmount(); - expect(removeSideBarClass).toHaveBeenCalled(); -}); - -it("should show a 404 if the page doesn't exist", async () => { - const wrapper = shallowRender({ params: { splat: 'unknown' } }); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should try to fetch language plugin documentation if documentationPath matches', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - expect(request).toHaveBeenCalledWith('/static/csharp/documentation.md'); - expect(request).not.toHaveBeenCalledWith('/static/vbnet/documentation.md'); - expect(request).not.toHaveBeenCalledWith('/static/vbnett/documentation.md'); - expect(getPages).toHaveBeenCalledWith( - expect.objectContaining({ - 'analysis/languages/csharp': expect.any(Object) - }) - ); -}); - -it('should display the issue tracker url of the plugin if it exists', async () => { - const wrapper = shallowRender({ params: { splat: 'analysis/languages/csharp/' } }); - await waitAndUpdate(wrapper); - - const { content } = (getPages as jest.Mock).mock.calls[0][0]['analysis/languages/csharp']; - - expect(content).toContain('csharp_plugin_issue_tracker_url'); -}); - -function shallowRender(props: Partial<App['props']> = {}) { - return shallow(<App params={{ splat: 'lorem/ipsum' }} location={{ hash: '#foo' }} {...props} />); -} 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 deleted file mode 100644 index f5da4e84c22..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Menu-test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import Menu from '../Menu'; - -function createPage(title: string, relativeName: string, text = '') { - return { relativeName, url: '/' + relativeName, title, navTitle: undefined, 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', () => { - const wrapper = shallow( - <Menu - navigation={[{ title: 'Block', children: ['/lorem/index', '/lorem/origin'] }, 'foobar']} - pages={pages} - splat="lorem/origin" - /> - ); - - expect(wrapper).toMatchSnapshot(); - wrapper.setProps({ splat: 'baz/bar' }); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuBlock-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuBlock-test.tsx deleted file mode 100644 index c2ba9a70683..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuBlock-test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { click } from '../../../../helpers/testUtils'; -import MenuBlock from '../MenuBlock'; - -it('should render a closed menu block', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should render an opened menu block', () => { - expect(shallowRender({ openByDefault: true })).toMatchSnapshot(); -}); - -it('should not render a high depth differently than a depth of 3', () => { - expect( - shallowRender({ block: { title: 'Foo', children: ['/foo'] }, depth: 6 }) - ).toMatchSnapshot(); -}); - -it('can be opened and closed', () => { - const wrapper = shallowRender(); - expect(wrapper.state('open')).toBe(false); - click(wrapper.find('ButtonLink')); - expect(wrapper.state('open')).toBe(true); -}); - -function shallowRender(props: Partial<MenuBlock['props']> = {}) { - return shallow( - <MenuBlock - block={{ - title: 'Foo', - children: [ - '/bar/', - '/baz/', - { - title: 'Baz', - children: ['/baz/foo'] - }, - { - title: 'Bar', - url: 'http://example.com' - } - ] - }} - openByDefault={false} - openChain={[]} - pages={[ - { - content: 'bar', - relativeName: '/bar/', - text: 'bar', - title: 'Bar', - navTitle: undefined, - url: '/bar/' - }, - { - content: 'baz', - relativeName: '/baz/', - text: 'baz', - title: 'baz', - navTitle: 'baznav', - url: '/baz/' - } - ]} - splat="/foo/" - title="Foo" - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuItem-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuItem-test.tsx deleted file mode 100644 index bada3d99f90..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuItem-test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { DocumentationEntry } from '../../utils'; -import { MenuItem } from '../MenuItem'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should render correctly if the current node matches the splat', () => { - expect(shallowRender({ splat: 'bar' })).toMatchSnapshot(); -}); - -it('should not render a high depth differently than a depth of 3', () => { - expect(shallowRender({ depth: 6 })).toMatchSnapshot(); -}); - -function shallowRender(props = {}) { - return shallow(<MenuItem node={{ url: '/bar' } as DocumentationEntry} splat="foo" {...props} />); -} 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 deleted file mode 100644 index 34046888c4f..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResultEntry-test.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import SearchResultEntry, { - SearchResult, - SearchResultText, - SearchResultTitle, - SearchResultTokens -} from '../SearchResultEntry'; - -describe('SearchResultEntry', () => { - it('should render', () => { - expect( - shallow(<SearchResultEntry active={true} result={mockSearchResult()} />) - ).toMatchSnapshot(); - }); -}); - -describe('SearchResultText', () => { - it('should render with highlights', () => { - expect( - shallow(<SearchResultText result={mockSearchResult({ highlights: { text: [[12, 9]] } })} />) - ).toMatchSnapshot(); - }); - - it('should correctly extract exact matches', () => { - expect( - shallow( - <SearchResultText - result={mockSearchResult({ exactMatch: true, query: 'variable understood' })} - /> - ) - ).toMatchSnapshot(); - }); - - it('should render without highlights', () => { - expect(shallow(<SearchResultText result={mockSearchResult()} />)).toMatchSnapshot(); - }); -}); - -describe('SearchResultTitle', () => { - it('should render with highlights', () => { - expect( - shallow(<SearchResultTitle result={mockSearchResult({ highlights: { title: [[0, 6]] } })} />) - ).toMatchSnapshot(); - }); - - it('should render not without highlights', () => { - expect(shallow(<SearchResultTitle result={mockSearchResult()} />)).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(); - }); -}); - -function mockSearchResult(overrides: Partial<SearchResult> = {}) { - return { - page: { - content: '', - relativeName: 'foo/bar', - url: '/foo/bar', - text: 'Foobar is a universal variable understood to represent whatever is being discussed.', - title: 'Foobar', - navTitle: undefined - }, - highlights: {}, - longestTerm: '', - query: '', - ...overrides - }; -} 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 deleted file mode 100644 index 429d6b4ebaf..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResults-test.tsx +++ /dev/null @@ -1,262 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import lunr, { LunrBuilder, LunrToken } from 'lunr'; -import * as React from 'react'; -import { mockDocumentationEntry } from '../../../../helpers/testMocks'; -import { getUrlsList } from '../../navTreeUtils'; -import { DocumentationEntry } from '../../utils'; -import SearchResultEntry from '../SearchResultEntry'; -import SearchResults, { tokenContextPlugin, tokenContextPluginCallback } from '../SearchResults'; - -jest.mock('../../navTreeUtils', () => ({ - getUrlsList: jest.fn().mockReturnValue([]) -})); - -jest.mock('lunr', () => { - const lunr = jest.fn(() => ({ - search: jest.fn(() => [ - { - ref: 'lorem/origin', - matchData: { - metadata: { - simply: { - title: { position: [[19, 5]] }, - text: { - position: [ - [15, 6], - [28, 4] - ], - tokenContext: ['is simply dummy', 'simply dummy text'] - } - } - } - } - }, - { - ref: 'foobar', - matchData: { - metadata: { - simply: { - title: { position: [[23, 4]] }, - text: { - position: [ - [111, 6], - [118, 4] - ], - tokenContext: ['dummy simply text'] - } - } - } - } - } - ]) - })); - - (lunr as any).Pipeline = { - registerFunction: jest.fn() - }; - - return lunr; -}); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ query: '' }).type()).toBeNull(); -}); - -describe('search engine', () => { - class LunrIndexMock { - plugins: Function[] = []; - fields: Array<{ field: string; args: any }> = []; - docs: DocumentationEntry[] = []; - metadataWhitelist: string[] = []; - - use(fn: Function) { - this.plugins.push(fn); - } - - ref(_ref: string) { - /* noop */ - } - - field(field: string, args = {}) { - this.fields.push({ field, args }); - } - - add(doc: DocumentationEntry) { - this.docs.push(doc); - } - } - - it('should correctly populate the index', () => { - (getUrlsList as jest.Mock).mockReturnValueOnce(['/lorem/index', '/lorem/origin']); - - shallowRender(); - - // Fetch the callback passed to lunr(), which serves as the index constructor. - const indexConstructor: Function = (lunr as jest.Mock).mock.calls[0][0]; - - // Apply it to our mock index. - const lunrMock = new LunrIndexMock(); - indexConstructor.apply(lunrMock); - - expect(lunrMock.docs.length).toBe(2); - expect(lunrMock.plugins).toContain(tokenContextPlugin); - expect(lunrMock.metadataWhitelist).toEqual(['position', 'tokenContext']); - expect(lunrMock.fields).toEqual([ - expect.objectContaining({ field: 'title', args: { boost: 10 } }), - expect.objectContaining({ field: 'text' }) - ]); - }); - - it('should correctly look for an exact match', () => { - // No exact match, should sort as the matches came in. - const wrapper = shallowRender({ query: 'text simply' }); - expect( - wrapper - .find(SearchResultEntry) - .at(0) - .props().result.page.relativeName - ).toBe('lorem/origin'); - - // Exact match, specific page should be at the top. - wrapper.setProps({ query: 'simply text' }); - expect( - wrapper - .find(SearchResultEntry) - .at(0) - .props().result.page.relativeName - ).toBe('foobar'); - }); - - it('should trigger a search if query is set', () => { - const wrapper = shallowRender({ query: undefined }); - expect(wrapper.instance().index.search).not.toHaveBeenCalled(); - wrapper.setProps({ query: 'si:+mply text' }); - expect(wrapper.instance().index.search).toHaveBeenCalledWith('simply~1 simply* text~1 text*'); - }); -}); - -describe('tokenContextPluginCallback', () => { - class LunrTokenMock { - str: string; - metadata: any; - - constructor(str: string) { - this.str = str; - this.metadata = {}; - } - - toString() { - return this.str; - } - } - - class LunrBuilderMock { - pipeline: { before: (stemmer: any, cb: Function) => void }; - metadataWhitelist: string[]; - - constructor() { - this.pipeline = { - before: () => { - /* noop */ - } - }; - this.metadataWhitelist = []; - } - } - - function mockLunrToken(str: string): LunrToken { - return new LunrTokenMock(str); - } - - function mockLunrBuilder(): LunrBuilder { - return new LunrBuilderMock(); - } - - it('should correctly provide token context for text', () => { - const tokens = [ - mockLunrToken('this'), - mockLunrToken('is'), - mockLunrToken('some'), - mockLunrToken('text') - ]; - - expect(tokenContextPluginCallback(mockLunrToken('this'), 0, tokens).metadata).toEqual( - expect.objectContaining({ tokenContext: 'this is' }) - ); - expect(tokenContextPluginCallback(mockLunrToken('is'), 1, tokens).metadata).toEqual( - expect.objectContaining({ tokenContext: 'this is some' }) - ); - expect(tokenContextPluginCallback(mockLunrToken('some'), 2, tokens).metadata).toEqual( - expect.objectContaining({ tokenContext: 'is some text' }) - ); - expect(tokenContextPluginCallback(mockLunrToken('text'), 3, tokens).metadata).toEqual( - expect.objectContaining({ tokenContext: 'some text' }) - ); - }); - - it('should only register the plugin once', () => { - tokenContextPlugin(mockLunrBuilder()); - tokenContextPlugin(mockLunrBuilder()); - expect((lunr as any).Pipeline.registerFunction).toHaveBeenCalledTimes(1); - }); -}); - -function shallowRender(props: Partial<SearchResults['props']> = {}) { - return shallow<SearchResults>( - <SearchResults - navigation={['lorem/index', 'lorem/origin', 'foobar']} - pages={[ - mockDocumentationEntry({ - title: 'Lorem Ipsum', - relativeName: 'lorem/index', - url: '/lorem/index', - content: - "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.", - text: - "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." - }), - mockDocumentationEntry({ - title: 'Where does it come from?', - relativeName: 'lorem/origin', - url: '/lorem/origin', - 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.', - 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.' - }), - mockDocumentationEntry({ - title: 'Where does Foobar come from?', - relativeName: 'foobar', - url: '/foobar', - content: - 'Foobar is a universal variable understood to represent whatever is being discussed. Now we need some keywords: simply text.', - text: - 'Foobar is a universal variable understood to represent whatever is being discussed. Now we need some keywords: simply text.' - }) - ]} - query="what is 42" - splat="foobar" - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Sidebar-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Sidebar-test.tsx deleted file mode 100644 index 27540d3c1fa..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/Sidebar-test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import Sidebar from '../Sidebar'; - -function createPage(title: string, relativeName: string, text = '') { - return { relativeName, url: '/' + relativeName, title, navTitle: undefined, text, content: text }; -} - -const pages = [ - createPage('Lorem Ipsum', 'lorem/index'), - createPage('Where does Foobar come from?', 'foobar') -]; - -it('should render menu', () => { - expect( - shallow( - <Sidebar - navigation={[{ title: 'Block', children: ['/lorem/index'] }, 'foobar']} - pages={pages} - splat="foobar" - /> - ) - ).toMatchSnapshot(); -}); - -it('should search', () => { - const wrapper = shallow( - <Sidebar - navigation={[{ title: 'Block', children: ['/lorem/index'] }, 'foobar']} - pages={pages} - splat="foobar" - /> - ); - wrapper.find('SearchBox').prop<Function>('onChange')('foo'); - wrapper.update(); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/App-test.tsx.snap deleted file mode 100644 index 690037a28e2..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/App-test.tsx.snap +++ /dev/null @@ -1,151 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly for SonarQube 1`] = ` -<div - className="layout-page" -> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="Lorem | documentation.page_title.sonarqube" - > - <meta - content="noindex nofollow" - name="robots" - /> - </Helmet> - <ScreenPositionHelper - className="layout-page-side-outer" - > - <Component /> - </ScreenPositionHelper> - <div - className="layout-page-main" - > - <div - className="layout-page-main-inner" - > - <div - className="boxed-group" - > - <A11ySkipTarget - anchor="documentation_main" - /> - <DocMarkdownBlock - className="documentation-content cut-margins boxed-group-inner" - content="Lorem ipsum dolor sit amet fredum" - scrollToHref="#foo" - stickyToc={true} - title="Lorem" - /> - </div> - </div> - </div> -</div> -`; - -exports[`should render correctly for SonarQube 2`] = ` -<div - className="layout-page-side" - style={ - Object { - "top": 0, - } - } -> - <div - className="layout-page-side-inner" - > - <div - className="layout-page-filters" - > - <div - className="documentation-page-header" - > - <A11ySkipTarget - anchor="documentation_menu" - label="documentation.skip_to_nav" - weight={10} - /> - <ForwardRef(Link) - to="/documentation/" - > - <h1> - documentation.page - </h1> - </ForwardRef(Link)> - </div> - <Sidebar - navigation={ - Array [ - Object { - "children": Array [ - "/lorem/ipsum/", - "/analysis/languages/csharp/", - Object { - "children": Array [ - "/lorem/ipsum/dolor", - Object { - "children": Array [ - "/lorem/ipsum/sit", - ], - "title": "Grandchild category", - }, - "/lorem/ipsum/amet", - ], - "title": "Child category", - }, - ], - "title": "SonarQube", - }, - ] - } - pages={ - Array [ - Object { - "content": "Lorem ipsum dolor sit amet fredum", - "navTitle": undefined, - "relativeName": "Lorem", - "text": "Lorem ipsum dolor sit amet fredum", - "title": "Lorem", - "url": "/lorem/ipsum", - }, - Object { - "content": "Lorem ipsum dolor sit amet fredum", - "navTitle": undefined, - "relativeName": "Lorem", - "text": "Lorem ipsum dolor sit amet fredum", - "title": "Lorem", - "url": "/analysis/languages/csharp/", - }, - ] - } - splat="lorem/ipsum" - /> - </div> - </div> -</div> -`; - -exports[`should show a 404 if the page doesn't exist 1`] = ` -<Fragment> - <Helmet - defer={true} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="documentation.page_title.sonarqube" - > - <meta - content="noindex nofollow" - name="robots" - /> - </Helmet> - <A11ySkipTarget - anchor="documentation_main" - /> - <NotFound - withContainer={false} - /> -</Fragment> -`; 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 deleted file mode 100644 index 7e95338634f..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Menu-test.tsx.snap +++ /dev/null @@ -1,118 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render hierarchical menu 1`] = ` -<Fragment> - <MenuBlock - block={ - Object { - "children": Array [ - "/lorem/index", - "/lorem/origin", - ], - "title": "Block", - } - } - key="Block" - openByDefault={true} - openChain={ - Array [ - Object { - "children": Array [ - "/lorem/index", - "/lorem/origin", - ], - "title": "Block", - }, - "/lorem/origin", - ] - } - pages={ - Array [ - Object { - "content": "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.", - "navTitle": undefined, - "relativeName": "lorem/index", - "text": "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.", - "title": "Lorem Ipsum", - "url": "/lorem/index", - }, - 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.", - "navTitle": undefined, - "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?", - "url": "/lorem/origin", - }, - Object { - "content": "Foobar is a universal variable understood to represent whatever is being discussed.", - "navTitle": undefined, - "relativeName": "foobar", - "text": "Foobar is a universal variable understood to represent whatever is being discussed.", - "title": "Where does Foobar come from?", - "url": "/foobar", - }, - ] - } - splat="lorem/origin" - title="Block" - /> - <MenuItem - key="foobar" - splat="lorem/origin" - /> -</Fragment> -`; - -exports[`should render hierarchical menu 2`] = ` -<Fragment> - <MenuBlock - block={ - Object { - "children": Array [ - "/lorem/index", - "/lorem/origin", - ], - "title": "Block", - } - } - key="Block" - openByDefault={false} - openChain={Array []} - pages={ - Array [ - Object { - "content": "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.", - "navTitle": undefined, - "relativeName": "lorem/index", - "text": "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.", - "title": "Lorem Ipsum", - "url": "/lorem/index", - }, - 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.", - "navTitle": undefined, - "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?", - "url": "/lorem/origin", - }, - Object { - "content": "Foobar is a universal variable understood to represent whatever is being discussed.", - "navTitle": undefined, - "relativeName": "foobar", - "text": "Foobar is a universal variable understood to represent whatever is being discussed.", - "title": "Where does Foobar come from?", - "url": "/foobar", - }, - ] - } - splat="baz/bar" - title="Block" - /> - <MenuItem - key="foobar" - splat="baz/bar" - /> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuBlock-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuBlock-test.tsx.snap deleted file mode 100644 index 6d5600201c8..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuBlock-test.tsx.snap +++ /dev/null @@ -1,124 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should not render a high depth differently than a depth of 3 1`] = ` -<Fragment> - <ButtonLink - className="list-group-item depth-3" - onClick={[Function]} - > - <h3 - className="list-group-item-heading" - > - <OpenCloseIcon - className="little-spacer-right" - open={false} - /> - Foo - </h3> - </ButtonLink> -</Fragment> -`; - -exports[`should render a closed menu block 1`] = ` -<Fragment> - <ButtonLink - className="list-group-item" - onClick={[Function]} - > - <h3 - className="list-group-item-heading" - > - <OpenCloseIcon - className="little-spacer-right" - open={false} - /> - Foo - </h3> - </ButtonLink> -</Fragment> -`; - -exports[`should render an opened menu block 1`] = ` -<Fragment> - <ButtonLink - className="list-group-item" - onClick={[Function]} - > - <h3 - className="list-group-item-heading" - > - <OpenCloseIcon - className="little-spacer-right" - open={true} - /> - Foo - </h3> - </ButtonLink> - <MenuItem - depth={1} - key="/bar/" - node={ - Object { - "content": "bar", - "navTitle": undefined, - "relativeName": "/bar/", - "text": "bar", - "title": "Bar", - "url": "/bar/", - } - } - splat="/foo/" - /> - <MenuItem - depth={1} - key="/baz/" - node={ - Object { - "content": "baz", - "navTitle": "baznav", - "relativeName": "/baz/", - "text": "baz", - "title": "baz", - "url": "/baz/", - } - } - splat="/foo/" - /> - <MenuBlock - block={ - Object { - "children": Array [ - "/baz/foo", - ], - "title": "Baz", - } - } - depth={1} - key="Baz" - openByDefault={false} - openChain={Array []} - pages={ - Array [ - Object { - "content": "bar", - "navTitle": undefined, - "relativeName": "/bar/", - "text": "bar", - "title": "Bar", - "url": "/bar/", - }, - Object { - "content": "baz", - "navTitle": "baznav", - "relativeName": "/baz/", - "text": "baz", - "title": "baz", - "url": "/baz/", - }, - ] - } - splat="/foo/" - title="Baz" - /> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuItem-test.tsx.snap deleted file mode 100644 index 6eb967c4ed3..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuItem-test.tsx.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should not render a high depth differently than a depth of 3 1`] = ` -<ForwardRef(Link) - className="list-group-item depth-3" - key="/bar" - to="/documentation/bar" -> - <h3 - className="list-group-item-heading" - /> -</ForwardRef(Link)> -`; - -exports[`should render correctly 1`] = ` -<ForwardRef(Link) - className="list-group-item" - key="/bar" - to="/documentation/bar" -> - <h3 - className="list-group-item-heading" - /> -</ForwardRef(Link)> -`; - -exports[`should render correctly if the current node matches the splat 1`] = ` -<ForwardRef(Link) - className="list-group-item active" - key="/bar" - to="/documentation/bar" -> - <h3 - className="list-group-item-heading" - /> -</ForwardRef(Link)> -`; 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 deleted file mode 100644 index 9537e85112a..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap +++ /dev/null @@ -1,142 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SearchResultEntry should render 1`] = ` -<ForwardRef(Link) - className="list-group-item active" - to="/documentation/foo/bar" -> - <SearchResultTitle - result={ - Object { - "highlights": Object {}, - "longestTerm": "", - "page": Object { - "content": "", - "navTitle": undefined, - "relativeName": "foo/bar", - "text": "Foobar is a universal variable understood to represent whatever is being discussed.", - "title": "Foobar", - "url": "/foo/bar", - }, - "query": "", - } - } - /> - <SearchResultText - result={ - Object { - "highlights": Object {}, - "longestTerm": "", - "page": Object { - "content": "", - "navTitle": undefined, - "relativeName": "foo/bar", - "text": "Foobar is a universal variable understood to represent whatever is being discussed.", - "title": "Foobar", - "url": "/foo/bar", - }, - "query": "", - } - } - /> -</ForwardRef(Link)> -`; - -exports[`SearchResultText should correctly extract exact matches 1`] = ` -<div - className="note" -> - <SearchResultTokens - tokens={ - Array [ - Object { - "marked": false, - "text": "Foobar is a universal ", - }, - Object { - "marked": true, - "text": "variable understood", - }, - Object { - "marked": false, - "text": " to represent whatever is being discussed.", - }, - ] - } - /> -</div> -`; - -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`] = ` -<Fragment> - Foobar is a - <mark - key="1" - > - universal - </mark> - variable understood to represent whatever is being discussed. -</Fragment> -`; 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 deleted file mode 100644 index ae487ab0528..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResults-test.tsx.snap +++ /dev/null @@ -1,80 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: default 1`] = ` -<Fragment> - <SearchResultEntry - active={false} - key="lorem/origin" - result={ - Object { - "exactMatch": false, - "highlights": Object { - "text": Array [ - Array [ - 15, - 6, - ], - Array [ - 28, - 4, - ], - ], - "title": Array [ - Array [ - 19, - 5, - ], - ], - }, - "longestTerm": "", - "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.", - "navTitle": undefined, - "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?", - "url": "/lorem/origin", - }, - "query": "what is 42", - } - } - /> - <SearchResultEntry - active={true} - key="foobar" - result={ - Object { - "exactMatch": false, - "highlights": Object { - "text": Array [ - Array [ - 111, - 6, - ], - Array [ - 118, - 4, - ], - ], - "title": Array [ - Array [ - 23, - 4, - ], - ], - }, - "longestTerm": "", - "page": Object { - "content": "Foobar is a universal variable understood to represent whatever is being discussed. Now we need some keywords: simply text.", - "navTitle": undefined, - "relativeName": "foobar", - "text": "Foobar is a universal variable understood to represent whatever is being discussed. Now we need some keywords: simply text.", - "title": "Where does Foobar come from?", - "url": "/foobar", - }, - "query": "what is 42", - } - } - /> -</Fragment> -`; 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 deleted file mode 100644 index b83396b9873..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Sidebar-test.tsx.snap +++ /dev/null @@ -1,145 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render menu 1`] = ` -<Fragment> - <SearchBox - className="big-spacer-top spacer-bottom" - minLength={2} - onChange={[Function]} - placeholder="Search for pages or keywords" - value="" - /> - <div - className="documentation-results panel" - > - <div - className="list-group" - > - <SearchResults - navigation={ - Array [ - Object { - "children": Array [ - "/lorem/index", - ], - "title": "Block", - }, - "foobar", - ] - } - pages={ - Array [ - Object { - "content": "", - "navTitle": undefined, - "relativeName": "lorem/index", - "text": "", - "title": "Lorem Ipsum", - "url": "/lorem/index", - }, - Object { - "content": "", - "navTitle": undefined, - "relativeName": "foobar", - "text": "", - "title": "Where does Foobar come from?", - "url": "/foobar", - }, - ] - } - query="" - splat="foobar" - /> - <Menu - navigation={ - Array [ - Object { - "children": Array [ - "/lorem/index", - ], - "title": "Block", - }, - "foobar", - ] - } - pages={ - Array [ - Object { - "content": "", - "navTitle": undefined, - "relativeName": "lorem/index", - "text": "", - "title": "Lorem Ipsum", - "url": "/lorem/index", - }, - Object { - "content": "", - "navTitle": undefined, - "relativeName": "foobar", - "text": "", - "title": "Where does Foobar come from?", - "url": "/foobar", - }, - ] - } - splat="foobar" - /> - </div> - </div> -</Fragment> -`; - -exports[`should search 1`] = ` -<Fragment> - <SearchBox - className="big-spacer-top spacer-bottom" - minLength={2} - onChange={[Function]} - placeholder="Search for pages or keywords" - value="foo" - /> - <div - className="documentation-results panel" - > - <div - className="list-group" - > - <SearchResults - navigation={ - Array [ - Object { - "children": Array [ - "/lorem/index", - ], - "title": "Block", - }, - "foobar", - ] - } - pages={ - Array [ - Object { - "content": "", - "navTitle": undefined, - "relativeName": "lorem/index", - "text": "", - "title": "Lorem Ipsum", - "url": "/lorem/index", - }, - Object { - "content": "", - "navTitle": undefined, - "relativeName": "foobar", - "text": "", - "title": "Where does Foobar come from?", - "url": "/foobar", - }, - ] - } - query="foo" - splat="foobar" - /> - </div> - </div> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/documentation/documentation.directory-loader.js b/server/sonar-web/src/main/js/apps/documentation/documentation.directory-loader.js deleted file mode 100644 index 3092d16f949..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/documentation.directory-loader.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 path = require('path'); - -module.exports = { - placeholder: true // doesn't matter, this is replaced by esbuild -}; diff --git a/server/sonar-web/src/main/js/apps/documentation/navTreeUtils.ts b/server/sonar-web/src/main/js/apps/documentation/navTreeUtils.ts deleted file mode 100644 index 84b95eff83b..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/navTreeUtils.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 NavigationTree from 'Docs/../static/SonarQubeNavigationTree.json'; -import { - DocNavigationItem, - DocsNavigationBlock, - DocsNavigationExternalLink -} from 'Docs/@types/types'; -import { flatten } from 'lodash'; - -export function getNavTree() { - return NavigationTree as DocNavigationItem[]; -} - -export function getUrlsList(navTree: DocNavigationItem[]): string[] { - return flatten( - navTree.map(leaf => { - if (isDocsNavigationBlock(leaf)) { - return getUrlsList(leaf.children); - } - if (isDocsNavigationExternalLink(leaf)) { - return [leaf.url]; - } - return [leaf]; - }) - ); -} - -export function getOpenChainFromPath(pathname: string, navTree: DocNavigationItem[]) { - let chain: DocNavigationItem[] = []; - - let found = false; - const walk = (leaf: DocNavigationItem, parents: DocNavigationItem[] = []) => { - if (found) { - return; - } - - parents = parents.concat(leaf); - - if (isDocsNavigationBlock(leaf)) { - leaf.children.forEach(child => { - if (typeof child === 'string' && testPathAgainstUrl(child, pathname)) { - chain = parents.concat(child); - found = true; - } else { - walk(child, parents); - } - }); - } else if (typeof leaf === 'string' && testPathAgainstUrl(leaf, pathname)) { - chain = parents; - found = true; - } - }; - - navTree.forEach(leaf => walk(leaf)); - - return chain; -} - -export function isDocsNavigationBlock(leaf?: DocNavigationItem): leaf is DocsNavigationBlock { - return typeof leaf === 'object' && (leaf as DocsNavigationBlock).children !== undefined; -} - -export function isDocsNavigationExternalLink( - leaf?: DocNavigationItem -): leaf is DocsNavigationExternalLink { - return typeof leaf === 'object' && (leaf as DocsNavigationExternalLink).url !== undefined; -} - -export function testPathAgainstUrl(path: string, url: string) { - const leadingRegEx = /^\//; - const trailingRegEx = /\/$/; - return ( - path.replace(leadingRegEx, '').replace(trailingRegEx, '') === - url.replace(leadingRegEx, '').replace(trailingRegEx, '') - ); -} diff --git a/server/sonar-web/src/main/js/apps/documentation/pages.ts b/server/sonar-web/src/main/js/apps/documentation/pages.ts deleted file mode 100644 index 11b8dca96a7..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/pages.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 visit from 'unist-util-visit'; -import { filterContent, ParsedContent, separateFrontMatter } from '../../helpers/markdown'; -import { Dict } from '../../types/types'; -import Docs from './documentation.directory-loader'; -import { DocumentationEntry, DocumentationEntryScope } from './utils'; - -export default function getPages(parsedOverrides: Dict<ParsedContent> = {}): DocumentationEntry[] { - // Get entries, merge with overrides if applicable. - const pages = ((Docs as unknown) as Array<{ content: string; path: string }>).map(file => { - let parsed = separateFrontMatter(file.content); - - if (parsedOverrides[file.path]) { - const parsedOverride = parsedOverrides[file.path]; - parsed = { - content: parsedOverride.content, - frontmatter: { ...parsed.frontmatter, ...parsedOverride.frontmatter } - }; - delete parsedOverrides[file.path]; - } - - return { parsed, file }; - }); - - // Add new entries. - Object.keys(parsedOverrides).forEach(path => { - const parsed = parsedOverrides[path]; - pages.push({ - parsed, - file: { content: parsed.content, path } - }); - }); - - return pages.map(({ parsed, file }) => { - let content = ''; - let text = ''; - try { - content = filterContent(parsed.content); - text = getText(content); - } catch (e) { - /* eslint-disable-next-line no-console */ - console.error( - `Documentation - an error occured while parsing page "${parsed.frontmatter.url || - file.path}":`, - e - ); - } - - return { - relativeName: file.path, - url: parsed.frontmatter.url || `/${file.path}/`, - title: parsed.frontmatter.title, - navTitle: parsed.frontmatter.nav || undefined, - order: Number(parsed.frontmatter.order || -1), - scope: parsed.frontmatter.scope - ? (parsed.frontmatter.scope.toLowerCase() as DocumentationEntryScope) - : undefined, - text, - content - }; - }); -} - -function getText(content: string) { - const ast = remark().parse(content); - const texts: string[] = []; - visit(ast, node => { - if (node.type === `text` || node.type === `inlineCode`) { - texts.push(node.value); - } - }); - return texts.join(' ').replace(/\s+/g, ' '); -} diff --git a/server/sonar-web/src/main/js/apps/documentation/routes.tsx b/server/sonar-web/src/main/js/apps/documentation/routes.tsx deleted file mode 100644 index c7be80023d0..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/routes.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import React from 'react'; -import { Route } from 'react-router-dom'; -import App from './components/App'; - -const routes = () => ( - <Route path="documentation"> - <Route index={true} element={<App />} /> - <Route path="*" element={<App />} /> - </Route> -); - -export default routes; diff --git a/server/sonar-web/src/main/js/apps/documentation/styles.css b/server/sonar-web/src/main/js/apps/documentation/styles.css deleted file mode 100644 index 599e453ead6..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/styles.css +++ /dev/null @@ -1,201 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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. - */ -.documentation-page-header { - margin: 10px 20px; -} - -.documentation-footer div, -.documentation-footer .page-footer-menu { - max-width: 740px; -} - -.documentation-content { - padding: calc(4 * var(--gridSize)) calc(8 * var(--gridSize)); -} - -.documentation-content.markdown { - position: relative; - font-size: 16px; - line-height: 1.7; -} - -.documentation-content .markdown-content { - width: 100%; -} - -.documentation-content.markdown iframe { - width: 100%; - border: 0; - overflow-y: auto; - height: 65vh; -} - -.documentation-content.markdown .documentation-title { - font-size: 24px; - padding-top: var(--gridSize); - margin-bottom: 2em; -} - -.documentation-content.markdown h2 { - font-size: 18px; - font-weight: 800; - margin-top: 3em; -} - -.documentation-content.markdown h3 { - font-size: 16px; - margin-bottom: 0.8em; -} - -.documentation-content.markdown pre { - border: 1px solid #e6e6e6; - border-radius: 3px; - background-color: rgba(0, 0, 0, 0.06); -} - -.documentation-content.markdown .alert, -.documentation-content.markdown p, -.documentation-content.markdown pre, -.documentation-content.markdown table { - margin: 0.8em 0 2em; -} - -.documentation-content.markdown ul { - margin: 0 0 2em; -} - -.documentation-content.markdown ul > ul { - margin: 0; -} - -.documentation-content.markdown p + ul, -.documentation-content.markdown p + ol, -.documentation-content.markdown p + pre { - margin: -1em 0 2em; -} - -.documentation-content.markdown li > p, -.documentation-content.markdown li > p + pre { - margin: 0; -} - -.documentation-content.markdown li > p + ul, -.documentation-content.markdown li > p + ol { - margin: 0; -} - -.documentation-content.markdown img[src$='.svg'] { - vertical-align: text-bottom; -} - -.documentation-content.markdown .alert { - display: block; - padding: var(--gridSize) calc(2 * var(--gridSize)); -} - -.documentation-content.markdown .alert .custom-block-body { - padding-left: 24px; - background-position: left 6px; - background-repeat: no-repeat; -} - -.documentation-content.markdown .alert-success .custom-block-body { - background-image: url(/images/check.svg); -} - -.documentation-content.markdown .alert-info .custom-block-body { - background-image: url(/images/info.svg); -} - -.documentation-content.markdown .alert-warning .custom-block-body { - background-image: url(/images/exclamation.svg); -} - -.documentation-content.markdown .alert-error .custom-block-body, -.documentation-content.markdown .alert-danger .custom-block-body { - background-image: url(/images/cross.svg); -} - -.documentation-content.markdown .collapse-container { - border: 1px solid var(--barBorderColor); - border-radius: 2px; - background-color: var(--barBackgroundColor); - padding: 8px; - margin: 0.8em 0 2em; -} - -.documentation-content.markdown .collapse-container > a:first-child { - display: block; -} - -.documentation-content.markdown .collapse-container > a:first-child:focus { - color: var(--darkBlue); -} - -.documentation-content.markdown .collapse-container *:last-child { - margin-bottom: 0; -} - -.markdown.has-toc { - display: flex; -} - -.markdown.has-toc .markdown-content { - flex-shrink: 1; - overflow: hidden; - text-overflow: ellipsis; - overflow-x: auto; -} - -.markdown-toc { - flex: 0 0 240px; - margin-right: -40px; -} - -.markdown-toc-content { - margin-left: calc(4 * var(--gridSize)); - padding: 0 var(--gridSize); - font-size: var(--baseFontSize); - background: white; - position: sticky; - top: calc(20px + var(--globalNavHeight)); -} - -.markdown-toc-content h4 { - margin: 0 var(--gridSize) var(--gridSize) var(--gridSize); - font-size: var(--mediumFontSize); -} - -.markdown-toc-content a { - display: block; - color: var(--baseFontColor); - padding: calc(var(--gridSize) / 2) var(--gridSize); - border: 1px solid white; - line-height: 1.2; - transition: none; -} - -.markdown-toc a:hover { - border-color: var(--blue); -} - -.markdown-toc a.active { - font-weight: bold; -} diff --git a/server/sonar-web/src/main/js/apps/documentation/utils.ts b/server/sonar-web/src/main/js/apps/documentation/utils.ts deleted file mode 100644 index a6d8e2d9fc8..00000000000 --- a/server/sonar-web/src/main/js/apps/documentation/utils.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { sortBy } from 'lodash'; - -export type DocumentationEntryScope = 'sonarqube' | 'sonarcloud' | 'static'; - -export interface DocumentationEntry { - content: string; - relativeName: string; - navTitle: string | undefined; - text: string; - title: string; - url: string; -} - -export function getNodeFromUrl(pages: DocumentationEntry[], url: string) { - return pages.find(p => p.url === url); -} - -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/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx index 60d03b5b190..ece024efdae 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx @@ -28,7 +28,7 @@ import { getPluginUpdates } from '../../api/plugins'; import { getValue, setSimpleSettingValue } from '../../api/settings'; -import Link from '../../components/common/Link'; +import DocLink from '../../components/common/DocLink'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../components/hoc/withRouter'; import { Alert } from '../../components/ui/Alert'; @@ -172,11 +172,9 @@ export class App extends React.PureComponent<Props, State> { defaultMessage={translate('marketplace.page.plugins.description2')} values={{ link: ( - <Link - to="/documentation/instance-administration/marketplace/" - target="_blank"> + <DocLink to="/instance-administration/marketplace/"> {translate('marketplace.page.plugins.description2.link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap index dfe9f4507b0..95315b39ff7 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap @@ -43,12 +43,11 @@ exports[`should render correctly: loaded 1`] = ` id="marketplace.page.plugins.description2" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/instance-administration/marketplace/" + "link": <withAppStateContext(DocLink) + to="/instance-administration/marketplace/" > marketplace.page.plugins.description2.link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -141,12 +140,11 @@ exports[`should render correctly: loading 1`] = ` id="marketplace.page.plugins.description2" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/instance-administration/marketplace/" + "link": <withAppStateContext(DocLink) + to="/instance-administration/marketplace/" > marketplace.page.plugins.description2.link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> 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 ab37b6e8f59..20f2890ec2e 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 @@ -17,11 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -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 * as React from 'react'; -import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock'; +import Link from '../../../components/common/Link'; import { getEditionUrl } from '../../../helpers/editions'; import { translate } from '../../../helpers/l10n'; import { Edition, EditionKey } from '../../../types/editions'; @@ -36,16 +33,116 @@ interface Props { export default function EditionBox({ edition, ncloc, serverId, currentEdition }: Props) { return ( <div className="boxed-group boxed-group-inner marketplace-edition"> - {edition.key === 'datacenter' && <DocMarkdownBlock content={tooltipDCE} />} - {edition.key === 'developer' && <DocMarkdownBlock content={tooltipDE} />} - {edition.key === 'enterprise' && <DocMarkdownBlock content={tooltipEE} />} + {edition.key === EditionKey.datacenter && ( + <div className="markdown"> + <div className="markdown-content"> + <div> + <h3 id="data-center-edition"> + <img + alt="SonarQube logo" + className="max-width-100 little-spacer-right" + src="/images/embed-doc/sq-icon.svg" + /> + Data Center Edition + </h3> + <p> + <em>Designed for High Availability and Scalability</em> + </p> + <p>Enterprise Edition functionality plus:</p> + <ul> + <li>Component redundancy</li> + <li>Data resiliency</li> + <li>Horizontal scalability</li> + </ul> + </div> + </div> + </div> + )} + {edition.key === EditionKey.developer && ( + <div className="markdown"> + <div className="markdown-content"> + <div> + <h3 id="developer-edition"> + <img + alt="SonarQube logo" + className="max-width-100 little-spacer-right" + src="/images/embed-doc/sq-icon.svg" + /> + Developer Edition + </h3> + <p> + <em>Built for Developers by Developers</em> + </p> + <p>Community Edition functionality plus:</p> + <ul> + <li> + PR / MR decoration & Quality Gate + <img + alt="GitHub" + className="little-spacer-left max-width-100" + src="/images/alm/github.svg" + /> + <img + alt="GitLab" + className="little-spacer-left max-width-100" + src="/images/alm/gitlab.svg" + /> + <img + alt="Azure DevOps" + className="little-spacer-left max-width-100" + src="/images/alm/azure.svg" + /> + <img + alt="Bitbucket" + className="little-spacer-left max-width-100" + src="/images/alm/bitbucket.svg" + /> + </li> + <li> + Taint analysis / Injection flaw detection for Java, C#, PHP, Python, JS & TS + </li> + <li>Branch analysis</li> + <li>Project aggregation</li> + <li>Additional languages: C, C++, Obj-C, PS/SQL, ABAP, TSQL & Swift</li> + </ul> + </div> + </div> + </div> + )} + {edition.key === EditionKey.enterprise && ( + <div className="markdown"> + <div className="markdown-content"> + <div> + <h3 id="enterprise-edition"> + <img + alt="SonarQube logo" + className="max-width-100 little-spacer-right" + src="/images/embed-doc/sq-icon.svg" + />{' '} + Enterprise Edition + </h3> + <p> + <em>Designed to Meet Enterprise Requirements</em> + </p> + <p>Developer Edition functionality plus:</p> + <ul> + <li>Faster analysis with parallel processing</li> + <li>OWASP/CWE security reports</li> + <li>Portfolio management</li> + <li>Executive reporting</li> + <li>Project transfer</li> + <li>Additional languages: Apex, COBOL, PL/I, RPG & VB6</li> + </ul> + </div> + </div> + </div> + )} <div className="marketplace-edition-action spacer-top"> - <a - href={getEditionUrl(edition, { ncloc, serverId, sourceEdition: currentEdition })} - rel="noopener noreferrer" + <Link + to={getEditionUrl(edition, { ncloc, serverId, sourceEdition: currentEdition })} target="_blank"> {translate('marketplace.request_free_trial')} - </a> + </Link> </div> </div> ); diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap index 51c52c7ea2a..544953e04d3 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap @@ -4,19 +4,80 @@ exports[`should display the edition 1`] = ` <div className="boxed-group boxed-group-inner marketplace-edition" > - <DocMarkdownBlock - content="test-file-stub" - /> + <div + className="markdown" + > + <div + className="markdown-content" + > + <div> + <h3 + id="developer-edition" + > + <img + alt="SonarQube logo" + className="max-width-100 little-spacer-right" + src="/images/embed-doc/sq-icon.svg" + /> + Developer Edition + </h3> + <p> + <em> + Built for Developers by Developers + </em> + </p> + <p> + Community Edition functionality plus: + </p> + <ul> + <li> + PR / MR decoration & Quality Gate + <img + alt="GitHub" + className="little-spacer-left max-width-100" + src="/images/alm/github.svg" + /> + <img + alt="GitLab" + className="little-spacer-left max-width-100" + src="/images/alm/gitlab.svg" + /> + <img + alt="Azure DevOps" + className="little-spacer-left max-width-100" + src="/images/alm/azure.svg" + /> + <img + alt="Bitbucket" + className="little-spacer-left max-width-100" + src="/images/alm/bitbucket.svg" + /> + </li> + <li> + Taint analysis / Injection flaw detection for Java, C#, PHP, Python, JS & TS + </li> + <li> + Branch analysis + </li> + <li> + Project aggregation + </li> + <li> + Additional languages: C, C++, Obj-C, PS/SQL, ABAP, TSQL & Swift + </li> + </ul> + </div> + </div> + </div> <div className="marketplace-edition-action spacer-top" > - <a - href="https://redirect.sonarsource.com/editions/developer.html?ncloc=1000&serverId=serverId&sourceEdition=community" - rel="noopener noreferrer" + <ForwardRef(Link) target="_blank" + to="https://redirect.sonarsource.com/editions/developer.html?ncloc=1000&serverId=serverId&sourceEdition=community" > marketplace.request_free_trial - </a> + </ForwardRef(Link)> </div> </div> `; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx index b377671d51f..15cdb87860c 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import DocLink from '../../../components/common/DocLink'; import Link from '../../../components/common/Link'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; @@ -94,9 +95,7 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp id="overview.measures.empty_link" values={{ learn_more_link: ( - <Link to="/documentation/user-guide/clean-as-you-code/"> - {translate('learn_more')} - </Link> + <DocLink to="/user-guide/clean-as-you-code/">{translate('learn_more')}</DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx index 76e9ea22aa1..49def02a09b 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanelNoNewCode-test.tsx @@ -60,11 +60,11 @@ it('should render the default message', () => { id="overview.measures.empty_link" values={ Object { - "learn_more_link": <ForwardRef(Link) - to="/documentation/user-guide/clean-as-you-code/" + "learn_more_link": <withAppStateContext(DocLink) + to="/user-guide/clean-as-you-code/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap index 7929a930024..7bfe4d72fb9 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelNoNewCode-test.tsx.snap @@ -34,11 +34,11 @@ exports[`should render "bad code setting" explanation: no link 1`] = ` id="overview.measures.empty_link" values={ Object { - "learn_more_link": <ForwardRef(Link) - to="/documentation/user-guide/clean-as-you-code/" + "learn_more_link": <withAppStateContext(DocLink) + to="/user-guide/clean-as-you-code/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -81,11 +81,11 @@ exports[`should render "bad code setting" explanation: with link 1`] = ` id="overview.measures.empty_link" values={ Object { - "learn_more_link": <ForwardRef(Link) - to="/documentation/user-guide/clean-as-you-code/" + "learn_more_link": <withAppStateContext(DocLink) + to="/user-guide/clean-as-you-code/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -160,11 +160,11 @@ exports[`should render the default message 6`] = ` id="overview.measures.empty_link" values={ Object { - "learn_more_link": <ForwardRef(Link) - to="/documentation/user-guide/clean-as-you-code/" + "learn_more_link": <withAppStateContext(DocLink) + to="/user-guide/clean-as-you-code/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.tsx index 634306b49fb..b1ca7a8665b 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/AppHeader.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import DocLink from '../../../components/common/DocLink'; import Link from '../../../components/common/Link'; import { translate } from '../../../helpers/l10n'; @@ -38,9 +39,9 @@ export default function AppHeader(props: AppHeaderProps) { id="project_baseline.page.description" values={{ link: ( - <Link to="/documentation/project-administration/new-code-period/"> + <DocLink to="/project-administration/new-code-period/"> {translate('project_baseline.page.description.link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap index 8ff79641274..b9d9688d93d 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/AppHeader-test.tsx.snap @@ -17,11 +17,11 @@ exports[`should render correctly: can admin 1`] = ` id="project_baseline.page.description" values={ Object { - "link": <ForwardRef(Link) - to="/documentation/project-administration/new-code-period/" + "link": <withAppStateContext(DocLink) + to="/project-administration/new-code-period/" > project_baseline.page.description.link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -60,11 +60,11 @@ exports[`should render correctly: cannot admin 1`] = ` id="project_baseline.page.description" values={ Object { - "link": <ForwardRef(Link) - to="/documentation/project-administration/new-code-period/" + "link": <withAppStateContext(DocLink) + to="/project-administration/new-code-period/" > project_baseline.page.description.link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx index 1df8b307482..4a09610dcbd 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx @@ -184,7 +184,7 @@ export class Conditions extends React.PureComponent<Props> { content={translate('quality_gates.conditions.help')} links={[ { - href: '/documentation/user-guide/clean-as-you-code/', + href: '/user-guide/clean-as-you-code/', label: translate('quality_gates.conditions.help.link') } ]} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx index d0a3a4cd550..8704f5bdaad 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx @@ -54,7 +54,7 @@ export default function ListHeader({ canCreate, refreshQualityGates }: Props) { content={translate('quality_gates.help')} links={[ { - href: '/documentation/user-guide/quality-gates/', + href: '/user-guide/quality-gates/', label: translate('learn_more') } ]} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx index 0280b5b11d2..d9811232f4c 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { Actions } from '../../../api/quality-profiles'; -import Link from '../../../components/common/Link'; +import DocLink from '../../../components/common/DocLink'; import { Button } from '../../../components/controls/buttons'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; import { Alert } from '../../../components/ui/Alert'; @@ -106,14 +106,9 @@ export class PageHeader extends React.PureComponent<Props, State> { {translate('quality_profiles.intro1')} <br /> {translate('quality_profiles.intro2')} - <Link - className="spacer-left" - target="_blank" - to={{ - pathname: '/documentation/instance-administration/quality-profiles/' - }}> + <DocLink className="spacer-left" to="/instance-administration/quality-profiles/"> {translate('learn_more')} - </Link> + </DocLink> </div> {this.state.restoreFormOpen && ( diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap index b56ff86f837..3b2de8d69bd 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap @@ -15,17 +15,12 @@ exports[`should render correctly 1`] = ` quality_profiles.intro1 <br /> quality_profiles.intro2 - <ForwardRef(Link) + <withAppStateContext(DocLink) className="spacer-left" - target="_blank" - to={ - Object { - "pathname": "/documentation/instance-administration/quality-profiles/", - } - } + to="/instance-administration/quality-profiles/" > learn_more - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> </header> `; @@ -63,17 +58,12 @@ exports[`should render correctly 2`] = ` quality_profiles.intro1 <br /> quality_profiles.intro2 - <ForwardRef(Link) + <withAppStateContext(DocLink) className="spacer-left" - target="_blank" - to={ - Object { - "pathname": "/documentation/instance-administration/quality-profiles/", - } - } + to="/instance-administration/quality-profiles/" > learn_more - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> </header> `; @@ -117,17 +107,12 @@ exports[`should render correctly 3`] = ` quality_profiles.intro1 <br /> quality_profiles.intro2 - <ForwardRef(Link) + <withAppStateContext(DocLink) className="spacer-left" - target="_blank" - to={ - Object { - "pathname": "/documentation/instance-administration/quality-profiles/", - } - } + to="/instance-administration/quality-profiles/" > learn_more - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> </header> `; @@ -165,17 +150,12 @@ exports[`should show a create form 1`] = ` quality_profiles.intro1 <br /> quality_profiles.intro2 - <ForwardRef(Link) + <withAppStateContext(DocLink) className="spacer-left" - target="_blank" - to={ - Object { - "pathname": "/documentation/instance-administration/quality-profiles/", - } - } + to="/instance-administration/quality-profiles/" > learn_more - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> <CreateProfileForm languages={ @@ -253,17 +233,12 @@ exports[`should show a restore form 1`] = ` quality_profiles.intro1 <br /> quality_profiles.intro2 - <ForwardRef(Link) + <withAppStateContext(DocLink) className="spacer-left" - target="_blank" - to={ - Object { - "pathname": "/documentation/instance-administration/quality-profiles/", - } - } + to="/instance-administration/quality-profiles/" > learn_more - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> <RestoreProfileForm onClose={[Function]} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx index 05096cc74a4..89599d1fe80 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Link from '../../../components/common/Link'; +import DocLink from '../../../components/common/DocLink'; import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; @@ -57,12 +57,9 @@ export default function EmptyHotspotsPage(props: EmptyHotspotsPageProps) { {translate(`hotspots.${translationRoot}.description`)} </div> {!(filtered || isStaticListOfHotspots) && ( - <Link - className="big-spacer-top" - target="_blank" - to={{ pathname: '/documentation/user-guide/security-hotspots/' }}> + <DocLink className="big-spacer-top" to="/user-guide/security-hotspots/"> {translate('hotspots.learn_more')} - </Link> + </DocLink> )} </div> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap index 440d4a6e8ae..0a01522c9a7 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap @@ -20,17 +20,12 @@ exports[`should render correctly 1`] = ` > hotspots.no_hotspots.description </div> - <ForwardRef(Link) + <withAppStateContext(DocLink) className="big-spacer-top" - target="_blank" - to={ - Object { - "pathname": "/documentation/user-guide/security-hotspots/", - } - } + to="/user-guide/security-hotspots/" > hotspots.learn_more - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> `; @@ -54,17 +49,12 @@ exports[`should render correctly: file 1`] = ` > hotspots.no_hotspots_for_file.description </div> - <ForwardRef(Link) + <withAppStateContext(DocLink) className="big-spacer-top" - target="_blank" - to={ - Object { - "pathname": "/documentation/user-guide/security-hotspots/", - } - } + to="/user-guide/security-hotspots/" > hotspots.learn_more - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> `; diff --git a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx index 0421c0a8528..78b71f912cd 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Link from '../../../components/common/Link'; +import DocLink from '../../../components/common/DocLink'; import { translate } from '../../../helpers/l10n'; import { AdditionalCategoryComponentProps } from './AdditionalCategories'; import CategoryDefinitionsList from './CategoryDefinitionsList'; @@ -30,11 +30,9 @@ export function AnalysisScope(props: AdditionalCategoryComponentProps) { <> <p className="spacer-bottom"> {translate('settings.analysis_scope.wildcards.introduction')} - <Link - className="spacer-left" - to="/documentation/project-administration/narrowing-the-focus/"> + <DocLink className="spacer-left" to="/project-administration/narrowing-the-focus/"> {translate('learn_more')} - </Link> + </DocLink> </p> <table className="data spacer-bottom"> diff --git a/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx b/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx index 1fc628761ea..c0cb640c133 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { getNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod'; -import Link from '../../../components/common/Link'; +import DocLink from '../../../components/common/DocLink'; import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; @@ -160,9 +160,9 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> { id="settings.new_code_period.description" values={{ link: ( - <Link to="/documentation/project-administration/new-code-period/"> + <DocLink to="/project-administration/new-code-period/"> {translate('learn_more')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap index 8f3c70f5740..0b50c3ff5b8 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap @@ -6,12 +6,12 @@ exports[`should render correctly 1`] = ` className="spacer-bottom" > settings.analysis_scope.wildcards.introduction - <ForwardRef(Link) + <withAppStateContext(DocLink) className="spacer-left" - to="/documentation/project-administration/narrowing-the-focus/" + to="/project-administration/narrowing-the-focus/" > learn_more - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </p> <table className="data spacer-bottom" diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap index 80f68b5aa3e..d51fc151cfd 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap @@ -29,11 +29,11 @@ exports[`should render correctly 1`] = ` id="settings.new_code_period.description" values={ Object { - "link": <ForwardRef(Link) - to="/documentation/project-administration/new-code-period/" + "link": <withAppStateContext(DocLink) + to="/project-administration/new-code-period/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx index e13e9a0a946..0924eedf763 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../../components/common/Link'; +import DocLink from '../../../../components/common/DocLink'; import { Button } from '../../../../components/controls/buttons'; import HelpTooltip from '../../../../components/controls/HelpTooltip'; import Tooltip from '../../../../components/controls/Tooltip'; @@ -230,9 +230,9 @@ export default function AlmBindingDefinitionBox(props: AlmBindingDefinitionBoxPr )} values={{ link: ( - <Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.GitHub]}> + <DocLink to={ALM_DOCUMENTATION_PATHS[AlmKeys.GitHub]}> {translate('learn_more')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx index be83d609cbe..9ebfd7a8d53 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../../components/common/Link'; +import DocLink from '../../../../components/common/DocLink'; import { ButtonLink } from '../../../../components/controls/buttons'; import ValidationInput, { ValidationInputErrorPlacement @@ -127,14 +127,9 @@ export function AlmBindingDefinitionFormField<B extends AlmBindingDefinitionBase defaultMessage={translate('settings.almintegration.form.secret.can_encrypt')} values={{ learn_more: ( - <Link - target="_blank" - to={{ - pathname: - '/documentation/instance-administration/security/#settings-encryption' - }}> + <DocLink to="/instance-administration/security/#settings-encryption"> {translate('learn_more')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx index 8cee4e30d84..3b454eb03fe 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import DocLink from '../../../../components/common/DocLink'; import Link from '../../../../components/common/Link'; import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; import { translate } from '../../../../helpers/l10n'; @@ -80,9 +81,9 @@ export default function AzureForm(props: AzureFormProps) { ), permission: <strong>{'Code > Read & Write'}</strong>, doc_link: ( - <Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.Azure]}> + <DocLink to={ALM_DOCUMENTATION_PATHS[AlmKeys.Azure]}> {translate('learn_more')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.tsx index 075775bc4c0..449ecdf5b17 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketCloudForm.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import DocLink from '../../../../components/common/DocLink'; import Link from '../../../../components/common/Link'; import { Alert } from '../../../../components/ui/Alert'; import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; @@ -91,9 +92,9 @@ export default function BitbucketCloudForm(props: BitbucketCloudFormProps) { ), permission: <strong>Pull Requests: Read</strong>, doc_link: ( - <Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.BitbucketCloud]}> + <DocLink to={ALM_DOCUMENTATION_PATHS[AlmKeys.BitbucketCloud]}> {translate('learn_more')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.tsx index 2920a0d03d6..3b60f43a85c 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketServerForm.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import DocLink from '../../../../components/common/DocLink'; import Link from '../../../../components/common/Link'; import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; import { translate } from '../../../../helpers/l10n'; @@ -78,9 +79,9 @@ export default function BitbucketServerForm(props: BitbucketServerFormProps) { ), permission: <strong>Read</strong>, doc_link: ( - <Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.BitbucketServer]}> + <DocLink to={ALM_DOCUMENTATION_PATHS[AlmKeys.BitbucketServer]}> {translate('learn_more')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx index 80709f0a8cb..cda048ea286 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../../components/common/Link'; +import DocLink from '../../../../components/common/DocLink'; import { Alert } from '../../../../components/ui/Alert'; import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; import { translate } from '../../../../helpers/l10n'; @@ -70,9 +70,9 @@ export default function GithubForm(props: GithubFormProps) { id="settings.almintegration.github.info" values={{ link: ( - <Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.GitHub]}> + <DocLink to={ALM_DOCUMENTATION_PATHS[AlmKeys.GitHub]}> {translate('learn_more')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx index e0e8f4b5b11..58686ea35a8 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import DocLink from '../../../../components/common/DocLink'; import Link from '../../../../components/common/Link'; import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; import { translate } from '../../../../helpers/l10n'; @@ -76,9 +77,9 @@ export default function GitlabForm(props: GitlabFormProps) { permission: <strong>Reporter</strong>, scope: <strong>api</strong>, doc_link: ( - <Link target="_blank" to={ALM_DOCUMENTATION_PATHS[AlmKeys.GitLab]}> + <DocLink to={ALM_DOCUMENTATION_PATHS[AlmKeys.GitLab]}> {translate('learn_more')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap index 188b67245c7..4afcf1ec133 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap @@ -522,12 +522,11 @@ exports[`should render correctly: success with alert 1`] = ` id="settings.almintegration.github.additional_permission" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/github-integration/" + "link": <withAppStateContext(DocLink) + to="/analysis/github-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -649,12 +648,11 @@ exports[`should render correctly: success with branches disabled 1`] = ` id="settings.almintegration.github.additional_permission" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/github-integration/" + "link": <withAppStateContext(DocLink) + to="/analysis/github-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap index 8928255a0de..f878e875d5e 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap @@ -81,16 +81,11 @@ exports[`should render correctly: encryptable 1`] = ` id="settings.almintegration.form.secret.can_encrypt" values={ Object { - "learn_more": <ForwardRef(Link) - target="_blank" - to={ - Object { - "pathname": "/documentation/instance-administration/security/#settings-encryption", - } - } + "learn_more": <withAppStateContext(DocLink) + to="/instance-administration/security/#settings-encryption" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap index fb5e0271cef..e7e4fed3d74 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap @@ -41,12 +41,11 @@ exports[`should render correctly: create 1`] = ` id="settings.almintegration.form.personal_access_token.azure.help" values={ Object { - "doc_link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/azuredevops-integration/" + "doc_link": <withAppStateContext(DocLink) + to="/analysis/azuredevops-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, "pat": <ForwardRef(Link) target="_blank" to="https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate" @@ -113,12 +112,11 @@ exports[`should render correctly: edit 1`] = ` id="settings.almintegration.form.personal_access_token.azure.help" values={ Object { - "doc_link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/azuredevops-integration/" + "doc_link": <withAppStateContext(DocLink) + to="/analysis/azuredevops-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, "pat": <ForwardRef(Link) target="_blank" to="https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate" diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap index 62a4d83d38f..23f4867930a 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketCloudForm-test.tsx.snap @@ -45,12 +45,11 @@ exports[`should render correctly: default 1`] = ` id="settings.almintegration.bitbucketcloud.info" values={ Object { - "doc_link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/bitbucket-cloud-integration/" + "doc_link": <withAppStateContext(DocLink) + to="/analysis/bitbucket-cloud-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, "oauth": <ForwardRef(Link) target="_blank" to="https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/" @@ -131,12 +130,11 @@ exports[`should render correctly: invalid workspace ID 1`] = ` id="settings.almintegration.bitbucketcloud.info" values={ Object { - "doc_link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/bitbucket-cloud-integration/" + "doc_link": <withAppStateContext(DocLink) + to="/analysis/bitbucket-cloud-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, "oauth": <ForwardRef(Link) target="_blank" to="https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/" diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap index f428bc72620..f580668efb4 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketServerForm-test.tsx.snap @@ -36,12 +36,11 @@ exports[`should render correctly 1`] = ` id="settings.almintegration.form.personal_access_token.bitbucket.help" values={ Object { - "doc_link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/bitbucket-integration/" + "doc_link": <withAppStateContext(DocLink) + to="/analysis/bitbucket-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, "pat": <ForwardRef(Link) target="_blank" to="https://confluence.atlassian.com/bitbucketserver0515/personal-access-tokens-961275199.html" diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap index d6125804a6f..71a84ff7946 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap @@ -43,12 +43,11 @@ exports[`should render correctly 1`] = ` id="settings.almintegration.github.info" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/github-integration/" + "link": <withAppStateContext(DocLink) + to="/analysis/github-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -147,12 +146,11 @@ exports[`should render correctly 2`] = ` id="settings.almintegration.github.info" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/github-integration/" + "link": <withAppStateContext(DocLink) + to="/analysis/github-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap index 3b034da6c5a..7af8b68c428 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap @@ -34,12 +34,11 @@ exports[`should render correctly 1`] = ` id="settings.almintegration.form.personal_access_token.gitlab.help" values={ Object { - "doc_link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/gitlab-integration/" + "doc_link": <withAppStateContext(DocLink) + to="/analysis/gitlab-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, "pat": <ForwardRef(Link) target="_blank" to="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html" @@ -102,12 +101,11 @@ exports[`should render correctly 2`] = ` id="settings.almintegration.form.personal_access_token.gitlab.help" values={ Object { - "doc_link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/gitlab-integration/" + "doc_link": <withAppStateContext(DocLink) + to="/analysis/gitlab-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, "pat": <ForwardRef(Link) target="_blank" to="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html" diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx index d8ce34a0b2d..4653c2a69fb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { useSearchParams } from 'react-router-dom'; -import Link from '../../../../components/common/Link'; +import DocLink from '../../../../components/common/DocLink'; import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper'; import BoxedTabs, { getTabId, getTabPanelId } from '../../../../components/controls/BoxedTabs'; import { Alert } from '../../../../components/ui/Alert'; @@ -142,12 +142,10 @@ export default function Authentication(props: Props) { defaultMessage={translate('settings.authentication.help')} values={{ link: ( - <Link - to={`/documentation/instance-administration/authentication/${DOCUMENTATION_LINK_SUFFIXES[currentTab]}/`} - rel="noopener noreferrer" - target="_blank"> + <DocLink + to={`/instance-administration/authentication/${DOCUMENTATION_LINK_SUFFIXES[currentTab]}/`}> {translate('settings.authentication.help.link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx index ad01fa26e18..5544186be4c 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx @@ -22,7 +22,7 @@ import { FormattedMessage } from 'react-intl'; import withAvailableFeatures, { WithAvailableFeaturesProps } from '../../../../app/components/available-features/withAvailableFeatures'; -import Link from '../../../../components/common/Link'; +import DocLink from '../../../../components/common/DocLink'; import Toggle from '../../../../components/controls/Toggle'; import { Alert } from '../../../../components/ui/Alert'; import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker'; @@ -286,11 +286,7 @@ export function AlmSpecificForm(props: AlmSpecificFormProps) { renderBooleanField({ help: true, helpParams: { - doc_link: ( - <Link to={ALM_DOCUMENTATION_PATHS[alm]} target="_blank"> - {translate('learn_more')} - </Link> - ) + doc_link: <DocLink to={ALM_DOCUMENTATION_PATHS[alm]}>{translate('learn_more')}</DocLink> }, id: 'monorepo', onFieldChange: props.onFieldChange, diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap index b7413c7b625..26c43916050 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap @@ -802,12 +802,11 @@ exports[`should render the monorepo field when the feature is supported 1`] = ` id="settings.pr_decoration.binding.form.monorepo.help" values={ Object { - "doc_link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/azuredevops-integration/" + "doc_link": <withAppStateContext(DocLink) + to="/analysis/azuredevops-integration/" > learn_more - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx index af24bb91804..5307a9bd840 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { encryptValue } from '../../../api/settings'; -import Link from '../../../components/common/Link'; +import DocLink from '../../../components/common/DocLink'; import { SubmitButton } from '../../../components/controls/buttons'; import { ClipboardButton } from '../../../components/controls/clipboard'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; @@ -129,9 +129,9 @@ export default class EncryptionForm extends React.PureComponent<Props, State> { id="encryption.form_note" values={{ moreInformationLink: ( - <Link to="/documentation/instance-administration/security/" target="_blank"> + <DocLink to="/instance-administration/security/"> {translate('more_information')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx index 66c6ecfb3fd..6fc39716548 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../components/common/Link'; +import DocLink from '../../../components/common/DocLink'; import { SubmitButton } from '../../../components/controls/buttons'; import { ClipboardButton } from '../../../components/controls/clipboard'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; @@ -112,9 +112,9 @@ export default class GenerateSecretKeyForm extends React.PureComponent<Props, St id="encryption.secret_key_description" values={{ moreInformationLink: ( - <Link to="/documentation/instance-administration/security/" target="_blank"> + <DocLink to="/instance-administration/security/"> {translate('more_information')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap index 4f79df0e425..c8771a86fd7 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/EncryptionForm-test.tsx.snap @@ -50,12 +50,11 @@ exports[`should render correctly 1`] = ` id="encryption.form_note" values={ Object { - "moreInformationLink": <ForwardRef(Link) - target="_blank" - to="/documentation/instance-administration/security/" + "moreInformationLink": <withAppStateContext(DocLink) + to="/instance-administration/security/" > more_information - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap index 6073e39503a..82f4f112d07 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/__snapshots__/GenerateSecretKeyForm-test.tsx.snap @@ -16,12 +16,11 @@ exports[`should render correctly 1`] = ` id="encryption.secret_key_description" values={ Object { - "moreInformationLink": <ForwardRef(Link) - target="_blank" - to="/documentation/instance-administration/security/" + "moreInformationLink": <withAppStateContext(DocLink) + to="/instance-administration/security/" > more_information - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx b/server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx index f0705457e0b..bffd08731c0 100644 --- a/server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { deactivateUser } from '../../../api/users'; +import DocLink from '../../../components/common/DocLink'; import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; import Checkbox from '../../../components/controls/Checkbox'; import Modal from '../../../components/controls/Modal'; @@ -99,12 +100,9 @@ export default class DeactivateForm extends React.PureComponent<Props, State> { id="delete-user-warning" values={{ link: ( - <a - href="/documentation/instance-administration/authentication/overview/" - rel="noopener noreferrer" - target="_blank"> + <DocLink to="/instance-administration/authentication/overview/"> {translate('users.delete_user.help.link')} - </a> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx index e581653e527..19088eb99ea 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/PageHeader.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../components/common/Link'; +import DocLink from '../../../components/common/DocLink'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -41,9 +41,9 @@ export default function PageHeader({ children, loading }: Props) { id="webhooks.description" values={{ url: ( - <Link to="/documentation/project-administration/webhooks/"> + <DocLink to="/project-administration/webhooks/"> {translate('webhooks.documentation_link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap index b1cbc315ff3..d6cdf4408c1 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap @@ -21,11 +21,11 @@ exports[`should render correctly 1`] = ` id="webhooks.description" values={ Object { - "url": <ForwardRef(Link) - to="/documentation/project-administration/webhooks/" + "url": <withAppStateContext(DocLink) + to="/project-administration/webhooks/" > webhooks.documentation_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/components/docs/DocImg.tsx b/server/sonar-web/src/main/js/components/common/DocLink.tsx index b5cd154b2bf..53d9c8c4e78 100644 --- a/server/sonar-web/src/main/js/components/docs/DocImg.tsx +++ b/server/sonar-web/src/main/js/components/common/DocLink.tsx @@ -18,21 +18,18 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { getBaseUrl } from '../../helpers/system'; +import withAppStateContext, { + WithAppStateContextProps +} from '../../app/components/app-state/withAppStateContext'; +import { getUrlForDoc } from '../../helpers/docs'; +import Link, { LinkProps } from './Link'; -export default function DocImg(props: React.ImgHTMLAttributes<HTMLImageElement>) { - const { alt, src, ...other } = props; +type Props = WithAppStateContextProps & + Omit<LinkProps, 'to'> & { to: string; innerRef?: React.Ref<HTMLAnchorElement> }; - if (process.env.NODE_ENV === 'development') { - return <img alt={alt} className="max-width-100" src={getBaseUrl() + src} {...other} />; - } - - return ( - <img - alt={alt} - className="max-width-100" - src={getBaseUrl() + '/images/embed-doc' + src} - {...other} - /> - ); +export function DocLink({ appState, to, innerRef, ...props }: Props) { + const toStatic = getUrlForDoc(appState.version, to); + return <Link ref={innerRef} to={toStatic} target="_blank" {...props} />; } + +export default withAppStateContext(DocLink); diff --git a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx index 953f5fa6b04..c44ca0efd50 100644 --- a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx +++ b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx @@ -19,13 +19,14 @@ */ import * as React from 'react'; import HelpTooltip from '../../components/controls/HelpTooltip'; +import DocLink from './DocLink'; import Link from './Link'; export interface DocumentationTooltipProps { children?: React.ReactNode; className?: string; content?: React.ReactNode; - links?: Array<{ href: string; label: string; inPlace?: boolean }>; + links?: Array<{ href: string; label: string; inPlace?: boolean; doc?: boolean }>; title?: string; } @@ -49,7 +50,7 @@ export default function DocumentationTooltip(props: DocumentationTooltipProps) { <> <hr className="big-spacer-top big-spacer-bottom" /> - {links.map(({ href, label, inPlace }) => ( + {links.map(({ href, label, inPlace, doc = true }) => ( <div className="little-spacer-bottom" key={label} @@ -58,9 +59,13 @@ export default function DocumentationTooltip(props: DocumentationTooltipProps) { // they won't be "clickable"), we hide the whole links section. // See https://sarahmhigley.com/writing/tooltips-in-wcag-21/ aria-hidden={true}> - <Link to={href} target={inPlace ? undefined : '_blank'}> - {label} - </Link> + {doc ? ( + <DocLink to={href}>{label}</DocLink> + ) : ( + <Link to={href} target={inPlace ? undefined : '_blank'}> + {label} + </Link> + )} </div> ))} </> diff --git a/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx index 16cca514122..67821c47ea9 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx +++ b/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx @@ -27,8 +27,8 @@ it('renders correctly', () => { shallowRender({ links: [ { href: 'http://link.tosome.place', label: 'external link' }, - { href: '/documentation/guide', label: 'internal link' }, - { href: '/projects', label: 'in place', inPlace: true } + { href: '/guide', label: 'internal link' }, + { href: '/projects', label: 'in place', inPlace: true, doc: false } ] }) ).toMatchSnapshot('with links'); diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap index 88d7984d8e3..b1ed2aba980 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap @@ -77,23 +77,21 @@ exports[`renders correctly: with links 1`] = ` aria-hidden={true} className="little-spacer-bottom" > - <ForwardRef(Link) - target="_blank" + <withAppStateContext(DocLink) to="http://link.tosome.place" > external link - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> <div aria-hidden={true} className="little-spacer-bottom" > - <ForwardRef(Link) - target="_blank" - to="/documentation/guide" + <withAppStateContext(DocLink) + to="/guide" > internal link - </ForwardRef(Link)> + </withAppStateContext(DocLink)> </div> <div aria-hidden={true} diff --git a/server/sonar-web/src/main/js/components/docs/DocCollapsibleBlock.tsx b/server/sonar-web/src/main/js/components/docs/DocCollapsibleBlock.tsx deleted file mode 100644 index 4a3eb1d604a..00000000000 --- a/server/sonar-web/src/main/js/components/docs/DocCollapsibleBlock.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 OpenCloseIcon from '../../components/icons/OpenCloseIcon'; - -interface State { - open: boolean; -} - -export default class DocCollapsibleBlock extends React.PureComponent<{}, State> { - state = { open: false }; - - handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => { - this.setState(state => ({ open: !state.open })); - event.stopPropagation(); - event.preventDefault(); - }; - - renderTitle(children: any) { - return ( - <a - aria-expanded={this.state.open} - aria-haspopup={true} - role="button" - className="link-no-underline" - href="#" - onClick={this.handleClick}> - <OpenCloseIcon className="text-middle little-spacer-right" open={this.state.open} /> - {children.props ? children.props.children : children} - </a> - ); - } - - render() { - const childrenAsArray = React.Children.toArray(this.props.children); - if (childrenAsArray.length < 1) { - return null; - } - - const firstChildChildren = React.Children.toArray( - (childrenAsArray[0] as React.ReactElement<any>).props.children - ); - if (firstChildChildren.length < 2) { - return null; - } - - return ( - <div className="collapse-container"> - {this.renderTitle(firstChildChildren[0])} - {this.state.open && firstChildChildren.slice(1)} - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/docs/DocLink.tsx b/server/sonar-web/src/main/js/components/docs/DocLink.tsx deleted file mode 100644 index 78c3228bb92..00000000000 --- a/server/sonar-web/src/main/js/components/docs/DocLink.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 withAppStateContext from '../../app/components/app-state/withAppStateContext'; -import { AppState } from '../../types/appstate'; -import Link from '../common/Link'; - -interface OwnProps { - appState: AppState; - customProps?: { - [k: string]: any; - }; -} - -type Props = OwnProps & React.AnchorHTMLAttributes<HTMLAnchorElement>; - -const SONARQUBE_LINK = '/#sonarqube#/'; -const SONARQUBE_ADMIN_LINK = '/#sonarqube-admin#/'; - -export class DocLink extends React.PureComponent<Props> { - handleClickOnAnchor = (event: React.MouseEvent<HTMLAnchorElement>) => { - const { customProps, href = '#' } = this.props; - if (customProps && customProps.onAnchorClick) { - customProps.onAnchorClick(href, event); - } - }; - - render() { - const { appState, children, href, customProps, ...other } = this.props; - if (href && href.startsWith('#')) { - return ( - <a href="#" onClick={this.handleClickOnAnchor}> - {children} - </a> - ); - } - - if (href && href.startsWith('/')) { - if (href.startsWith(SONARQUBE_LINK)) { - return <SonarQubeLink url={href}>{children}</SonarQubeLink>; - } else if (href.startsWith(SONARQUBE_ADMIN_LINK)) { - return ( - <SonarQubeAdminLink canAdmin={appState.canAdmin} url={href}> - {children} - </SonarQubeAdminLink> - ); - } - const url = '/documentation' + href; - return ( - <Link to={url} {...other}> - {children} - </Link> - ); - } - - return href ? ( - <Link to={href} target="_blank" size={12} {...other}> - {children} - </Link> - ) : null; - } -} - -export default withAppStateContext(DocLink); - -interface SonarQubeLinkProps { - children: React.ReactNode; - url: string; -} - -function SonarQubeLink({ children, url }: SonarQubeLinkProps) { - const to = `/${url.substr(SONARQUBE_LINK.length)}`; - return ( - <Link target="_blank" to={to}> - {children} - </Link> - ); -} - -interface SonarQubeAdminLinkProps { - canAdmin?: boolean; - children: React.ReactNode; - url: string; -} - -function SonarQubeAdminLink({ canAdmin, children, url }: SonarQubeAdminLinkProps) { - if (!canAdmin) { - return <>{children}</>; - } - const to = `/${url.substr(SONARQUBE_ADMIN_LINK.length)}`; - return ( - <Link target="_blank" to={to}> - {children} - </Link> - ); -} diff --git a/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.css b/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.css deleted file mode 100644 index 840ceebe78f..00000000000 --- a/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.css +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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. - */ -.markdown-content .alert { - margin-bottom: var(--gridSize); - border: 1px solid; - border-radius: 2px; -} - -.markdown-content .alert.is-inline { - display: inline-flex; -} - -.markdown-content .alert:empty { - display: none; -} - -.markdown-content .alert-error, -.markdown-content .alert-danger { - border-color: var(--alertBorderError); - background-color: var(--alertBackgroundError); - color: var(--alertTextError); -} - -.markdown-content .alert-error .alert-icon, -.markdown-content .alert-danger .alert-icon { - border-color: var(--alertBorderError); -} - -.markdown-content .alert-warning { - border-color: var(--alertBorderWarning); - background-color: var(--alertBackgroundWarning); - color: var(--alertTextWarning); -} - -.markdown-content .alert-warning .alert-icon { - border-color: var(--alertBorderWarning); -} - -.markdown-content .alert-info { - border-color: var(--alertBorderInfo); - background-color: var(--alertBackgroundInfo); - color: var(--alertTextInfo); -} - -.markdown-content .alert-info .alert-icon { - border-color: var(--alertBorderInfo); -} - -.markdown-content .alert-success { - border-color: var(--alertBorderSuccess); - background-color: var(--alertBackgroundSuccess); - color: var(--alertTextSuccess); -} - -.markdown-content .alert-success .alert-icon { - border-color: var(--alertBorderSuccess); -} diff --git a/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx b/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx deleted file mode 100644 index 2e9a7770578..00000000000 --- a/server/sonar-web/src/main/js/components/docs/DocMarkdownBlock.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 classNames from 'classnames'; -import * as React from 'react'; -import rehypeRaw from 'rehype-raw'; -import rehypeReact from 'rehype-react'; -import rehypeSlug from 'rehype-slug'; -import remark from 'remark'; -import remarkCustomBlocks from 'remark-custom-blocks'; -import remarkRehype from 'remark-rehype'; -import { scrollToElement } from '../../helpers/scrolling'; -import { Dict } from '../../types/types'; -import DocCollapsibleBlock from './DocCollapsibleBlock'; -import DocImg from './DocImg'; -import DocLink from './DocLink'; -import './DocMarkdownBlock.css'; -import DocToc from './DocToc'; -import DocTooltipLink from './DocTooltipLink'; - -interface Props { - childProps?: Dict<string>; - className?: string; - content: string; - isTooltip?: boolean; - scrollToHref?: string; - stickyToc?: boolean; - title?: string; -} - -const WAIT_TIMEOUT = 500; - -export default class DocMarkdownBlock extends React.PureComponent<Props> { - node: HTMLElement | null = null; - - componentDidMount() { - const { scrollToHref } = this.props; - if (scrollToHref) { - setTimeout(() => { - this.handleAnchorClick(scrollToHref); - }, WAIT_TIMEOUT); - } - } - - handleAnchorClick = (href: string, event?: React.MouseEvent<HTMLAnchorElement>) => { - if (this.node) { - const element = this.node.querySelector(href); - if (element) { - if (event) { - event.preventDefault(); - } - scrollToElement(element, { bottomOffset: window.innerHeight - 80 }); - - // We cannot use React Router here, because we cannot simply replace a hash. - if (history.pushState) { - history.pushState(null, '', href); - } - } - } - }; - - render() { - const { childProps, content, className, title, stickyToc, isTooltip } = this.props; - - const md = remark(); - - // TODO find a way to replace these custom blocks with real Alert components - md.use(remarkCustomBlocks, { - danger: { classes: 'alert alert-danger' }, - warning: { classes: 'alert alert-warning' }, - info: { classes: 'alert alert-info' }, - success: { classes: 'alert alert-success' }, - collapse: { classes: 'collapse' } - }) - .use(remarkRehype, { allowDangerousHTML: true }) - .use(rehypeSlug) - .use(rehypeRaw) - .use(rehypeReact, { - createElement: React.createElement, - components: { - div: Block, - // use custom link to render documentation anchors - a: isTooltip - ? withChildProps(DocTooltipLink, childProps) - : withChildProps(DocLink, { onAnchorClick: this.handleAnchorClick }), - // use custom img tag to render documentation images - img: DocImg - } - }); - - return ( - <div - className={classNames('markdown', className, { 'has-toc': stickyToc })} - ref={ref => (this.node = ref)}> - <div className="markdown-content"> - {title !== undefined && <h1 className="documentation-title">{title}</h1>} - {md.processSync(content).contents} - </div> - {stickyToc && <DocToc content={content} onAnchorClick={this.handleAnchorClick} />} - </div> - ); - } -} - -function withChildProps<P>( - WrappedComponent: React.ComponentType<P & { customProps?: Dict<any> }>, - childProps?: Dict<any> -) { - return function withChildProps(props: P) { - return <WrappedComponent customProps={childProps} {...props} />; - }; -} - -function Block(props: React.HtmlHTMLAttributes<HTMLDivElement>) { - if (props.className) { - if (props.className.includes('collapse')) { - return <DocCollapsibleBlock>{props.children}</DocCollapsibleBlock>; - } else { - return <div className={classNames('cut-margins', props.className)}>{props.children}</div>; - } - } else { - return props.children; - } -} diff --git a/server/sonar-web/src/main/js/components/docs/DocToc.tsx b/server/sonar-web/src/main/js/components/docs/DocToc.tsx deleted file mode 100644 index 2332fd17337..00000000000 --- a/server/sonar-web/src/main/js/components/docs/DocToc.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 classNames from 'classnames'; -import { debounce, memoize } from 'lodash'; -import * as React from 'react'; -import { findDOMNode } from 'react-dom'; -import remark from 'remark'; -import reactRenderer from 'remark-react'; -import { translate } from '../../helpers/l10n'; -import onlyToc from './plugins/remark-only-toc'; - -interface Props { - content: string; - onAnchorClick: (href: string, event: React.MouseEvent<HTMLAnchorElement>) => void; -} - -interface State { - anchors: AnchorObject[]; - highlightAnchor?: string; -} - -interface AnchorObject { - href: string; - title: string; -} - -export default class DocToc extends React.PureComponent<Props, State> { - debouncedScrollHandler: () => void; - - node: HTMLDivElement | null = null; - - state: State = { anchors: [] }; - - constructor(props: Props) { - super(props); - this.debouncedScrollHandler = debounce(this.scrollHandler); - } - - static getDerivedStateFromProps(props: Props) { - const { content } = props; - return { anchors: DocToc.getAnchors(content) }; - } - - componentDidMount() { - window.addEventListener('scroll', this.debouncedScrollHandler, true); - this.scrollHandler(); - } - - componentWillUnmount() { - window.removeEventListener('scroll', this.debouncedScrollHandler, true); - } - - static getAnchors = memoize((content: string) => { - const file: { contents: JSX.Element } = remark() - .use(reactRenderer) - .use(onlyToc) - .processSync('\n## doctoc\n' + content); - - if (file && file.contents.props.children) { - let list = file.contents; - let limit = 10; - while (limit && list.props.children.length && list.type !== 'ul') { - list = list.props.children[0]; - limit--; - } - - if (list.type === 'ul' && list.props.children.length) { - return list.props.children - .map((li: JSX.Element | string) => { - if (typeof li === 'string') { - return null; - } - - const anchor = li.props.children[0]; - return { - href: anchor.props.href, - title: anchor.props.children[0] - } as AnchorObject; - }) - .filter((item: AnchorObject | null) => item); - } - } - return []; - }); - - scrollHandler = () => { - // eslint-disable-next-line react/no-find-dom-node - const node = findDOMNode(this) as HTMLElement; - - if (!node || !node.parentNode) { - return; - } - - const headings: NodeListOf<HTMLHeadingElement> = node.parentNode.querySelectorAll('h2[id]'); - const scrollTop = window.pageYOffset || document.body.scrollTop; - let highlightAnchor; - - for (let i = 0, len = headings.length; i < len; i++) { - if (headings.item(i).offsetTop > scrollTop + 120) { - break; - } - highlightAnchor = `#${headings.item(i).id}`; - } - - this.setState({ - highlightAnchor - }); - }; - - render() { - const { anchors, highlightAnchor } = this.state; - - if (anchors.length === 0) { - return null; - } - - return ( - <div className="markdown-toc"> - <div className="markdown-toc-content"> - <h4>{translate('documentation.on_this_page')}</h4> - {anchors.map(anchor => { - return ( - <a - className={classNames({ active: highlightAnchor === anchor.href })} - href={anchor.href} - key={anchor.title} - onClick={(event: React.MouseEvent<HTMLAnchorElement>) => { - this.props.onAnchorClick(anchor.href, event); - }}> - {anchor.title} - </a> - ); - })} - </div> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx b/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx deleted file mode 100644 index daf1c8c4662..00000000000 --- a/server/sonar-web/src/main/js/components/docs/DocTooltipLink.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { forEach } from 'lodash'; -import * as React from 'react'; -import { Dict } from '../../types/types'; -import Link from '../common/Link'; - -interface OwnProps { - customProps?: Dict<string>; -} - -type Props = OwnProps & React.AnchorHTMLAttributes<HTMLAnchorElement>; - -export default function DocTooltipLink({ children, customProps, href, ...other }: Props) { - if (customProps) { - forEach(customProps, (value, key) => { - if (href) { - href = href.replace(`#${key}#`, encodeURIComponent(value)); - } - }); - } - - if (href && href.startsWith('/')) { - href = `/documentation/${href.substr(1)}`; - - return ( - <Link target="_blank" to={href} {...other}> - {children} - </Link> - ); - } - - return href ? ( - <Link size={12} to={href} target="_blank" {...other}> - {children} - </Link> - ) : null; -} diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocCollapsibleBlock-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocCollapsibleBlock-test.tsx deleted file mode 100644 index b0eba13816e..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/DocCollapsibleBlock-test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { click } from '../../../helpers/testUtils'; -import DocCollapsibleBlock from '../DocCollapsibleBlock'; - -const children = ( - <div> - <h2>Foo</h2> - <p>Bar</p> - </div> -); - -it('should render a collapsible block', () => { - const wrapper = shallow(<DocCollapsibleBlock>{children}</DocCollapsibleBlock>); - expect(wrapper).toMatchSnapshot(); - - click(wrapper.find('a')); - wrapper.update(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should not render if not at least 2 children', () => { - const wrapper = shallow( - <DocCollapsibleBlock> - <div>foobar</div> - </DocCollapsibleBlock> - ); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx deleted file mode 100644 index fbc71124d70..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/DocLink-test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockAppState } from '../../../helpers/testMocks'; -import { DocLink } from '../DocLink'; - -it('should render simple link', () => { - expect( - shallow( - <DocLink appState={mockAppState({ canAdmin: false })} href="http://sample.com"> - link text - </DocLink> - ) - ).toMatchSnapshot(); -}); - -it('should render documentation link', () => { - expect( - shallow( - <DocLink appState={mockAppState({ canAdmin: false })} href="/foo/bar"> - link text - </DocLink> - ) - ).toMatchSnapshot(); -}); - -it('should render sonarqube link on sonarqube', () => { - const wrapper = shallow( - <DocLink appState={mockAppState({ canAdmin: false })} href="/#sonarqube#/foo/bar"> - link text - </DocLink> - ); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('SonarQubeLink').dive()).toMatchSnapshot(); -}); - -it('should render sonarqube admin link on sonarqube for admin', () => { - const wrapper = shallow( - <DocLink appState={mockAppState({ canAdmin: true })} href="/#sonarqube-admin#/foo/bar"> - link text - </DocLink> - ); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('SonarQubeAdminLink').dive()).toMatchSnapshot(); -}); - -it('should not render sonarqube admin link on sonarqube for non-admin', () => { - const wrapper = shallow( - <DocLink appState={mockAppState({ canAdmin: false })} href="/#sonarqube-admin#/foo/bar"> - link text - </DocLink> - ); - expect(wrapper.find('SonarQubeAdminLink').dive()).toMatchSnapshot(); -}); - -it('should render documentation anchor', () => { - expect( - shallow( - <DocLink appState={mockAppState({ canAdmin: false })} href="#quality-profiles"> - link text - </DocLink> - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx deleted file mode 100644 index 1df772fbe6f..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { scrollToElement } from '../../../helpers/scrolling'; -import { mockEvent } from '../../../helpers/testUtils'; -import DocMarkdownBlock from '../DocMarkdownBlock'; - -const CONTENT = ` -## Lorem ipsum - -Quisque vitae tincidunt felis. Nam blandit risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. - -## Sit amet - -### Maecenas diam - -Velit, vestibulum nec ultrices id, mollis eget arcu. Sed dapibus, sapien ut auctor consectetur, mi tortor vestibulum ante, eget dapibus lacus risus. - -### Integer - -At cursus turpis. Aenean at elit fringilla, porttitor mi eget, dapibus nisi. Donec quis congue odio. - -## Nam blandit - -Risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. -`; - -// mock `remark` & co to work around the issue with cjs imports -jest.mock('remark', () => jest.requireActual('remark')); -jest.mock('remark-rehype', () => jest.requireActual('remark-rehype')); -jest.mock('rehype-raw', () => jest.requireActual('rehype-raw')); -jest.mock('rehype-react', () => jest.requireActual('rehype-react')); -jest.mock('rehype-slug', () => jest.requireActual('rehype-slug')); - -jest.mock('../../../helpers/scrolling', () => ({ - scrollToElement: jest.fn() -})); - -const WINDOW_HEIGHT = 800; -const originalWindowHeight = window.innerHeight; - -const historyPushState = jest.fn(); -const originalHistoryPushState = history.pushState; - -beforeEach(jest.clearAllMocks); - -beforeAll(() => { - Object.defineProperty(window, 'innerHeight', { - writable: true, - configurable: true, - value: WINDOW_HEIGHT - }); - Object.defineProperty(history, 'pushState', { - writable: true, - configurable: true, - value: historyPushState - }); -}); - -afterAll(() => { - Object.defineProperty(window, 'innerHeight', { - writable: true, - configurable: true, - value: originalWindowHeight - }); - Object.defineProperty(history, 'pushState', { - writable: true, - configurable: true, - value: originalHistoryPushState - }); -}); - -it('should render correctly', () => { - expect(shallowRender({ content: 'this is *bold* text' })).toMatchSnapshot('default'); - expect( - shallowRender({ content: 'some [link](/quality-profiles)' }).find('withChildProps') - ).toMatchSnapshot('custom component for links'); - expect( - shallowRender({ - childProps: { foo: 'bar' }, - content: 'some [link](#quality-profiles)', - isTooltip: true - }).find('withChildProps') - ).toMatchSnapshot('custom props for links'); - expect(shallowRender({ content: CONTENT, stickyToc: true })).toMatchSnapshot('sticky TOC'); -}); - -it('should correctly scroll to clicked headings', () => { - const element = {} as Element; - const querySelector: (selector: string) => Element | null = jest.fn((selector: string) => - selector === '#id' ? element : null - ); - const preventDefault = jest.fn(); - const wrapper = shallowRender(); - const instance = wrapper.instance(); - - // Node Ref isn't set yet. - instance.handleAnchorClick('#unknown', mockEvent()); - expect(scrollToElement).not.toHaveBeenCalled(); - - // Set node Ref. - instance.node = { querySelector } as HTMLElement; - - // Unknown element. - instance.handleAnchorClick('#unknown', mockEvent()); - expect(scrollToElement).not.toHaveBeenCalled(); - - // Known element, should scroll. - instance.handleAnchorClick('#id', mockEvent({ preventDefault })); - expect(scrollToElement).toHaveBeenCalledWith(element, { bottomOffset: 720 }); - expect(preventDefault).toHaveBeenCalled(); - expect(historyPushState).toHaveBeenCalledWith(null, '', '#id'); -}); - -it('should correctly scroll to a specific heading if passed as a prop', () => { - jest.useFakeTimers(); - - const element = {} as Element; - const querySelector: (_: string) => Element | null = jest.fn(() => element); - const wrapper = shallowRender({ scrollToHref: '#id' }); - const instance = wrapper.instance(); - instance.node = { querySelector } as HTMLElement; - - expect(scrollToElement).not.toHaveBeenCalled(); - - jest.runAllTimers(); - - expect(scrollToElement).toHaveBeenCalledWith(element, { bottomOffset: 720 }); - jest.runOnlyPendingTimers(); - jest.useRealTimers(); -}); - -function shallowRender(props: Partial<DocMarkdownBlock['props']> = {}) { - return shallow<DocMarkdownBlock>(<DocMarkdownBlock content="" {...props} />); -} diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocToc-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocToc-test.tsx deleted file mode 100644 index 1a837603275..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/DocToc-test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { mount } from 'enzyme'; -import * as React from 'react'; -import { click, scrollTo } from '../../../helpers/testUtils'; -import DocToc from '../DocToc'; - -const OFFSET = 300; - -const CONTENT = ` -## Lorem ipsum - -Quisque vitae tincidunt felis. Nam blandit risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. - -## Sit amet - -### Maecenas diam - -Velit, vestibulum nec ultrices id, mollis eget arcu. Sed dapibus, sapien ut auctor consectetur, mi tortor vestibulum ante, eget dapibus lacus risus. - -### Integer - -At cursus turpis. Aenean at elit fringilla, porttitor mi eget, dapibus nisi. Donec quis congue odio. - -## Nam blandit - -Risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. -`; - -jest.mock('remark', () => { - const remark = jest.requireActual('remark'); - return remark; -}); - -jest.mock('remark-react', () => { - const remarkReact = jest.requireActual('remark-react'); - return remarkReact; -}); - -jest.mock('lodash', () => { - const lodash = jest.requireActual('lodash'); - lodash.debounce = (fn: any) => fn; - return lodash; -}); - -jest.mock('react-dom', () => ({ - findDOMNode: jest.fn() -})); - -it('should render correctly', () => { - const wrapper = renderComponent(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should trigger the handler when an anchor is clicked', () => { - const onAnchorClick = jest.fn(); - const wrapper = renderComponent({ onAnchorClick }); - click(wrapper.find('a[href="#sit-amet"]')); - expect(onAnchorClick).toHaveBeenCalled(); -}); - -it('should highlight anchors when scrolling', () => { - mockDomEnv(); - const wrapper = renderComponent(); - - scrollTo({ top: OFFSET }); - expect(wrapper.state('highlightAnchor')).toEqual('#lorem-ipsum'); - - scrollTo({ top: OFFSET * 3 }); - expect(wrapper.state('highlightAnchor')).toEqual('#nam-blandit'); -}); - -function renderComponent(props: Partial<DocToc['props']> = {}) { - return mount(<DocToc content={CONTENT} onAnchorClick={jest.fn()} {...props} />); -} - -function mockDomEnv() { - const findDOMNode = require('react-dom').findDOMNode as jest.Mock<any>; - const parent = document.createElement('div'); - const element = document.createElement('div'); - parent.appendChild(element); - - let offset = OFFSET; - (CONTENT.match(/^## .+$/gm) as Array<string>).forEach(match => { - const slug = match - .replace(/^#+ */, '') - .replace(' ', '-') - .toLowerCase() - .trim(); - const heading = document.createElement('h2'); - heading.id = slug; - Object.defineProperty(heading, 'offsetTop', { value: offset }); - offset += OFFSET; - - parent.appendChild(heading); - }); - - findDOMNode.mockReturnValue(element); -} diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltipLink-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltipLink-test.tsx deleted file mode 100644 index 632d4d31af6..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltipLink-test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import DocTooltipLink from '../DocTooltipLink'; - -it('should render simple link', () => { - expect(shallow(<DocTooltipLink href="http://sample.com" />)).toMatchSnapshot(); -}); - -it('should render internal link', () => { - expect(shallow(<DocTooltipLink href="/foo/bar" />)).toMatchSnapshot(); -}); - -it('should render links with custom props', () => { - expect( - shallow(<DocTooltipLink customProps={{ bar: 'baz' }} href="/foo/#bar#" />) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocCollapsibleBlock-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocCollapsibleBlock-test.tsx.snap deleted file mode 100644 index 7d1afc88f86..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocCollapsibleBlock-test.tsx.snap +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should not render if not at least 2 children 1`] = `""`; - -exports[`should render a collapsible block 1`] = ` -<div - className="collapse-container" -> - <a - aria-expanded={false} - aria-haspopup={true} - className="link-no-underline" - href="#" - onClick={[Function]} - role="button" - > - <OpenCloseIcon - className="text-middle little-spacer-right" - open={false} - /> - Foo - </a> -</div> -`; - -exports[`should render a collapsible block 2`] = ` -<div - className="collapse-container" -> - <a - aria-expanded={true} - aria-haspopup={true} - className="link-no-underline" - href="#" - onClick={[Function]} - role="button" - > - <OpenCloseIcon - className="text-middle little-spacer-right" - open={true} - /> - Foo - </a> - <p - key=".1" - > - Bar - </p> -</div> -`; diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap deleted file mode 100644 index 48b6223cb29..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocLink-test.tsx.snap +++ /dev/null @@ -1,69 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should not render sonarqube admin link on sonarqube for non-admin 1`] = ` -<Fragment> - link text -</Fragment> -`; - -exports[`should render documentation anchor 1`] = ` -<a - href="#" - onClick={[Function]} -> - link text -</a> -`; - -exports[`should render documentation link 1`] = ` -<ForwardRef(Link) - to="/documentation/foo/bar" -> - link text -</ForwardRef(Link)> -`; - -exports[`should render simple link 1`] = ` -<ForwardRef(Link) - size={12} - target="_blank" - to="http://sample.com" -> - link text -</ForwardRef(Link)> -`; - -exports[`should render sonarqube admin link on sonarqube for admin 1`] = ` -<SonarQubeAdminLink - canAdmin={true} - url="/#sonarqube-admin#/foo/bar" -> - link text -</SonarQubeAdminLink> -`; - -exports[`should render sonarqube admin link on sonarqube for admin 2`] = ` -<ForwardRef(Link) - target="_blank" - to="/foo/bar" -> - link text -</ForwardRef(Link)> -`; - -exports[`should render sonarqube link on sonarqube 1`] = ` -<SonarQubeLink - url="/#sonarqube#/foo/bar" -> - link text -</SonarQubeLink> -`; - -exports[`should render sonarqube link on sonarqube 2`] = ` -<ForwardRef(Link) - target="_blank" - to="/foo/bar" -> - link text -</ForwardRef(Link)> -`; diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocMarkdownBlock-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocMarkdownBlock-test.tsx.snap deleted file mode 100644 index e15b023ec86..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocMarkdownBlock-test.tsx.snap +++ /dev/null @@ -1,148 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: custom component for links 1`] = ` -<withChildProps - href="/quality-profiles" - key="h-2" -> - link -</withChildProps> -`; - -exports[`should render correctly: custom props for links 1`] = ` -<withChildProps - href="#quality-profiles" - key="h-2" -> - link -</withChildProps> -`; - -exports[`should render correctly: default 1`] = ` -<div - className="markdown" -> - <div - className="markdown-content" - > - <div> - <p - key="h-1" - > - this is - <em - key="h-2" - > - bold - </em> - text - </p> - </div> - </div> -</div> -`; - -exports[`should render correctly: sticky TOC 1`] = ` -<div - className="markdown has-toc" -> - <div - className="markdown-content" - > - <div> - <Block - key="h-1" - > - <h2 - id="lorem-ipsum" - key="h-2" - > - Lorem ipsum - </h2> - - - <p - key="h-3" - > - Quisque vitae tincidunt felis. Nam blandit risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. - </p> - - - <h2 - id="sit-amet" - key="h-4" - > - Sit amet - </h2> - - - <h3 - id="maecenas-diam" - key="h-5" - > - Maecenas diam - </h3> - - - <p - key="h-6" - > - Velit, vestibulum nec ultrices id, mollis eget arcu. Sed dapibus, sapien ut auctor consectetur, mi tortor vestibulum ante, eget dapibus lacus risus. - </p> - - - <h3 - id="integer" - key="h-7" - > - Integer - </h3> - - - <p - key="h-8" - > - At cursus turpis. Aenean at elit fringilla, porttitor mi eget, dapibus nisi. Donec quis congue odio. - </p> - - - <h2 - id="nam-blandit" - key="h-9" - > - Nam blandit - </h2> - - - <p - key="h-10" - > - Risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. - </p> - </Block> - </div> - </div> - <DocToc - content=" -## Lorem ipsum - -Quisque vitae tincidunt felis. Nam blandit risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. - -## Sit amet - -### Maecenas diam - -Velit, vestibulum nec ultrices id, mollis eget arcu. Sed dapibus, sapien ut auctor consectetur, mi tortor vestibulum ante, eget dapibus lacus risus. - -### Integer - -At cursus turpis. Aenean at elit fringilla, porttitor mi eget, dapibus nisi. Donec quis congue odio. - -## Nam blandit - -Risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. -" - onAnchorClick={[Function]} - /> -</div> -`; diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocToc-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocToc-test.tsx.snap deleted file mode 100644 index ea33e896b40..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocToc-test.tsx.snap +++ /dev/null @@ -1,62 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<DocToc - content=" -## Lorem ipsum - -Quisque vitae tincidunt felis. Nam blandit risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. - -## Sit amet - -### Maecenas diam - -Velit, vestibulum nec ultrices id, mollis eget arcu. Sed dapibus, sapien ut auctor consectetur, mi tortor vestibulum ante, eget dapibus lacus risus. - -### Integer - -At cursus turpis. Aenean at elit fringilla, porttitor mi eget, dapibus nisi. Donec quis congue odio. - -## Nam blandit - -Risus placerat, efficitur enim ut, pellentesque sem. Mauris non lorem auctor, consequat neque eget, dignissim augue. -" - onAnchorClick={[MockFunction]} -> - <div - className="markdown-toc" - > - <div - className="markdown-toc-content" - > - <h4> - documentation.on_this_page - </h4> - <a - className="" - href="#lorem-ipsum" - key="Lorem ipsum" - onClick={[Function]} - > - Lorem ipsum - </a> - <a - className="" - href="#sit-amet" - key="Sit amet" - onClick={[Function]} - > - Sit amet - </a> - <a - className="" - href="#nam-blandit" - key="Nam blandit" - onClick={[Function]} - > - Nam blandit - </a> - </div> - </div> -</DocToc> -`; diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap deleted file mode 100644 index 43cac601766..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltipLink-test.tsx.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render internal link 1`] = ` -<ForwardRef(Link) - target="_blank" - to="/documentation/foo/bar" -/> -`; - -exports[`should render links with custom props 1`] = ` -<ForwardRef(Link) - target="_blank" - to="/documentation/foo/baz" -/> -`; - -exports[`should render simple link 1`] = ` -<ForwardRef(Link) - size={12} - target="_blank" - to="http://sample.com" -/> -`; diff --git a/server/sonar-web/src/main/js/components/docs/plugins/__tests__/remark-only-toc-test.ts b/server/sonar-web/src/main/js/components/docs/plugins/__tests__/remark-only-toc-test.ts deleted file mode 100644 index e3c6a7cde19..00000000000 --- a/server/sonar-web/src/main/js/components/docs/plugins/__tests__/remark-only-toc-test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 util from 'mdast-util-toc'; -import onlyToc from '../remark-only-toc'; - -jest.mock('mdast-util-toc', () => ({ - __esModule: true, - default: jest.fn().mockReturnValue({}) -})); - -it('should only render toc', () => { - const node = { type: 'test', children: ['a'] }; - onlyToc()(node); - expect(node.children).toHaveLength(0); - - (util as jest.Mock).mockReturnValue({ index: -1 }); - node.children.push('a'); - - onlyToc()(node); - expect(node.children).toHaveLength(0); - - (util as jest.Mock).mockReturnValue({ index: 0 }); - node.children.push('a'); - - onlyToc()(node); - expect(node.children).toHaveLength(0); - - (util as jest.Mock).mockReturnValue({ index: 0, map: 'a' }); - node.children.push('a'); - - onlyToc()(node); - expect(node.children).toHaveLength(1); -}); diff --git a/server/sonar-web/src/main/js/components/docs/plugins/remark-only-toc.ts b/server/sonar-web/src/main/js/components/docs/plugins/remark-only-toc.ts deleted file mode 100644 index 06d95a4ead9..00000000000 --- a/server/sonar-web/src/main/js/components/docs/plugins/remark-only-toc.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 util from 'mdast-util-toc'; -import { Node } from 'unist'; - -/** - * This is a simplified version of the remark-toc plugin: https://github.com/remarkjs/remark-toc - * It *only* renders the TOC, and leaves all the rest out. - */ -export default function onlyToc() { - return transformer; - - function transformer(node: Node) { - const result = util(node, { heading: 'doctoc', maxDepth: 2 }); - - if (result.index === null || result.index === -1 || !result.map) { - node.children = []; - } else { - node.children = [result.map]; - } - } -} diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx index 256af65b302..d1d5c932654 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { getBaseUrl } from '../../helpers/system'; import { SuggestionLink } from '../../types/types'; +import DocLink from '../common/DocLink'; import Link from '../common/Link'; import { DropdownOverlay } from '../controls/Dropdown'; import { SuggestionsContext } from './SuggestionsContext'; @@ -38,7 +39,7 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> { * If we have at least 1 suggestion, it will make the call first, and prevent 'documentation' from * getting the focus. */ - focusFirstItem = (node: HTMLAnchorElement | null) => { + focusFirstItem: React.Ref<HTMLAnchorElement> = (node: HTMLAnchorElement | null) => { if (node && !this.firstItem) { this.firstItem = node; this.firstItem.focus(); @@ -59,16 +60,15 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> { } return ( <ul className="menu abs-width-240" role="group"> - {this.renderTitle(translate('embed_docs.suggestion'))} + {this.renderTitle(translate('docs.suggestion'))} {suggestions.map((suggestion, i) => ( <li key={suggestion.link}> - <Link - ref={i === 0 ? this.focusFirstItem : undefined} + <DocLink + innerRef={i === 0 ? this.focusFirstItem : undefined} onClick={this.props.onClose} - target="_blank" to={suggestion.link}> {suggestion.text} - </Link> + </DocLink> </li> ))} </ul> @@ -96,13 +96,9 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> { <SuggestionsContext.Consumer>{this.renderSuggestions}</SuggestionsContext.Consumer> <ul className="menu abs-width-240" role="group"> <li> - <Link - ref={this.focusFirstItem} - onClick={this.props.onClose} - target="_blank" - to="/documentation"> - {translate('embed_docs.documentation')} - </Link> + <DocLink innerRef={this.focusFirstItem} onClick={this.props.onClose} to="/"> + {translate('docs.documentation')} + </DocLink> </li> <li> <Link onClick={this.props.onClose} to="/web_api"> @@ -116,17 +112,17 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> { className="display-flex-center" to="https://community.sonarsource.com/" target="_blank"> - {translate('embed_docs.get_help')} + {translate('docs.get_help')} </Link> </li> </ul> <ul className="menu abs-width-240" role="group"> - {this.renderTitle(translate('embed_docs.stay_connected'))} + {this.renderTitle(translate('docs.stay_connected'))} <li> {this.renderIconLink( 'https://www.sonarqube.org/whats-new/?referrer=sonarqube', 'embed-doc/sq-icon.svg', - translate('embed_docs.news') + translate('docs.news') )} </li> <li> diff --git a/server/sonar-docs/src/EmbedDocsSuggestions.json b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsSuggestions.json index ac1f9b43c9a..af4a5bd79df 100644 --- a/server/sonar-docs/src/EmbedDocsSuggestions.json +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsSuggestions.json @@ -3,24 +3,24 @@ "api_documentation": [], "background_tasks": [ { - "link": "/documentation/analysis/background-tasks/", + "link": "/analysis/background-tasks/", "text": "About Background Tasks" } ], "code": [], "coding_rules": [ { - "link": "/documentation/instance-administration/quality-profiles/", + "link": "/instance-administration/quality-profiles/", "text": "Quality Profiles" } ], "component_measures": [ { - "link": "/documentation/user-guide/clean-as-you-code/", + "link": "/user-guide/clean-as-you-code/", "text": "Clean as You Code" }, { - "link": "/documentation/user-guide/metric-definitions/", + "link": "/user-guide/metric-definitions/", "text": "Metric Definitions" } ], @@ -29,45 +29,45 @@ "marketplace": [], "overview": [ { - "link": "/documentation/analysis/pull-request/", + "link": "/analysis/pull-request/", "text": "Enable Pull Request Decoration" }, { - "link": "/documentation/analysis/ci-integration-overview/", + "link": "/analysis/ci-integration-overview/", "text": "Set up CI analysis" }, { - "link": "/documentation/user-guide/clean-as-you-code/", + "link": "/user-guide/clean-as-you-code/", "text": "Clean as You Code" }, { - "link": "/documentation/user-guide/connected-mode/", + "link": "/user-guide/connected-mode/", "text": "SonarLint Connected Mode" } ], "permission_templates": [], "profiles": [ { - "link": "/documentation/instance-administration/quality-profiles/", + "link": "/instance-administration/quality-profiles/", "text": "Quality Profiles" } ], "project_activity": [], "project_baseline": [ { - "link": "/documentation/project-administration/new-code-period/", + "link": "/project-administration/new-code-period/", "text": "Defining New Code" } ], "project_quality_gate": [ { - "link": "/documentation/user-guide/clean-as-you-code/", + "link": "/user-guide/clean-as-you-code/", "text": "Clean as You Code" } ], "project_quality_profiles": [ { - "link": "/documentation/instance-administration/quality-profiles/", + "link": "/instance-administration/quality-profiles/", "text": "About Quality Profiles" } ], @@ -75,33 +75,33 @@ "projects": [], "pull_requests": [ { - "link": "/documentation/user-guide/clean-as-you-code/", + "link": "/user-guide/clean-as-you-code/", "text": "Clean as You Code" }, { - "link": "/documentation/analysis/pull-request/", + "link": "/analysis/pull-request/", "text": "Analyzing Pull Requests" }, { - "link": "/documentation/user-guide/connected-mode/", + "link": "/user-guide/connected-mode/", "text": "SonarLint connected mode" } ], "quality_gates": [ { - "link": "/documentation/user-guide/clean-as-you-code/", + "link": "/user-guide/clean-as-you-code/", "text": "Clean as You Code" } ], "quality_profiles": [ { - "link": "/documentation/instance-administration/quality-profiles/", + "link": "/instance-administration/quality-profiles/", "text": "Quality Profiles" } ], "security_reports": [ { - "link": "/documentation/user-guide/security-reports/", + "link": "/user-guide/security-reports/", "text": "About Security Reports" } ], @@ -111,7 +111,7 @@ "users": [], "webhooks": [ { - "link": "/documentation/project-administration/webhooks/", + "link": "/project-administration/webhooks/", "text": "About Webhooks" } ] diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsProvider.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsProvider.tsx index 89481d2ab81..70ec0da9d51 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsProvider.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsProvider.tsx @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import suggestionsJson from 'Docs/EmbedDocsSuggestions.json'; import * as React from 'react'; import { Dict, SuggestionLink } from '../../types/types'; +import suggestionsJson from './EmbedDocsSuggestions.json'; import { SuggestionsContext } from './SuggestionsContext'; type SuggestionsJson = Dict<SuggestionLink[]>; @@ -41,8 +41,6 @@ export default class SuggestionsProvider extends React.Component<{}, State> { } }); - suggestions = suggestions.filter(suggestion => suggestion.scope !== 'sonarcloud'); - this.setState({ suggestions }); }; diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx index 87f7315b3b2..c410eea978d 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx @@ -28,7 +28,7 @@ it('should render with no suggestions', () => { renderEmbedDocsPopup(); expect(screen.getAllByRole('link')).toHaveLength(5); - expect(screen.getByText('embed_docs.documentation')).toHaveFocus(); + expect(screen.getByText('docs.documentation')).toHaveFocus(); }); it('should render with suggestions', () => { diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx index 4cff54080c4..28a914dcee1 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx @@ -22,12 +22,9 @@ import * as React from 'react'; import SuggestionsProvider from '../SuggestionsProvider'; jest.mock( - 'Docs/EmbedDocsSuggestions.json', + '../EmbedDocsSuggestions.json', () => ({ - pageA: [ - { link: '/foo', text: 'Foo' }, - { link: '/bar', text: 'Bar', scope: 'sonarcloud' } - ], + pageA: [{ link: '/foo', text: 'Foo' }], pageB: [{ link: '/qux', text: 'Qux' }] }), { virtual: true } diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx index b4dc65e4647..77e20ad10a7 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx @@ -72,7 +72,7 @@ export default function IssueMessageTags(props: IssueMessageTagsProps) { content={translate('rules.status', ruleStatus, 'help')} links={[ { - href: '/documentation/user-guide/rules/', + href: '/user-guide/rules/', label: translateWithParameters('see_x', translate('rules')) } ]}> diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.tsx index 1aa48b199d7..b334f5373cd 100644 --- a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/AlertClassicEditor.tsx @@ -23,7 +23,7 @@ import { Alert } from '../../../../components/ui/Alert'; import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; import { translate } from '../../../../helpers/l10n'; import { AlmKeys } from '../../../../types/alm-settings'; -import Link from '../../../common/Link'; +import DocLink from '../../../common/DocLink'; export default function AlertClassicEditor() { return ( @@ -33,9 +33,9 @@ export default function AlertClassicEditor() { defaultMessage={translate('onboarding.tutorial.with.azure_pipelines.BranchAnalysis.info')} values={{ doc_link: ( - <Link to={ALM_DOCUMENTATION_PATHS[AlmKeys.Azure]} target="_blank"> + <DocLink to={ALM_DOCUMENTATION_PATHS[AlmKeys.Azure]}> {translate('onboarding.tutorial.with.azure_pipelines.BranchAnalysis.info.doc_link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx index ea1ed9c725f..20852c0405a 100644 --- a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx @@ -27,7 +27,7 @@ import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; import { translate } from '../../../../helpers/l10n'; import { AlmKeys } from '../../../../types/alm-settings'; import { Feature } from '../../../../types/features'; -import Link from '../../../common/Link'; +import DocLink from '../../../common/DocLink'; import SentenceWithHighlights from '../../components/SentenceWithHighlights'; export interface PublishStepsProps extends WithAvailableFeaturesProps {} @@ -67,11 +67,11 @@ export function PublishSteps(props: PublishStepsProps) { )} values={{ link: ( - <Link to={ALM_DOCUMENTATION_PATHS[AlmKeys.Azure]} target="_blank"> + <DocLink to={ALM_DOCUMENTATION_PATHS[AlmKeys.Azure]}> {translate( 'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.branch_protection.link' )} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/components/CompilationInfo.tsx b/server/sonar-web/src/main/js/components/tutorials/components/CompilationInfo.tsx index cd7fa1e8db5..0753cec1c97 100644 --- a/server/sonar-web/src/main/js/components/tutorials/components/CompilationInfo.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/components/CompilationInfo.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { Alert } from '../../../components/ui/Alert'; import { translate } from '../../../helpers/l10n'; +import DocLink from '../../common/DocLink'; export interface CompilationInfoProps { className?: string; @@ -35,12 +36,9 @@ export function CompilationInfo({ className = 'spacer-top spacer-bottom' }: Comp defaultMessage={translate('onboarding.tutorial.cfamilly.compilation_database_info')} values={{ link: ( - <a - href="/documentation/analysis/languages/cfamily/" - rel="noopener noreferrer" - target="_blank"> + <DocLink to="/analysis/languages/cfamily/"> {translate('onboarding.tutorial.cfamilly.compilation_database_info.link')} - </a> + </DocLink> ) }} /> @@ -51,12 +49,9 @@ export function CompilationInfo({ className = 'spacer-top spacer-bottom' }: Comp defaultMessage={translate('onboarding.tutorial.cfamilly.speed_caching')} values={{ link: ( - <a - href="/documentation/analysis/languages/cfamily/#analysis-cache" - rel="noopener noreferrer" - target="_blank"> + <DocLink to="/analysis/languages/cfamily/#analysis-cache"> {translate('onboarding.tutorial.cfamilly.speed_caching.link')} - </a> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/components/ProjectTokenScopeInfo.tsx b/server/sonar-web/src/main/js/components/tutorials/components/ProjectTokenScopeInfo.tsx index 202d297397c..d7cd473c8c8 100644 --- a/server/sonar-web/src/main/js/components/tutorials/components/ProjectTokenScopeInfo.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/components/ProjectTokenScopeInfo.tsx @@ -21,6 +21,7 @@ import classNames from 'classnames'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { translate } from '../../../helpers/l10n'; +import DocLink from '../../common/DocLink'; import Link from '../../common/Link'; import { Alert } from '../../ui/Alert'; @@ -40,11 +41,7 @@ export default function ProjectTokenScopeInfo({ className }: ProjectTokenScopeIn {translate('onboarding.token.text.user_account')} </Link> ), - doc_link: ( - <Link target="_blank" to="/documentation/user-guide/user-token/"> - {translate('documentation')} - </Link> - ) + doc_link: <DocLink to="/user-guide/user-token/">{translate('documentation')}</DocLink> }} /> </Alert> diff --git a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/CompilationInfo-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/CompilationInfo-test.tsx.snap index 93595e2e15d..10463c95d30 100644 --- a/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/CompilationInfo-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/components/__tests__/__snapshots__/CompilationInfo-test.tsx.snap @@ -13,13 +13,11 @@ exports[`should render correctly 1`] = ` id="onboarding.tutorial.cfamilly.compilation_database_info" values={ Object { - "link": <a - href="/documentation/analysis/languages/cfamily/" - rel="noopener noreferrer" - target="_blank" + "link": <withAppStateContext(DocLink) + to="/analysis/languages/cfamily/" > onboarding.tutorial.cfamilly.compilation_database_info.link - </a>, + </withAppStateContext(DocLink)>, } } /> @@ -30,13 +28,11 @@ exports[`should render correctly 1`] = ` id="onboarding.tutorial.cfamilly.speed_caching" values={ Object { - "link": <a - href="/documentation/analysis/languages/cfamily/#analysis-cache" - rel="noopener noreferrer" - target="_blank" + "link": <withAppStateContext(DocLink) + to="/analysis/languages/cfamily/#analysis-cache" > onboarding.tutorial.cfamilly.speed_caching.link - </a>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx index f2c4a4d2339..4705527a94f 100644 --- a/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/jenkins/PreRequisitesStep.tsx @@ -25,7 +25,7 @@ import ChevronRightIcon from '../../../components/icons/ChevronRightIcon'; import { Alert } from '../../../components/ui/Alert'; import { translate } from '../../../helpers/l10n'; import { AlmKeys } from '../../../types/alm-settings'; -import Link from '../../common/Link'; +import DocLink from '../../common/DocLink'; import SentenceWithHighlights from '../components/SentenceWithHighlights'; import Step from '../components/Step'; @@ -72,9 +72,9 @@ export default function PreRequisitesStep(props: PreRequisitesStepProps) { id="onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide" values={{ link: ( - <Link target="_blank" to="/documentation/analysis/jenkins/"> + <DocLink to="/analysis/jenkins/"> {translate('onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide.link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap index eba200ef8ff..474ed5565f6 100644 --- a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/__snapshots__/PreRequisitesStep-test.tsx.snap @@ -46,12 +46,11 @@ exports[`should render correctly: content 1`] = ` id="onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/jenkins/" + "link": <withAppStateContext(DocLink) + to="/analysis/jenkins/" > onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide.link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -106,12 +105,11 @@ exports[`should render correctly: content for branches disabled 1`] = ` id="onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/jenkins/" + "link": <withAppStateContext(DocLink) + to="/analysis/jenkins/" > onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide.link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -169,12 +167,11 @@ exports[`should render correctly: content for branches disabled, gitlab 1`] = ` id="onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/jenkins/" + "link": <withAppStateContext(DocLink) + to="/analysis/jenkins/" > onboarding.tutorial.with.jenkins.prereqs.step_by_step_guide.link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/DoneNextSteps.tsx b/server/sonar-web/src/main/js/components/tutorials/other/DoneNextSteps.tsx index 292bdc4387e..920b2695ee5 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/DoneNextSteps.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/DoneNextSteps.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { translate } from '../../../helpers/l10n'; import { Component } from '../../../types/types'; -import Link from '../../common/Link'; +import DocLink from '../../common/DocLink'; export interface DoneNextStepsProps { component: Component; @@ -51,24 +51,18 @@ export default function DoneNextSteps({ component }: DoneNextStepsProps) { id="onboarding.analysis.auto_refresh_after_analysis.check_these_links" values={{ link_branches: ( - <Link - to="/documentation/branches/overview/" - target="_blank" - rel="noopener noreferrer"> + <DocLink to="/branches/overview/"> {translate( 'onboarding.analysis.auto_refresh_after_analysis.check_these_links.branches' )} - </Link> + </DocLink> ), link_pr_analysis: ( - <Link - to="/documentation/analysis/pull-request/" - target="_blank" - rel="noopener noreferrer"> + <DocLink to="/analysis/pull-request/"> {translate( 'onboarding.analysis.auto_refresh_after_analysis.check_these_links.pr_analysis' )} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/TokenStep.tsx b/server/sonar-web/src/main/js/components/tutorials/other/TokenStep.tsx index d4ae7963b23..f28be6ea39f 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/TokenStep.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/TokenStep.tsx @@ -214,7 +214,7 @@ export default class TokenStep extends React.PureComponent<Props, State> { content={translate('onboarding.token.name.help')} links={[ { - href: '/documentation/user-guide/user-token/', + href: '/user-guide/user-token/', label: translate('learn_more') } ]} @@ -282,7 +282,7 @@ export default class TokenStep extends React.PureComponent<Props, State> { content={translate('onboarding.token.use_existing_token.help')} links={[ { - href: '/documentation/user-guide/user-token/', + href: '/user-guide/user-token/', label: translate('learn_more') } ]} diff --git a/server/sonar-web/src/main/js/components/tutorials/other/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/other/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap index c5c553d1340..61cea6b709b 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/other/__tests__/__snapshots__/DoneNextSteps-test.tsx.snap @@ -25,20 +25,16 @@ exports[`should render correctly: default 1`] = ` id="onboarding.analysis.auto_refresh_after_analysis.check_these_links" values={ Object { - "link_branches": <ForwardRef(Link) - rel="noopener noreferrer" - target="_blank" - to="/documentation/branches/overview/" + "link_branches": <withAppStateContext(DocLink) + to="/branches/overview/" > onboarding.analysis.auto_refresh_after_analysis.check_these_links.branches - </ForwardRef(Link)>, - "link_pr_analysis": <ForwardRef(Link) - rel="noopener noreferrer" - target="_blank" - to="/documentation/analysis/pull-request/" + </withAppStateContext(DocLink)>, + "link_pr_analysis": <withAppStateContext(DocLink) + to="/analysis/pull-request/" > onboarding.analysis.auto_refresh_after_analysis.check_these_links.pr_analysis - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -71,20 +67,16 @@ exports[`should render correctly: project admin 1`] = ` id="onboarding.analysis.auto_refresh_after_analysis.check_these_links" values={ Object { - "link_branches": <ForwardRef(Link) - rel="noopener noreferrer" - target="_blank" - to="/documentation/branches/overview/" + "link_branches": <withAppStateContext(DocLink) + to="/branches/overview/" > onboarding.analysis.auto_refresh_after_analysis.check_these_links.branches - </ForwardRef(Link)>, - "link_pr_analysis": <ForwardRef(Link) - rel="noopener noreferrer" - target="_blank" - to="/documentation/analysis/pull-request/" + </withAppStateContext(DocLink)>, + "link_pr_analysis": <withAppStateContext(DocLink) + to="/analysis/pull-request/" > onboarding.analysis.auto_refresh_after_analysis.check_these_links.pr_analysis - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/__tests__/__snapshots__/TokenStep-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/other/__tests__/__snapshots__/TokenStep-test.tsx.snap index e0696eebd28..ff1c2346c08 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/__tests__/__snapshots__/TokenStep-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/other/__tests__/__snapshots__/TokenStep-test.tsx.snap @@ -50,7 +50,7 @@ exports[`generates token 1`] = ` links={ Array [ Object { - "href": "/documentation/user-guide/user-token/", + "href": "/user-guide/user-token/", "label": "learn_more", }, ] @@ -210,7 +210,7 @@ exports[`generates token 2`] = ` links={ Array [ Object { - "href": "/documentation/user-guide/user-token/", + "href": "/user-guide/user-token/", "label": "learn_more", }, ] @@ -582,7 +582,7 @@ exports[`revokes token 3`] = ` links={ Array [ Object { - "href": "/documentation/user-guide/user-token/", + "href": "/user-guide/user-token/", "label": "learn_more", }, ] diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/DotNetExecute.tsx b/server/sonar-web/src/main/js/components/tutorials/other/commands/DotNetExecute.tsx index 2a97d7cb8b2..958f2ca6aaa 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/DotNetExecute.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/DotNetExecute.tsx @@ -22,8 +22,8 @@ import { FormattedMessage } from 'react-intl'; import { translate } from '../../../../helpers/l10n'; import { Component } from '../../../../types/types'; import CodeSnippet from '../../../common/CodeSnippet'; +import DocLink from '../../../common/DocLink'; import InstanceMessage from '../../../common/InstanceMessage'; -import Link from '../../../common/Link'; import DoneNextSteps from '../DoneNextSteps'; export interface DotNetExecuteProps { @@ -50,9 +50,9 @@ export default function DotNetExecute({ commands, component }: DotNetExecuteProp id="onboarding.analysis.docs" values={{ link: ( - <Link to="/documentation/analysis/scan/sonarscanner-for-msbuild/" target="_blank"> + <DocLink to="/analysis/scan/sonarscanner-for-msbuild/"> {translate('onboarding.analysis.msbuild.docs_link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/ExecBuildWrapper.tsx b/server/sonar-web/src/main/js/components/tutorials/other/commands/ExecBuildWrapper.tsx index 8d752b03e12..9bc2bfe920c 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/ExecBuildWrapper.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/ExecBuildWrapper.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { translate } from '../../../../helpers/l10n'; import CodeSnippet from '../../../common/CodeSnippet'; -import Link from '../../../common/Link'; +import DocLink from '../../../common/DocLink'; import { OSs } from '../../types'; export interface ExecBuildWrapperProps { @@ -56,9 +56,9 @@ export default function ExecBuildWrapper(props: ExecBuildWrapperProps) { id="onboarding.analysis.build_wrapper.docs" values={{ link: ( - <Link to="/documentation/analysis/languages/cfamily/" target="_blank"> + <DocLink to="/analysis/languages/cfamily/"> {translate('onboarding.analysis.build_wrapper.docs_link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/ExecScanner.tsx b/server/sonar-web/src/main/js/components/tutorials/other/commands/ExecScanner.tsx index c1d2c035f18..067af232fd2 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/ExecScanner.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/ExecScanner.tsx @@ -22,8 +22,8 @@ import { FormattedMessage } from 'react-intl'; import { translate } from '../../../../helpers/l10n'; import { Component } from '../../../../types/types'; import CodeSnippet from '../../../common/CodeSnippet'; +import DocLink from '../../../common/DocLink'; import InstanceMessage from '../../../common/InstanceMessage'; -import Link from '../../../common/Link'; import { OSs } from '../../types'; import { quote } from '../../utils'; import DoneNextSteps from '../DoneNextSteps'; @@ -65,9 +65,9 @@ export default function ExecScanner(props: ExecScannerProps) { id="onboarding.analysis.sq_scanner.docs" values={{ link: ( - <Link to="/documentation/analysis/scan/sonarscanner/" target="_blank"> + <DocLink to="/analysis/scan/sonarscanner/"> {translate('onboarding.analysis.sq_scanner.docs_link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/JavaGradle.tsx b/server/sonar-web/src/main/js/components/tutorials/other/commands/JavaGradle.tsx index afe3ed0030d..85a191cf74a 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/JavaGradle.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/JavaGradle.tsx @@ -22,8 +22,8 @@ import { FormattedMessage } from 'react-intl'; import { translate } from '../../../../helpers/l10n'; import { Component } from '../../../../types/types'; import CodeSnippet from '../../../common/CodeSnippet'; +import DocLink from '../../../common/DocLink'; import InstanceMessage from '../../../common/InstanceMessage'; -import Link from '../../../common/Link'; import DoneNextSteps from '../DoneNextSteps'; export interface JavaGradleProps { @@ -68,9 +68,7 @@ export default function JavaGradle(props: JavaGradleProps) { id="onboarding.analysis.java.gradle.latest_version" values={{ link: ( - <Link to="/documentation/analysis/scan/sonarscanner-for-gradle/" target="_blank"> - {translate('here')} - </Link> + <DocLink to="/analysis/scan/sonarscanner-for-gradle/">{translate('here')}</DocLink> ) }} /> @@ -86,9 +84,9 @@ export default function JavaGradle(props: JavaGradleProps) { id="onboarding.analysis.docs" values={{ link: ( - <Link to="/documentation/analysis/scan/sonarscanner-for-gradle/" target="_blank"> + <DocLink to="/analysis/scan/sonarscanner-for-gradle/"> {translate('onboarding.analysis.java.gradle.docs_link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/JavaMaven.tsx b/server/sonar-web/src/main/js/components/tutorials/other/commands/JavaMaven.tsx index 5a939f2a36c..473f99d17ac 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/JavaMaven.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/JavaMaven.tsx @@ -22,8 +22,8 @@ import { FormattedMessage } from 'react-intl'; import { translate } from '../../../../helpers/l10n'; import { Component } from '../../../../types/types'; import CodeSnippet from '../../../common/CodeSnippet'; +import DocLink from '../../../common/DocLink'; import InstanceMessage from '../../../common/InstanceMessage'; -import Link from '../../../common/Link'; import DoneNextSteps from '../DoneNextSteps'; export interface JavaMavenProps { @@ -54,9 +54,9 @@ export default function JavaMaven(props: JavaMavenProps) { id="onboarding.analysis.docs" values={{ link: ( - <Link to="/documentation/analysis/scan/sonarscanner-for-maven/" target="_blank"> + <DocLink to="/analysis/scan/sonarscanner-for-maven/"> {translate('onboarding.analysis.java.maven.docs_link')} - </Link> + </DocLink> ) }} /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap index 8c2722936e3..03cd7a29122 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/DotNetExecute-test.tsx.snap @@ -28,12 +28,11 @@ exports[`should render correctly 1`] = ` id="onboarding.analysis.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner-for-msbuild/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner-for-msbuild/" > onboarding.analysis.msbuild.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap index 4060e8cf706..378ca7b16c0 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/ExecBuildWrapper-test.tsx.snap @@ -23,12 +23,11 @@ exports[`Shoud renders for "linux" correctly 1`] = ` id="onboarding.analysis.build_wrapper.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/languages/cfamily/" + "link": <withAppStateContext(DocLink) + to="/analysis/languages/cfamily/" > onboarding.analysis.build_wrapper.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -59,12 +58,11 @@ exports[`Shoud renders for "mac" correctly 1`] = ` id="onboarding.analysis.build_wrapper.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/languages/cfamily/" + "link": <withAppStateContext(DocLink) + to="/analysis/languages/cfamily/" > onboarding.analysis.build_wrapper.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -95,12 +93,11 @@ exports[`Shoud renders for "win" correctly 1`] = ` id="onboarding.analysis.build_wrapper.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/languages/cfamily/" + "link": <withAppStateContext(DocLink) + to="/analysis/languages/cfamily/" > onboarding.analysis.build_wrapper.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap index b4b780f819f..ef9958ca68a 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/ExecScanner-test.tsx.snap @@ -33,12 +33,11 @@ exports[`should render correctly for "linux" 1`] = ` id="onboarding.analysis.sq_scanner.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner/" > onboarding.analysis.sq_scanner.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -103,12 +102,11 @@ exports[`should render correctly for "mac" 1`] = ` id="onboarding.analysis.sq_scanner.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner/" > onboarding.analysis.sq_scanner.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -173,12 +171,11 @@ exports[`should render correctly for "win" 1`] = ` id="onboarding.analysis.sq_scanner.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner/" > onboarding.analysis.sq_scanner.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -243,12 +240,11 @@ exports[`should render correctly for cfamily 1`] = ` id="onboarding.analysis.sq_scanner.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner/" > onboarding.analysis.sq_scanner.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -313,12 +309,11 @@ exports[`should render correctly for remote execution 1`] = ` id="onboarding.analysis.sq_scanner.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner/" > onboarding.analysis.sq_scanner.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap index a980bc7303e..3356e83af9e 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap @@ -28,12 +28,11 @@ exports[`renders correctly 1`] = ` id="onboarding.analysis.java.gradle.latest_version" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner-for-gradle/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner-for-gradle/" > here - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> @@ -62,12 +61,11 @@ exports[`renders correctly 1`] = ` id="onboarding.analysis.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner-for-gradle/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner-for-gradle/" > onboarding.analysis.java.gradle.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap index f91c2cc29cc..c004deea92a 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/other/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap @@ -32,12 +32,11 @@ exports[`renders correctly 1`] = ` id="onboarding.analysis.docs" values={ Object { - "link": <ForwardRef(Link) - target="_blank" - to="/documentation/analysis/scan/sonarscanner-for-maven/" + "link": <withAppStateContext(DocLink) + to="/analysis/scan/sonarscanner-for-maven/" > onboarding.analysis.java.maven.docs_link - </ForwardRef(Link)>, + </withAppStateContext(DocLink)>, } } /> diff --git a/server/sonar-web/src/main/js/helpers/__tests__/__snapshots__/markdown-test.ts.snap b/server/sonar-web/src/main/js/helpers/__tests__/__snapshots__/markdown-test.ts.snap deleted file mode 100644 index cdc1f9a74e0..00000000000 --- a/server/sonar-web/src/main/js/helpers/__tests__/__snapshots__/markdown-test.ts.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should cut sonarqube/sonarcloud/static content 1`] = ` -" -This text has inline text for SonarQube. Donec sed nulla magna. - -This is text for SonarQube, multi-line. Consectetur adipiscing elit. Duis dignissim nulla at massa iaculis interdum. -Aenean sit amet lacus a tortor ullamcorper interdum. Donec sed nulla magna. - - - - - -This is text for SonarQube, single line. - -* In hac habitasse -* Duis sagittis semper sapien nec tempor -* This is a bullet point for SonarQube - -* Platea dictumst - -Duis sagittis semper sapien nec tempor. Nullam vehicula nisi vitae nisi interdum aliquam. - -| Parameter Name | Description | -| --------------------- | ------------------ | -| sonar.pullrequest.github.repository | SLUG of the GitHub Repo | -| sonar.pullrequest.github.endpoint | The API url for your GitHub instance. | -" -`; - -exports[`should not break when conditional tags are misused 1`] = ` -"Random SC text - Break - Bad SQ conditional formatting - Break - SC text - Break - Bad SQ conditional formatting - Break - Static stuff" -`; diff --git a/server/sonar-web/src/main/js/helpers/__tests__/markdown-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/markdown-test.ts deleted file mode 100644 index 5c74c9f20ab..00000000000 --- a/server/sonar-web/src/main/js/helpers/__tests__/markdown-test.ts +++ /dev/null @@ -1,178 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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. - */ -/* eslint-disable no-console */ -import { filterContent, getFrontMatter, separateFrontMatter } from '../markdown'; - -jest.mock('../system', () => ({ - getInstance: () => 'SonarQube' -})); - -it('returns parsed frontmatter of one item', () => { - expect( - getFrontMatter(` - --- - title: Foo - --- - - some content here - `) - ).toEqual({ title: 'Foo' }); -}); - -it('returns parsed frontmatter of two items', () => { - expect( - getFrontMatter(` - --- - title: Foo - scope: sonarcloud - --- - - some content here - `) - ).toEqual({ title: 'Foo', scope: 'sonarcloud' }); -}); - -it('returns empty object when frontmatter is missing', () => { - expect( - getFrontMatter(` - some content here - `) - ).toEqual({}); -}); - -it('returns empty object when frontmatter is unfinished', () => { - expect( - getFrontMatter(` - --- - title: Foo - - some content here - `) - ).toEqual({}); -}); - -it('ignores frontmatter in wrong format', () => { - expect( - getFrontMatter(` - --- - title: Foo - scope: sonarcloud: sonarqube - --- - - some content here - `) - ).toEqual({ title: 'Foo' }); -}); - -it('returns parsed frontmatter and the rest of the content', () => { - expect( - separateFrontMatter(` ---- -title: Foo ---- - -some content here`) - ).toEqual({ content: '\nsome content here', frontmatter: { title: 'Foo' } }); -}); - -it('returns empty object and content when frontmatter is missing', () => { - expect(separateFrontMatter('some content here')).toEqual({ - content: 'some content here', - frontmatter: {} - }); -}); - -it('returns full content when frontmatter has bad formatting', () => { - const content = ` - ---- - title: Foo - scope: sonarcloud - --- - - some content here`; - - expect(separateFrontMatter(content)).toEqual({ content, frontmatter: {} }); -}); - -it('replaces {instance}', () => { - expect( - filterContent('This is {instance} content. It replaces all {instance}{instance} messages') - ).toBe('This is SonarQube content. It replaces all SonarQubeSonarQube messages'); -}); - -it('should cut sonarqube/sonarcloud/static content', () => { - const content = ` -This text has inline text for <!-- sonarqube -->SonarQube<!-- /sonarqube --><!-- sonarcloud -->SonarCloud<!-- /sonarcloud -->. Donec sed nulla magna. - -<!-- sonarqube --> -This is text for SonarQube, multi-line. Consectetur adipiscing elit. Duis dignissim nulla at massa iaculis interdum. -Aenean sit amet lacus a tortor ullamcorper interdum. Donec sed nulla magna. -<!-- /sonarqube --> - -<!-- sonarcloud --> -This is text for SonarCloud, multi-line. In hac habitasse platea dictumst. Duis sagittis semper sapien nec tempor. Nullam vehicula nisi vitae nisi interdum aliquam. Mauris volutpat nunc non fermentum rhoncus. Aenean laoreet, orci vitae tempor bibendum, -metus nisl euismod neque, vitae euismod nibh nisl eu velit. Vivamus luctus suscipit elit vel semper. -<!-- /sonarcloud --> - -<!-- static --> -This is static text. -<!-- /static --> - -<!-- sonarqube --> -This is text for SonarQube, single line. -<!-- /sonarqube --> - -* In hac habitasse -* Duis sagittis semper sapien nec tempor -<!-- sonarqube -->* This is a bullet point for SonarQube<!-- /sonarqube --> -<!-- sonarcloud -->* This is a bullet point for SonarCloud<!-- /sonarcloud --> -* Platea dictumst - -Duis sagittis semper sapien nec tempor. Nullam vehicula nisi vitae nisi interdum aliquam. - -| Parameter Name | Description | -| --------------------- | ------------------ | -| sonar.pullrequest.github.repository | SLUG of the GitHub Repo | -<!-- sonarqube --> -| sonar.pullrequest.github.endpoint | The API url for your GitHub instance. | -<!-- /sonarqube --> -`; - - expect(filterContent(content)).toMatchSnapshot(); -}); - -it('should not break when conditional tags are misused', () => { - const originalConsoleError = console.error; - console.error = jest.fn(); - - const content = `Random <!-- /sonarqube -->SC <!-- sonarqube -->text - Break - Bad <!-- /sonarcloud -->SQ conditional <!-- sonarcloud -->formatting - Break - <!-- sonarqube -->SC<!-- /sonarqube --><!-- sonarcloud -->SQ<!-- /sonarcloud --> text - Break - Bad <!-- /sonarcloud -->SQ conditional <!-- sonarcloud -->formatting - Break - <!-- static -->Static <!-- /sonarcloud -->stuff`; - expect(filterContent(content)).toMatchSnapshot(); - expect(console.error).toHaveBeenCalledTimes(2); - - console.error = originalConsoleError; -}); diff --git a/server/sonar-web/src/main/js/helpers/constants.ts b/server/sonar-web/src/main/js/helpers/constants.ts index 9da9a03c8e9..0f73991916f 100644 --- a/server/sonar-web/src/main/js/helpers/constants.ts +++ b/server/sonar-web/src/main/js/helpers/constants.ts @@ -57,11 +57,11 @@ export const RATING_COLORS = [ export const PROJECT_KEY_MAX_LEN = 400; export const ALM_DOCUMENTATION_PATHS = { - [AlmKeys.Azure]: '/documentation/analysis/azuredevops-integration/', - [AlmKeys.BitbucketServer]: '/documentation/analysis/bitbucket-integration/', - [AlmKeys.BitbucketCloud]: '/documentation/analysis/bitbucket-cloud-integration/', - [AlmKeys.GitHub]: '/documentation/analysis/github-integration/', - [AlmKeys.GitLab]: '/documentation/analysis/gitlab-integration/' + [AlmKeys.Azure]: '/analysis/azuredevops-integration/', + [AlmKeys.BitbucketServer]: '/analysis/bitbucket-integration/', + [AlmKeys.BitbucketCloud]: '/analysis/bitbucket-cloud-integration/', + [AlmKeys.GitHub]: '/analysis/github-integration/', + [AlmKeys.GitLab]: '/analysis/gitlab-integration/' }; export const IMPORT_COMPATIBLE_ALMS = [ diff --git a/server/sonar-web/src/main/js/helpers/markdown.d.ts b/server/sonar-web/src/main/js/helpers/docs.ts index 629a797df71..6bae9f6f56b 100644 --- a/server/sonar-web/src/main/js/helpers/markdown.d.ts +++ b/server/sonar-web/src/main/js/helpers/docs.ts @@ -17,18 +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. */ -interface FrontMatter { - [x: string]: string; -} - -interface ParsedContent { - content: string; - frontmatter: FrontMatter; -} -export function getFrontMatter(content: string): FrontMatter; +const VERSION_PARSER = /^(\d+\.\d+).*$/; -export function separateFrontMatter(content: string): ParsedContent; +export function getUrlForDoc(version: string, to: string) { + const versionPrefix = VERSION_PARSER.exec(version); + const isSnapshot = version.indexOf('SNAPSHOT') !== -1; + const docPrefix = + versionPrefix && versionPrefix.length === 2 && !isSnapshot ? versionPrefix[1] : 'latest'; -/** Removes SonarQube/SonarCloud only content */ -export function filterContent(content: string): string; + return `https://docs.sonarqube.org/${docPrefix}${to}`; +} diff --git a/server/sonar-web/src/main/js/helpers/markdown.js b/server/sonar-web/src/main/js/helpers/markdown.js deleted file mode 100644 index 6701d972642..00000000000 --- a/server/sonar-web/src/main/js/helpers/markdown.js +++ /dev/null @@ -1,119 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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. - */ -// keep this file in JavaScript, because it is used by a webpack loader -module.exports = { getFrontMatter, separateFrontMatter, filterContent }; - -function getFrontMatter(content) { - const lines = content.split('\n'); - const position = getFrontMatterPosition(lines); - return position ? parseFrontMatter(lines.slice(position.firstLine + 1, position.lastLine)) : {}; -} - -function separateFrontMatter(content) { - const lines = content.split('\n'); - const position = getFrontMatterPosition(lines); - if (position) { - const frontmatter = parseFrontMatter(lines.slice(position.firstLine + 1, position.lastLine)); - const content = lines.slice(position.lastLine + 1).join('\n'); - return { frontmatter, content }; - } - return { frontmatter: {}, content }; -} - -function getFrontMatterPosition(lines) { - let firstLine; - let lastLine; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.trim() === '---') { - if (firstLine === undefined) { - firstLine = i; - } else { - lastLine = i; - break; - } - } - } - return lastLine !== undefined ? { firstLine, lastLine } : undefined; -} - -function parseFrontMatter(lines) { - const data = {}; - for (const element of lines) { - const tokens = element.split(':').map(x => x.trim()); - if (tokens.length === 2) { - data[tokens[0]] = tokens[1]; - } - } - return data; -} - -/** - * @param {string} content - * @returns {string} - */ -function filterContent(content) { - const regexBase = '<!-- \\/?(sonarqube|sonarcloud|static) -->'; - const { getInstance } = require('./system'); - const contentWithInstance = content.replace(/{instance}/gi, getInstance()); - const contentWithoutStatic = cutConditionalContent(contentWithInstance, 'static'); - const filteredContent = cutConditionalContent(contentWithoutStatic, 'sonarcloud'); - return filteredContent - .replace(new RegExp(`^${regexBase}(\n|\r|\r\n|$)`, 'gm'), '') // First, remove single-line ones, including ending carriage-returns. - .replace(new RegExp(`${regexBase}`, 'g'), ''); // Now remove all remaining ones. -} - -/** - * @param {string} content - * @param {string} tag - * @returns {string} - */ -function cutConditionalContent(content, tag) { - const beginning = `<!-- ${tag} -->`; - const ending = `<!-- /${tag} -->`; - - let newContent = content; - let start = newContent.indexOf(beginning); - let end = newContent.indexOf(ending); - while (start !== -1 && end !== -1) { - if (start < end) { - newContent = newContent.substring(0, start) + newContent.substring(end + ending.length); - } else { - // When conditional tags are incorrectly used we log an error, strip them out and pass to the next pair of tags - // eslint-disable-next-line no-console - console.error( - new Error( - `Documentation - incorrect usage of conditional formatting tags here: "${newContent.substring( - end, - start + beginning.length - )}"` - ) - ); - newContent = - newContent.substring(0, end) + - newContent.substring(end + ending.length, start) + - newContent.substring(start + beginning.length); - } - start = newContent.indexOf(beginning); - end = newContent.indexOf(ending); - } - - return newContent; -} diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts index 0f89329fe9f..d089cfcd86a 100644 --- a/server/sonar-web/src/main/js/helpers/testMocks.ts +++ b/server/sonar-web/src/main/js/helpers/testMocks.ts @@ -19,7 +19,6 @@ */ import { To } from 'react-router-dom'; import { RuleDescriptionSections } from '../apps/coding-rules/rule'; -import { DocumentationEntry } from '../apps/documentation/utils'; import { Exporter, Profile } from '../apps/quality-profiles/types'; import { Location, Router } from '../components/hoc/withRouter'; import { AppState } from '../types/appstate'; @@ -652,20 +651,6 @@ ${overrides.key ? 'key: ' + overrides.key : ''} ${content}`; } -export function mockDocumentationEntry( - overrides: Partial<DocumentationEntry> = {} -): DocumentationEntry { - return { - content: 'Lorem ipsum dolor sit amet fredum', - relativeName: 'Lorem', - navTitle: undefined, - text: 'Lorem ipsum dolor sit amet fredum', - title: 'Lorem', - url: '/lorem/ipsum', - ...overrides - }; -} - export function mockLanguage(overrides: Partial<Language> = {}): Language { return { key: 'css', diff --git a/server/sonar-web/tsconfig.json b/server/sonar-web/tsconfig.json index 1537162d60d..99489bfd95e 100644 --- a/server/sonar-web/tsconfig.json +++ b/server/sonar-web/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "Docs/*": ["../sonar-docs/src/*"], "*": ["./src/main/js/@types/*"] } }, diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index cd5e871033a..f0c738bd016 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -1401,15 +1401,6 @@ __metadata: languageName: node linkType: hard -"@mapbox/hast-util-table-cell-style@npm:^0.1.3": - version: 0.1.3 - resolution: "@mapbox/hast-util-table-cell-style@npm:0.1.3" - dependencies: - unist-util-visit: ^1.3.0 - checksum: 4e563a274ae54bfb15f7d12fb711bbfc9579ff2704149cc8dddbc36b3d007a1cdefa993462c767b6b3da5d392b848680e9cc466b282ed9df8f16b23df685062c - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.4": version: 2.1.4 resolution: "@nodelib/fs.scandir@npm:2.1.4" @@ -2077,15 +2068,6 @@ __metadata: languageName: node linkType: hard -"@types/mdast@npm:^3.0.3": - version: 3.0.3 - resolution: "@types/mdast@npm:3.0.3" - dependencies: - "@types/unist": "*" - checksum: 5318624af815ac531e49de06da1d9458f1570f87274dced00353a240b2d2c4260f1fdd40c5e65784e4a4f49b0c5eb43f77faee60def723b501880ab3747b9916 - languageName: node - linkType: hard - "@types/minimatch@npm:*": version: 3.0.3 resolution: "@types/minimatch@npm:3.0.3" @@ -2227,13 +2209,6 @@ __metadata: languageName: node linkType: hard -"@types/unist@npm:*, @types/unist@npm:^2.0.0, @types/unist@npm:^2.0.2, @types/unist@npm:^2.0.3": - version: 2.0.3 - resolution: "@types/unist@npm:2.0.3" - checksum: 4427306b094561da28164e7e5250c4e6b382cb8eac40bf7e6bb0ff1e6e00c13e47aaf32e4a08fc8ba54602d07f79a39fb9ba304cc9dc886b1e3caf824649edbd - languageName: node - linkType: hard - "@types/valid-url@npm:1.0.3": version: 1.0.3 resolution: "@types/valid-url@npm:1.0.3" @@ -2513,7 +2488,6 @@ __metadata: jsdom: 19.0.0 lodash: 4.17.21 lunr: 2.3.9 - mdast-util-toc: 5.0.2 path-browserify: 1.0.1 postcss-calc: 7.0.2 postcss-custom-properties: 9.1.1 @@ -2530,16 +2504,8 @@ __metadata: react-select-event: 5.5.1 react-virtualized: 9.22.3 regenerator-runtime: 0.13.9 - rehype-raw: 4.0.2 - rehype-react: 5.0.0 - rehype-slug: 3.0.0 - remark: 11.0.2 - remark-custom-blocks: 2.5.1 - remark-react: 7.0.1 - remark-rehype: 6.0.0 testing-library-selector: 0.2.1 typescript: 4.8.4 - unist-util-visit: 2.0.2 valid-url: 1.0.9 whatwg-fetch: 3.6.2 languageName: unknown @@ -3083,13 +3049,6 @@ __metadata: languageName: node linkType: hard -"bail@npm:^1.0.0": - version: 1.0.5 - resolution: "bail@npm:1.0.5" - checksum: 6c334940d7eaa4e656a12fb12407b6555649b6deb6df04270fa806e0da82684ebe4a4e47815b271c794b40f8d6fa286e0c248b14ddbabb324a917fab09b7301a - languageName: node - linkType: hard - "balanced-match@npm:^1.0.0": version: 1.0.0 resolution: "balanced-match@npm:1.0.0" @@ -3282,13 +3241,6 @@ __metadata: languageName: node linkType: hard -"ccount@npm:^1.0.0, ccount@npm:^1.0.3": - version: 1.0.5 - resolution: "ccount@npm:1.0.5" - checksum: 231f463a6de16367587740ae8a8a9dd9bbbd4048fae0d93b8b181e6ce6c936b4d376d7629e2b7194434e1102c8ac7809de9c612c00cfb8f0f4575bf16ccd5ae8 - languageName: node - linkType: hard - "chalk@npm:4.1.2, chalk@npm:^4.1.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -3337,34 +3289,6 @@ __metadata: languageName: node linkType: hard -"character-entities-html4@npm:^1.0.0": - version: 1.1.4 - resolution: "character-entities-html4@npm:1.1.4" - checksum: 22536aba07a378a2326420423ceadd65c0121032c527f80e84dfc648381992ed5aa666d7c2b267cd269864b3682d5b0315fc2f03a9e7c017d1a96d24ec292d5f - languageName: node - linkType: hard - -"character-entities-legacy@npm:^1.0.0": - version: 1.1.4 - resolution: "character-entities-legacy@npm:1.1.4" - checksum: fe03a82c154414da3a0c8ab3188e4237ec68006cbcd681cf23c7cfb9502a0e76cd30ab69a2e50857ca10d984d57de3b307680fff5328ccd427f400e559c3a811 - languageName: node - linkType: hard - -"character-entities@npm:^1.0.0": - version: 1.2.4 - resolution: "character-entities@npm:1.2.4" - checksum: e1545716571ead57beac008433c1ff69517cd8ca5b336889321c5b8ff4a99c29b65589a701e9c086cda8a5e346a67295e2684f6c7ea96819fe85cbf49bf8686d - languageName: node - linkType: hard - -"character-reference-invalid@npm:^1.0.0": - version: 1.1.4 - resolution: "character-reference-invalid@npm:1.1.4" - checksum: 20274574c70e05e2f81135f3b93285536bc8ff70f37f0809b0d17791a832838f1e49938382899ed4cb444e5bbd4314ca1415231344ba29f4222ce2ccf24fea0b - languageName: node - linkType: hard - "cheerio@npm:^1.0.0-rc.3": version: 1.0.0-rc.3 resolution: "cheerio@npm:1.0.0-rc.3" @@ -3483,20 +3407,6 @@ __metadata: languageName: node linkType: hard -"collapse-white-space@npm:^1.0.0": - version: 1.0.5 - resolution: "collapse-white-space@npm:1.0.5" - checksum: 80b98d7d49f581280a9afb8da0df30793f25311ffe7b2e1212b7cb256ade1d0968cca0cab10ad25b6a558bcfd1d944d8f9b1fdd1f3d9aa51a37b8e0c0bb13d05 - languageName: node - linkType: hard - -"collapse-white-space@npm:^1.0.2": - version: 1.0.6 - resolution: "collapse-white-space@npm:1.0.6" - checksum: 9673fb797952c5c888341435596c69388b22cd5560c8cd3f40edb72734a9c820f56a7c9525166bcb7068b5d5805372e6fd0c4b9f2869782ad070cb5d3faf26e7 - languageName: node - linkType: hard - "collect-v8-coverage@npm:^1.0.0": version: 1.0.0 resolution: "collect-v8-coverage@npm:1.0.0" @@ -3552,13 +3462,6 @@ __metadata: languageName: node linkType: hard -"comma-separated-tokens@npm:^1.0.0": - version: 1.0.7 - resolution: "comma-separated-tokens@npm:1.0.7" - checksum: 8cae1b00c560d409a5723157f1871d121e6542170546000284c8ebae4e8080fb001899e814c1148e54bca19a47d0dfd14c3d59d0f7348f9c29d393e5f1fa81b5 - languageName: node - linkType: hard - "commander@npm:^2.19.0": version: 2.20.0 resolution: "commander@npm:2.20.0" @@ -4117,15 +4020,6 @@ __metadata: languageName: node linkType: hard -"detab@npm:^2.0.0": - version: 2.0.2 - resolution: "detab@npm:2.0.2" - dependencies: - repeat-string: ^1.5.4 - checksum: cc3b62365f9c09fa8c455f254de68046bcd6a32aa553f68528b1f8375cff4e3ff7075e98351345dafa9b83a88277354fae38332a494c27eed19f63116fbcdfa6 - languageName: node - linkType: hard - "detect-newline@npm:^3.0.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -4304,13 +4198,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:>=6.0.0 <=6.1.1": - version: 6.1.1 - resolution: "emoji-regex@npm:6.1.1" - checksum: 6c54300a743d0b7af6e52292508d4865945ac966572473b65fdf31b54d5e4d91a1a1d769ea89f541b4023aa0c8dd2a51697fd9d186a698faf2ff380d18e5a016 - languageName: node - linkType: hard - "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -5302,13 +5189,6 @@ __metadata: languageName: node linkType: hard -"extend@npm:^3.0.0, extend@npm:^3.0.2": - version: 3.0.2 - resolution: "extend@npm:3.0.2" - checksum: a50a8309ca65ea5d426382ff09f33586527882cf532931cb08ca786ea3146c0553310bda688710ff61d7668eba9f96b923fe1420cdf56a2c3eaf30fcab87b515 - languageName: node - linkType: hard - "fast-deep-equal@npm:^3.1.1": version: 3.1.1 resolution: "fast-deep-equal@npm:3.1.1" @@ -5709,15 +5589,6 @@ __metadata: languageName: node linkType: hard -"github-slugger@npm:^1.1.1, github-slugger@npm:^1.2.1": - version: 1.3.0 - resolution: "github-slugger@npm:1.3.0" - dependencies: - emoji-regex: ">=6.0.0 <=6.1.1" - checksum: fe96b363df9363c78bd2f644697e2ce2a05b1b10391ac0ff918a433992b03d0dcb0b6903b1f44bee972616eb5d8799837f357cbe278fb35c5fef40b69316a8d8 - languageName: node - linkType: hard - "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -5968,125 +5839,6 @@ __metadata: languageName: node linkType: hard -"hast-to-hyperscript@npm:^7.0.0": - version: 7.0.4 - resolution: "hast-to-hyperscript@npm:7.0.4" - dependencies: - comma-separated-tokens: ^1.0.0 - property-information: ^5.3.0 - space-separated-tokens: ^1.0.0 - style-to-object: ^0.2.1 - unist-util-is: ^3.0.0 - web-namespaces: ^1.1.2 - checksum: b7ce1549dfed58563e849b76b3af8e5cda3141793a5f31c0207cc7fe3234f6b1e63baf7528f1bd53b7826422dd403f212aabb2e54be901fdfb8070b36f750c92 - languageName: node - linkType: hard - -"hast-to-hyperscript@npm:^8.0.0": - version: 8.0.0 - resolution: "hast-to-hyperscript@npm:8.0.0" - dependencies: - comma-separated-tokens: ^1.0.0 - property-information: ^5.0.0 - space-separated-tokens: ^1.0.0 - style-to-object: ^0.3.0 - unist-util-is: ^4.0.0 - web-namespaces: ^1.0.0 - checksum: cf6d4cb2ebe7d9e20aca761fd2c281798043601b3a27b08b4d3db396f61c2c847c1f67a02bc819fa320b51c03e40701b0bfa266b1d93aa12ba6aa5e30029e174 - languageName: node - linkType: hard - -"hast-util-from-parse5@npm:^5.0.0": - version: 5.0.3 - resolution: "hast-util-from-parse5@npm:5.0.3" - dependencies: - ccount: ^1.0.3 - hastscript: ^5.0.0 - property-information: ^5.0.0 - web-namespaces: ^1.1.2 - xtend: ^4.0.1 - checksum: 31ecd040dd03bda38b8efbcb93ed95b19619bc8548da19973b6cdbb36302bc54c84662be345e6a4f3a53cf8b33956b502916e349871dc095802ca39cfe55040a - languageName: node - linkType: hard - -"hast-util-has-property@npm:^1.0.0": - version: 1.0.4 - resolution: "hast-util-has-property@npm:1.0.4" - checksum: 23025cee6692cf9aaf70a369248901deff0886c9bd2e2f0e81735c5f67ff500d1cfd991d3c236fc1d43e02b29d2db4075ee9fd2fe0aea1a7da261f19195046e8 - languageName: node - linkType: hard - -"hast-util-is-element@npm:^1.0.0": - version: 1.0.4 - resolution: "hast-util-is-element@npm:1.0.4" - checksum: ca9dfce2a7dccff5e4d9d0b79c4fe4a7f98d3f07d819026718398809b171ee5f93587857005d40a23fc230588683a32f12eec1af8eb1e5f6093ac0c914fc4276 - languageName: node - linkType: hard - -"hast-util-parse-selector@npm:^2.0.0": - version: 2.2.4 - resolution: "hast-util-parse-selector@npm:2.2.4" - checksum: 06e8b534626517929856877df116d95b46d384cc159595270c1e5b3af7404f20843065a1c675d60944445f7356c5c876ed10d5e2d66654b62fe06ecc8b423d45 - languageName: node - linkType: hard - -"hast-util-raw@npm:^5.0.0": - version: 5.0.2 - resolution: "hast-util-raw@npm:5.0.2" - dependencies: - hast-util-from-parse5: ^5.0.0 - hast-util-to-parse5: ^5.0.0 - html-void-elements: ^1.0.0 - parse5: ^5.0.0 - unist-util-position: ^3.0.0 - web-namespaces: ^1.0.0 - xtend: ^4.0.0 - zwitch: ^1.0.0 - checksum: 7d36fd990624315ce92f82ebda8b61c1e4b73e756205adfc8bcff9917990b678c90bc93b68ed414f9ee020c5f929f8967742c23c6bf46803f0d0f398af52590f - languageName: node - linkType: hard - -"hast-util-sanitize@npm:^2.0.0": - version: 2.0.2 - resolution: "hast-util-sanitize@npm:2.0.2" - dependencies: - xtend: ^4.0.0 - checksum: 2fc7e03deb1724bca68d6764de614376cc40dda296a814f5d8a665865860907e62819f3f66a938f67986993c924aed630223f56d799b1ed6e5cda934daf7abd4 - languageName: node - linkType: hard - -"hast-util-to-parse5@npm:^5.0.0": - version: 5.1.2 - resolution: "hast-util-to-parse5@npm:5.1.2" - dependencies: - hast-to-hyperscript: ^7.0.0 - property-information: ^5.0.0 - web-namespaces: ^1.0.0 - xtend: ^4.0.0 - zwitch: ^1.0.0 - checksum: a37b58e48e741efef270bb90347799f0837b6e7e917c991e1874b804128d4d67e76461f741ae9b0d9a1bab829438129a3f77d55753404449b722595fa6677f4c - languageName: node - linkType: hard - -"hast-util-to-string@npm:^1.0.0": - version: 1.0.3 - resolution: "hast-util-to-string@npm:1.0.3" - checksum: 6e8621e716294040537d943db1aa93105dc99986cc9ead37a6d337b07a24ea7f4d1ab9269625edfa99ecfa0475607848ddbbef3b284bcf313968ba676cccb9d4 - languageName: node - linkType: hard - -"hastscript@npm:^5.0.0": - version: 5.1.2 - resolution: "hastscript@npm:5.1.2" - dependencies: - comma-separated-tokens: ^1.0.0 - hast-util-parse-selector: ^2.0.0 - property-information: ^5.0.0 - space-separated-tokens: ^1.0.0 - checksum: 662321af446f09c76d67af31d05823f382ce1e6c007828dc77f899f310cea682c00216b67c317a4ebe7f0c05e50552c4810d214e6ed4e95388f7b7d7fc93158f - languageName: node - linkType: hard - "history@npm:^5.2.0": version: 5.3.0 resolution: "history@npm:5.3.0" @@ -6148,13 +5900,6 @@ __metadata: languageName: node linkType: hard -"html-void-elements@npm:^1.0.0": - version: 1.0.5 - resolution: "html-void-elements@npm:1.0.5" - checksum: 1a56f4f6cfbeb994c21701ff72b4b7f556fe784a70e5e554d1566ff775af83b91ea93f10664f039a67802d9f7b40d4a7f1ed20312bab47bd88d89bd792ea84ca - languageName: node - linkType: hard - "htmlparser2@npm:^3.9.1": version: 3.10.1 resolution: "htmlparser2@npm:3.10.1" @@ -6355,7 +6100,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.0, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -6369,13 +6114,6 @@ __metadata: languageName: node linkType: hard -"inline-style-parser@npm:0.1.1": - version: 0.1.1 - resolution: "inline-style-parser@npm:0.1.1" - checksum: 5d545056a3e1f2bf864c928a886a0e1656a3517127d36917b973de581bd54adc91b4bf1febcb0da054f204b4934763f1a4e09308b4d55002327cf1d48ac5d966 - languageName: node - linkType: hard - "internal-slot@npm:^1.0.3": version: 1.0.3 resolution: "internal-slot@npm:1.0.3" @@ -6436,30 +6174,6 @@ __metadata: languageName: node linkType: hard -"is-alphabetical@npm:^1.0.0": - version: 1.0.4 - resolution: "is-alphabetical@npm:1.0.4" - checksum: 6508cce44fd348f06705d377b260974f4ce68c74000e7da4045f0d919e568226dc3ce9685c5a2af272195384df6930f748ce9213fc9f399b5d31b362c66312cb - languageName: node - linkType: hard - -"is-alphanumeric@npm:^1.0.0": - version: 1.0.0 - resolution: "is-alphanumeric@npm:1.0.0" - checksum: 2f4f4f227fe4cae977529f628021655edc172e1e5debfb3c30efd547f32e8d390c9bb7a71f3e9fea4187fe6598980072323d5a1b1abd3368379e33ba6504558c - languageName: node - linkType: hard - -"is-alphanumerical@npm:^1.0.0": - version: 1.0.4 - resolution: "is-alphanumerical@npm:1.0.4" - dependencies: - is-alphabetical: ^1.0.0 - is-decimal: ^1.0.0 - checksum: e2e491acc16fcf5b363f7c726f666a9538dba0a043665740feb45bba1652457a73441e7c5179c6768a638ed396db3437e9905f403644ec7c468fb41f4813d03f - languageName: node - linkType: hard - "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -6509,13 +6223,6 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^2.0.0": - version: 2.0.4 - resolution: "is-buffer@npm:2.0.4" - checksum: b1616ff40c1644e219d6038819044608e31edcc60eb287e5f214391222dea889a68c659f0654865ce34b6c4dcfa2c8cae0174343a0f6ae3f2150f7856326cb80 - languageName: node - linkType: hard - "is-callable@npm:^1.1.4": version: 1.1.4 resolution: "is-callable@npm:1.1.4" @@ -6585,13 +6292,6 @@ __metadata: languageName: node linkType: hard -"is-decimal@npm:^1.0.0, is-decimal@npm:^1.0.2": - version: 1.0.4 - resolution: "is-decimal@npm:1.0.4" - checksum: ed483a387517856dc395c68403a10201fddcc1b63dc56513fbe2fe86ab38766120090ecdbfed89223d84ca8b1cd28b0641b93cb6597b6e8f4c097a7c24e3fb96 - languageName: node - linkType: hard - "is-extendable@npm:^0.1.0": version: 0.1.1 resolution: "is-extendable@npm:0.1.1" @@ -6654,13 +6354,6 @@ __metadata: languageName: node linkType: hard -"is-hexadecimal@npm:^1.0.0": - version: 1.0.4 - resolution: "is-hexadecimal@npm:1.0.4" - checksum: a452e047587b6069332d83130f54d30da4faf2f2ebaa2ce6d073c27b5703d030d58ed9e0b729c8e4e5b52c6f1dab26781bb77b7bc6c7805f14f320e328ff8cd5 - languageName: node - linkType: hard - "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -6696,13 +6389,6 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^2.0.0": - version: 2.1.0 - resolution: "is-plain-obj@npm:2.1.0" - checksum: cec9100678b0a9fe0248a81743041ed990c2d4c99f893d935545cfbc42876cbe86d207f3b895700c690ad2fa520e568c44afc1605044b535a7820c1d40e38daa - languageName: node - linkType: hard - "is-potential-custom-element-name@npm:^1.0.1": version: 1.0.1 resolution: "is-potential-custom-element-name@npm:1.0.1" @@ -6834,13 +6520,6 @@ __metadata: languageName: node linkType: hard -"is-whitespace-character@npm:^1.0.0": - version: 1.0.4 - resolution: "is-whitespace-character@npm:1.0.4" - checksum: adab8ad9847ccfcb6f1b7000b8f622881b5ba2a09ce8be2794a6d2b10c3af325b469fc562c9fb889f468eed27be06e227ac609d0aa1e3a59b4dbcc88e2b0418e - languageName: node - linkType: hard - "is-windows@npm:^0.2.0": version: 0.2.0 resolution: "is-windows@npm:0.2.0" @@ -6848,13 +6527,6 @@ __metadata: languageName: node linkType: hard -"is-word-character@npm:^1.0.0": - version: 1.0.4 - resolution: "is-word-character@npm:1.0.4" - checksum: 1821d6c6abe5bc0b3abe3fdc565d66d7c8a74ea4e93bc77b4a47d26e2e2a306d6ab7d92b353b0d2b182869e3ecaa8f4a346c62d0e31d38ebc0ceaf7cae182c3f - languageName: node - linkType: hard - "isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -7868,13 +7540,6 @@ __metadata: languageName: node linkType: hard -"longest-streak@npm:^2.0.1": - version: 2.0.4 - resolution: "longest-streak@npm:2.0.4" - checksum: 28b8234a14963002c5c71035dee13a0a11e9e9d18ffa320fdc8796ed7437399204495702ed69cd2a7087b0af041a2a8b562829b7c1e2042e73a3374d1ecf6580 - languageName: node - linkType: hard - "loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -7962,84 +7627,6 @@ __metadata: languageName: node linkType: hard -"markdown-escapes@npm:^1.0.0": - version: 1.0.4 - resolution: "markdown-escapes@npm:1.0.4" - checksum: 6833a93d72d3f70a500658872312c6fa8015c20cc835a85ae6901fa232683fbc6ed7118ebe920fea7c80039a560f339c026597d96eee0e9de602a36921804997 - languageName: node - linkType: hard - -"markdown-table@npm:^1.1.0": - version: 1.1.3 - resolution: "markdown-table@npm:1.1.3" - checksum: 292e8c956ae833c2ccb0a55cd8d87980cd657ab11cd9ff63c3fcc4d3a518d3b3882ba07410b8f477ba9e30b3f70658677e4e8acf61816dd6cfdd1f6293130664 - languageName: node - linkType: hard - -"mdast-util-compact@npm:^1.0.0": - version: 1.0.4 - resolution: "mdast-util-compact@npm:1.0.4" - dependencies: - unist-util-visit: ^1.1.0 - checksum: 5c2eec96bd9f231d54733bbfb4fbd83ec8e1a6ddd9084a79f502614b6868a27be8ac75946ba5871c3d291673904da1fa63a5738de9c98ac1dbfd011ebb57d853 - languageName: node - linkType: hard - -"mdast-util-definitions@npm:^2.0.0": - version: 2.0.1 - resolution: "mdast-util-definitions@npm:2.0.1" - dependencies: - unist-util-visit: ^2.0.0 - checksum: dd00a97dd8c65d04b5ab501773ab48f81f093f1465b3db3596189f67c4d260f9e02757e55049ac886c80e10e4d51a2641f8a77d5c6e29aee1ec586dbbb82db57 - languageName: node - linkType: hard - -"mdast-util-to-hast@npm:^8.0.0": - version: 8.1.0 - resolution: "mdast-util-to-hast@npm:8.1.0" - dependencies: - collapse-white-space: ^1.0.0 - detab: ^2.0.0 - mdast-util-definitions: ^2.0.0 - mdurl: ^1.0.0 - trim-lines: ^1.0.0 - unist-builder: ^2.0.0 - unist-util-generated: ^1.0.0 - unist-util-position: ^3.0.0 - unist-util-visit: ^2.0.0 - checksum: 6cea5ac6ca5fa09dba237b106a7521af9cd8b5f60955932827d44072a978153dac5a85a6153f2fa32cee307a08519720f64dac3b30af314414e11759c7edb61a - languageName: node - linkType: hard - -"mdast-util-to-string@npm:^1.0.5": - version: 1.1.0 - resolution: "mdast-util-to-string@npm:1.1.0" - checksum: eec1eb283f3341376c8398b67ce512a11ab3e3191e3dbd5644d32a26784eac8d5f6d0b0fb81193af00d75a2c545cde765c8b03e966bd890076efb5d357fb4fe2 - languageName: node - linkType: hard - -"mdast-util-toc@npm:5.0.2": - version: 5.0.2 - resolution: "mdast-util-toc@npm:5.0.2" - dependencies: - "@types/mdast": ^3.0.3 - "@types/unist": ^2.0.3 - extend: ^3.0.2 - github-slugger: ^1.2.1 - mdast-util-to-string: ^1.0.5 - unist-util-is: ^4.0.0 - unist-util-visit: ^2.0.0 - checksum: 0ae885adc00a00f9116f03e49c0fc9383cdd6f895e8e69d82e65e1229de197fd86c7c6a2e4f70da94dac30b3756cc0a0a1c2a59d03c2f8309700fef03c478049 - languageName: node - linkType: hard - -"mdurl@npm:^1.0.0": - version: 1.0.1 - resolution: "mdurl@npm:1.0.1" - checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b - languageName: node - linkType: hard - "memoize-one@npm:^5.0.0": version: 5.2.1 resolution: "memoize-one@npm:5.2.1" @@ -8701,20 +8288,6 @@ __metadata: languageName: node linkType: hard -"parse-entities@npm:^1.0.2, parse-entities@npm:^1.1.0": - version: 1.2.2 - resolution: "parse-entities@npm:1.2.2" - dependencies: - character-entities: ^1.0.0 - character-entities-legacy: ^1.0.0 - character-reference-invalid: ^1.0.0 - is-alphanumerical: ^1.0.0 - is-decimal: ^1.0.0 - is-hexadecimal: ^1.0.0 - checksum: abf070c67912647a016efd5547607ecddc7e1963e59fc20c76797419b6699a3a9a522c067efa509feefedd37afd6c2a44200b3e5546a023a973c90e6e650b68a - languageName: node - linkType: hard - "parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -8757,13 +8330,6 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^5.0.0": - version: 5.1.1 - resolution: "parse5@npm:5.1.1" - checksum: 613a714af4c1101d1cb9f7cece2558e35b9ae8a0c03518223a4a1e35494624d9a9ad5fad4c13eab66a0e0adccd9aa3d522fc8f5f9cc19789e0579f3fa0bdfc65 - languageName: node - linkType: hard - "path-browserify@npm:1.0.1": version: 1.0.1 resolution: "path-browserify@npm:1.0.1" @@ -9154,15 +8720,6 @@ __metadata: languageName: node linkType: hard -"property-information@npm:^5.0.0, property-information@npm:^5.3.0": - version: 5.4.0 - resolution: "property-information@npm:5.4.0" - dependencies: - xtend: ^4.0.0 - checksum: be497e34f48f981e53ecf7ad3be1b4cd7c54be055f1e36e8e556110e68d8a1d56b07128fa3c690b2266ca77fe4c672ba30889982fee0efd82ac967915945ac31 - languageName: node - linkType: hard - "prr@npm:~1.0.1": version: 1.0.1 resolution: "prr@npm:1.0.1" @@ -9560,138 +9117,6 @@ __metadata: languageName: node linkType: hard -"rehype-raw@npm:4.0.2": - version: 4.0.2 - resolution: "rehype-raw@npm:4.0.2" - dependencies: - hast-util-raw: ^5.0.0 - checksum: 53956b5ef2eb181dbc6e8858da375590ad45cc2144c2fcdb305411d3b443f638dcadec69eecb1b2d7b20373d95bba7b9cdac4669a939970d885bbf304c84022f - languageName: node - linkType: hard - -"rehype-react@npm:5.0.0": - version: 5.0.0 - resolution: "rehype-react@npm:5.0.0" - dependencies: - "@mapbox/hast-util-table-cell-style": ^0.1.3 - hast-to-hyperscript: ^8.0.0 - checksum: 531723b397f8601e7c741233988f60bf6e61e52a1ae557089cfc3f60177c94695f9cca103ae3be32ec954b842eed770e45739bfb258c3afa7f14b20fe7431e37 - languageName: node - linkType: hard - -"rehype-slug@npm:3.0.0": - version: 3.0.0 - resolution: "rehype-slug@npm:3.0.0" - dependencies: - github-slugger: ^1.1.1 - hast-util-has-property: ^1.0.0 - hast-util-is-element: ^1.0.0 - hast-util-to-string: ^1.0.0 - unist-util-visit: ^2.0.0 - checksum: b4a63d77f306fe0dcf7ba310ec7b21ee4f0273eaf0022e8f0ab13aa94ad6bbd2055445c62d05ca82e5afbbd1e3019088223ee2d2ab24bda01f4753d2d2a47697 - languageName: node - linkType: hard - -"remark-custom-blocks@npm:2.5.1": - version: 2.5.1 - resolution: "remark-custom-blocks@npm:2.5.1" - dependencies: - space-separated-tokens: ^1.1.5 - checksum: 04523f71df625b56faf160d23623e76196848ed4b4061432f0a9e77a71a4a71bd1478b55d00fa4e6561592189532644c1df7af0c5f0312f8e8b0779c725d0eae - languageName: node - linkType: hard - -"remark-parse@npm:^7.0.0": - version: 7.0.2 - resolution: "remark-parse@npm:7.0.2" - dependencies: - collapse-white-space: ^1.0.2 - is-alphabetical: ^1.0.0 - is-decimal: ^1.0.0 - is-whitespace-character: ^1.0.0 - is-word-character: ^1.0.0 - markdown-escapes: ^1.0.0 - parse-entities: ^1.1.0 - repeat-string: ^1.5.4 - state-toggle: ^1.0.0 - trim: 0.0.1 - trim-trailing-lines: ^1.0.0 - unherit: ^1.0.4 - unist-util-remove-position: ^1.0.0 - vfile-location: ^2.0.0 - xtend: ^4.0.1 - checksum: bf512c5e1c7f08961a032b8422a6f916f1a60f08481b8fbaf62590dbb184d4c583db1c3b7139cf803e55078de20fe6a55a33fd8a62fd06d4fac9133e8c3aa463 - languageName: node - linkType: hard - -"remark-react@npm:7.0.1": - version: 7.0.1 - resolution: "remark-react@npm:7.0.1" - dependencies: - "@mapbox/hast-util-table-cell-style": ^0.1.3 - hast-to-hyperscript: ^8.0.0 - hast-util-sanitize: ^2.0.0 - mdast-util-to-hast: ^8.0.0 - checksum: b7c9dec2c070b7aad9b8fb7eb169a55f8cc2855dd2e93210e2b3aff03e80a35e673c6db71c1c748af46757d9c172f272f97757f755fd3b2e1c0daf6f8c987008 - languageName: node - linkType: hard - -"remark-rehype@npm:6.0.0": - version: 6.0.0 - resolution: "remark-rehype@npm:6.0.0" - dependencies: - mdast-util-to-hast: ^8.0.0 - checksum: ebeab70745cbc8ceba03a6b26adcba556d60620ca349e2003520d824bcfe2d9f399535f8e6db18b3b3cd32b329c9d34788cf01b612353e33d725549bdf850a2c - languageName: node - linkType: hard - -"remark-stringify@npm:^7.0.0": - version: 7.0.4 - resolution: "remark-stringify@npm:7.0.4" - dependencies: - ccount: ^1.0.0 - is-alphanumeric: ^1.0.0 - is-decimal: ^1.0.0 - is-whitespace-character: ^1.0.0 - longest-streak: ^2.0.1 - markdown-escapes: ^1.0.0 - markdown-table: ^1.1.0 - mdast-util-compact: ^1.0.0 - parse-entities: ^1.0.2 - repeat-string: ^1.5.4 - state-toggle: ^1.0.0 - stringify-entities: ^2.0.0 - unherit: ^1.0.4 - xtend: ^4.0.1 - checksum: bfb99390da47c4a901ae8cf4f3f012b37776c9b1c529f12e357e4ba4626700c5e86ca7dd4a871119f628c32cca9f9f80900f38f578f3cfe6350e1c495e940cf7 - languageName: node - linkType: hard - -"remark@npm:11.0.2": - version: 11.0.2 - resolution: "remark@npm:11.0.2" - dependencies: - remark-parse: ^7.0.0 - remark-stringify: ^7.0.0 - unified: ^8.2.0 - checksum: 1a9ef3bbdef61c4c8dc15a9d18665bccd8c8dd55b42f6d541dc3f71d5a7b12cb768ee8538904d58e40d1cfcda0b7c350e3f0f38a18e0f5105d9c7bffd63ccdea - languageName: node - linkType: hard - -"repeat-string@npm:^1.5.4": - version: 1.6.1 - resolution: "repeat-string@npm:1.6.1" - checksum: 1b809fc6db97decdc68f5b12c4d1a671c8e3f65ec4a40c238bc5200e44e85bcc52a54f78268ab9c29fcf5fe4f1343e805420056d1f30fa9a9ee4c2d93e3cc6c0 - languageName: node - linkType: hard - -"replace-ext@npm:1.0.0": - version: 1.0.0 - resolution: "replace-ext@npm:1.0.0" - checksum: 123e5c28046e4f0b82e1cdedb0340058d362ddbd8e17d98e5068bbacc3b3b397b4d8e3c69d603f9c4c0f6a6494852064396570c44f9426a4673dba63850fab34 - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -10270,20 +9695,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"space-separated-tokens@npm:^1.0.0": - version: 1.1.4 - resolution: "space-separated-tokens@npm:1.1.4" - checksum: 4048c44ac1bded6927f4a31bf4bc958d6b878adf3ed3af9b08e7873b789016fe946ee69ca049d8030a3829ec2fe0481b55a93b066f29ab8f529864bcc85e043b - languageName: node - linkType: hard - -"space-separated-tokens@npm:^1.1.5": - version: 1.1.5 - resolution: "space-separated-tokens@npm:1.1.5" - checksum: 8ef68f1cfa8ccad316b7f8d0df0919d0f1f6d32101e8faeee34ea3a923ce8509c1ad562f57388585ee4951e92d27afa211ed0a077d3d5995b5ba9180331be708 - languageName: node - linkType: hard - "specificity@npm:^0.4.1": version: 0.4.1 resolution: "specificity@npm:0.4.1" @@ -10318,13 +9729,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"state-toggle@npm:^1.0.0": - version: 1.0.3 - resolution: "state-toggle@npm:1.0.3" - checksum: 17398af928413e8d8b866cf0c81fd1b1348bb7d65d8983126ff6ff2317a80d6ee023484fba0c54d8169f5aa544f125434a650ae3a71eddc935cae307d4692b4f - languageName: node - linkType: hard - "string-hash@npm:^1.1.1": version: 1.1.3 resolution: "string-hash@npm:1.1.3" @@ -10501,19 +9905,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"stringify-entities@npm:^2.0.0": - version: 2.0.0 - resolution: "stringify-entities@npm:2.0.0" - dependencies: - character-entities-html4: ^1.0.0 - character-entities-legacy: ^1.0.0 - is-alphanumerical: ^1.0.0 - is-decimal: ^1.0.2 - is-hexadecimal: ^1.0.0 - checksum: 2e3c2ed25c48e9f8383ecf6cc7fa1d74819acf9671e08f9f78109dc12c0e34dab24b003256db9b690a32ba07b27f2dcd4bd2d03bf235b750db909713738aa808 - languageName: node - linkType: hard - "strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": version: 3.0.1 resolution: "strip-ansi@npm:3.0.1" @@ -10587,24 +9978,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"style-to-object@npm:^0.2.1": - version: 0.2.3 - resolution: "style-to-object@npm:0.2.3" - dependencies: - inline-style-parser: 0.1.1 - checksum: dc137512b3ff403c77ec10b8ebb00294ddc6716be195d907ffe80e7625a43bef4144bfe5f745224022ffd3e43b45de812e5e3db1fa25ba402b0877cdb8dc4d2f - languageName: node - linkType: hard - -"style-to-object@npm:^0.3.0": - version: 0.3.0 - resolution: "style-to-object@npm:0.3.0" - dependencies: - inline-style-parser: 0.1.1 - checksum: 4d7084015207f2a606dfc10c29cb5ba569f2fe8005551df7396110dd694d6ff650f2debafa95bd5d147dfb4ca50f57868e2a7f91bf5d11ef734fe7ccbd7abf59 - languageName: node - linkType: hard - "stylis@npm:4.0.13": version: 4.0.13 resolution: "stylis@npm:4.0.13" @@ -10848,13 +10221,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"trim-lines@npm:^1.0.0": - version: 1.1.2 - resolution: "trim-lines@npm:1.1.2" - checksum: 2132d4f852b9e1ecebb4b276001424d5348802139fc5c3eb66f91785dd20d2f726a56681ca0baa5954c2eee655db237289294c0903bc039499566e42899dc0e6 - languageName: node - linkType: hard - "trim-right@npm:^1.0.1": version: 1.0.1 resolution: "trim-right@npm:1.0.1" @@ -10862,27 +10228,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"trim-trailing-lines@npm:^1.0.0": - version: 1.1.3 - resolution: "trim-trailing-lines@npm:1.1.3" - checksum: 7eb4ac54079c330211453114be3d62f6a2f480e422b428982ccb7a48d278c5f826366f9f453e001eef42d50c06c0aafa2ec88280c971ecd6bc00c330a8214045 - languageName: node - linkType: hard - -"trim@npm:0.0.1": - version: 0.0.1 - resolution: "trim@npm:0.0.1" - checksum: 2b4646dff99a222e8e1526edd4e3a43bbd925af0b8e837c340455d250157e7deefaa4da49bb891ab841e5c27b1afc5e9e32d4b57afb875d2dfcabf4e319b8f7f - languageName: node - linkType: hard - -"trough@npm:^1.0.0": - version: 1.0.5 - resolution: "trough@npm:1.0.5" - checksum: d6c8564903ed00e5258bab92134b020724dbbe83148dc72e4bf6306c03ed8843efa1bcc773fa62410dd89161ecb067432dd5916501793508a9506cacbc408e25 - languageName: node - linkType: hard - "tsconfig-paths@npm:^3.14.1": version: 3.14.1 resolution: "tsconfig-paths@npm:3.14.1" @@ -11012,29 +10357,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"unherit@npm:^1.0.4": - version: 1.1.3 - resolution: "unherit@npm:1.1.3" - dependencies: - inherits: ^2.0.0 - xtend: ^4.0.0 - checksum: fd7922f84fc0bfb7c4df6d1f5a50b5b94a0218e3cda98a54dbbd209226ddd4072d742d3df44d0e295ab08d5ccfd304a1e193dfe31a86d2a91b7cb9fdac093194 - languageName: node - linkType: hard - -"unified@npm:^8.2.0": - version: 8.4.2 - resolution: "unified@npm:8.4.2" - dependencies: - bail: ^1.0.0 - extend: ^3.0.0 - is-plain-obj: ^2.0.0 - trough: ^1.0.0 - vfile: ^4.0.0 - checksum: c2af7662d6375b14721df305786b15ba3228cd39c37da748bff00ed08ababd12ce52568f475347f270b1dea72fb0b9608563574a55c29e4f73f8be7ce0a01b4a - languageName: node - linkType: hard - "uniq@npm:^1.0.1": version: 1.0.1 resolution: "uniq@npm:1.0.1" @@ -11060,98 +10382,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"unist-builder@npm:^2.0.0": - version: 2.0.3 - resolution: "unist-builder@npm:2.0.3" - checksum: e946fdf77dbfc320feaece137ce4959ae2da6614abd1623bd39512dc741a9d5f313eb2ba79f8887d941365dccddec7fef4e953827475e392bf49b45336f597f6 - languageName: node - linkType: hard - -"unist-util-generated@npm:^1.0.0": - version: 1.1.5 - resolution: "unist-util-generated@npm:1.1.5" - checksum: 7c2a2efb5467f756679761cbc02c36c8d9f8ca7fcb27e8add5e583b9bd9d1beda9c3805b6cb487cbfea8d1d45372a34f6e8d470e35526877fc42fc0c6327f6ab - languageName: node - linkType: hard - -"unist-util-is@npm:^3.0.0": - version: 3.0.0 - resolution: "unist-util-is@npm:3.0.0" - checksum: d24a5dd80c670f763b2ae608651cf062317456aa81be51f66f45cbd7d440a2ab18356e4f48aeac6b5e3d391c69d3c3452ade5fe5aa9574bec4a2de0b10122ed5 - languageName: node - linkType: hard - -"unist-util-is@npm:^4.0.0": - version: 4.0.2 - resolution: "unist-util-is@npm:4.0.2" - checksum: 851a07cd0eeadde5ec98fe7453d554804b3cc5be94d892de6f52178c05b683d753dfd7bb566bb42afa7aca8633935e86b1c34816832d151b41a28348bd876cc1 - languageName: node - linkType: hard - -"unist-util-position@npm:^3.0.0": - version: 3.0.3 - resolution: "unist-util-position@npm:3.0.3" - checksum: b938a4dadb9960d7ab815a7f6d5d67bd2f26207b149b214067c90062750dd89e8f8882e9c23175098c613c6140e8943d131afb5ba092170223cf582094ae2da8 - languageName: node - linkType: hard - -"unist-util-remove-position@npm:^1.0.0": - version: 1.1.4 - resolution: "unist-util-remove-position@npm:1.1.4" - dependencies: - unist-util-visit: ^1.1.0 - checksum: 74be7078d135601e9d295f392ef2768efc2c0bdb8720480c36fa608df6290cb85d324e82d4bdfc2f38303c466ffbba4f0fa4f9acb25fff45d23926259bdafcf6 - languageName: node - linkType: hard - -"unist-util-stringify-position@npm:^2.0.0": - version: 2.0.3 - resolution: "unist-util-stringify-position@npm:2.0.3" - dependencies: - "@types/unist": ^2.0.2 - checksum: f755cadc959f9074fe999578a1a242761296705a7fe87f333a37c00044de74ab4b184b3812989a57d4cd12211f0b14ad397b327c3a594c7af84361b1c25a7f09 - languageName: node - linkType: hard - -"unist-util-visit-parents@npm:^2.0.0": - version: 2.1.2 - resolution: "unist-util-visit-parents@npm:2.1.2" - dependencies: - unist-util-is: ^3.0.0 - checksum: 048edbb590a8c4bc0043eec9f50d3fe76faa58f1ac663a7e6dee5e895ddd0ce8bc52f2cfe2e633849fa93671e8de021070667acb1518e3d40220768c7f70a3d3 - languageName: node - linkType: hard - -"unist-util-visit-parents@npm:^3.0.0": - version: 3.0.2 - resolution: "unist-util-visit-parents@npm:3.0.2" - dependencies: - "@types/unist": ^2.0.0 - unist-util-is: ^4.0.0 - checksum: fa00ee47427a2ece4ab6a997ac74147844842b824ce512274e5b62f95e83deff79f2b68a0497bca036d0462954b890e8a5263d4a6640595e6be696bd3c844814 - languageName: node - linkType: hard - -"unist-util-visit@npm:2.0.2, unist-util-visit@npm:^2.0.0": - version: 2.0.2 - resolution: "unist-util-visit@npm:2.0.2" - dependencies: - "@types/unist": ^2.0.0 - unist-util-is: ^4.0.0 - unist-util-visit-parents: ^3.0.0 - checksum: 818028efae123a8e1931ac74286f808c43649394a52f091f1632169b421151a4a6dba15db65561ecf3002236928e533aefe038dffdafc94920c3c70324e1afe6 - languageName: node - linkType: hard - -"unist-util-visit@npm:^1.1.0, unist-util-visit@npm:^1.3.0": - version: 1.4.1 - resolution: "unist-util-visit@npm:1.4.1" - dependencies: - unist-util-visit-parents: ^2.0.0 - checksum: e9395205b6908c8d0fe71bc44e65d89d4781d1bb2d453a33cb67ed4124bad0b89d6b1d526ebaecb82a7c48e211bdf6f24351449b8cc115327b345f4617c18728 - languageName: node - linkType: hard - "universalify@npm:^0.1.2": version: 0.1.2 resolution: "universalify@npm:0.1.2" @@ -11240,36 +10470,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"vfile-location@npm:^2.0.0": - version: 2.0.6 - resolution: "vfile-location@npm:2.0.6" - checksum: ca0da908fdcd86f3df749a328ff777cf8994240eb333da7e6ee270b4fec09058d7b64f174ce9e31a9c591bb9ed01b45c223186a31036860d9f463eca059c058e - languageName: node - linkType: hard - -"vfile-message@npm:^2.0.0": - version: 2.0.3 - resolution: "vfile-message@npm:2.0.3" - dependencies: - "@types/unist": ^2.0.0 - unist-util-stringify-position: ^2.0.0 - checksum: 3477d6798b2c1051aa5d65448f49ecc157dc9176d2b11feb480698a7c91517fe536adf7bec2b9bc1d93696cf6a843d3aca621f00047514dd009dbd079b717160 - languageName: node - linkType: hard - -"vfile@npm:^4.0.0": - version: 4.0.3 - resolution: "vfile@npm:4.0.3" - dependencies: - "@types/unist": ^2.0.0 - is-buffer: ^2.0.0 - replace-ext: 1.0.0 - unist-util-stringify-position: ^2.0.0 - vfile-message: ^2.0.0 - checksum: 979dc4056dfccfee09982669045f2dcc2012dcb9bedda3645685930a17335a6120922a6df14931e1309cc87536e792491f1159a89ac94346d439457095abeda4 - languageName: node - linkType: hard - "w3c-hr-time@npm:^1.0.2": version: 1.0.2 resolution: "w3c-hr-time@npm:1.0.2" @@ -11315,13 +10515,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"web-namespaces@npm:^1.0.0, web-namespaces@npm:^1.1.2": - version: 1.1.4 - resolution: "web-namespaces@npm:1.1.4" - checksum: 5149842ccbfbc56fe4f8758957b3f8c8616a281874a5bb84aa1b305e4436a9bad853d21c629a7b8f174902449e1489c7a6c724fccf60965077c5636bd8aed42b - languageName: node - linkType: hard - "webidl-conversions@npm:^5.0.0": version: 5.0.0 resolution: "webidl-conversions@npm:5.0.0" @@ -11549,13 +10742,6 @@ resolve@^1.3.2: languageName: node linkType: hard -"xtend@npm:^4.0.0, xtend@npm:^4.0.1": - version: 4.0.1 - resolution: "xtend@npm:4.0.1" - checksum: 6148d4f9b978f858560b21f1666d1d2b8a799289671ce3274a0b2e8b843d960ba7507842d73c2f44705a87ca9adc25ab12d627aac41ba911038f78f9eb6e6d78 - languageName: node - linkType: hard - "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" @@ -11605,10 +10791,3 @@ resolve@^1.3.2: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard - -"zwitch@npm:^1.0.0": - version: 1.0.5 - resolution: "zwitch@npm:1.0.5" - checksum: 28a1bebacab3bc60150b6b0a2ba1db2ad033f068e81f05e4892ec0ea13ae63f5d140a1d692062ac0657840c8da076f35b94433b5f1c329d7803b247de80f064a - languageName: node - linkType: hard |