'/project/background_tasks',
'/admin/background_tasks',
'/admin/groups',
+ '/admin/marketplace',
'/admin/system',
'/admin/users',
'/admin/settings/encryption',
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { FormattedMessage } from 'react-intl';
+import Link from '../../components/common/Link';
+import Modal from '../../components/controls/Modal';
+import { ResetButtonLink } from '../../components/controls/buttons';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ onClose: () => void;
+}
+
+export default function LicensePromptModal({ onClose }: Readonly<Props>) {
+ const header = translate('license.prompt.title');
+ return (
+ <Modal contentLabel={header} onRequestClose={onClose}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+ <div className="modal-body">
+ <FormattedMessage
+ defaultMessage={translate('license.prompt.description')}
+ id="license.prompt.description"
+ values={{
+ url: (
+ <Link onClick={onClose} to="/admin/extension/license/app">
+ {translate('license.prompt.link')}
+ </Link>
+ ),
+ }}
+ />
+ </div>
+ <footer className="modal-foot">
+ <ResetButtonLink onClick={onClose}>{translate('cancel')}</ResetButtonLink>
+ </footer>
+ </Modal>
+ );
+}
import { differenceInDays } from 'date-fns';
import * as React from 'react';
import { showLicense } from '../../api/editions';
-import LicensePromptModal from '../../apps/marketplace/components/LicensePromptModal';
import { parseDate, toShortISO8601String } from '../../helpers/dates';
import { hasMessage } from '../../helpers/l10n';
import { get, save } from '../../helpers/storage';
import { AppState } from '../../types/appstate';
import { EditionKey } from '../../types/editions';
import { CurrentUser, isLoggedIn } from '../../types/users';
+import LicensePromptModal from './LicensePromptModal';
import withAppStateContext from './app-state/withAppStateContext';
import withCurrentUserContext from './current-user/withCurrentUserContext';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ BasicSeparator,
+ FlagMessage,
+ LargeCenteredLayout,
+ PageContentFontWrapper,
+ Spinner,
+ SubTitle,
+} from 'design-system';
import { sortBy, uniqBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { FormattedMessage } from 'react-intl';
import { getAvailablePlugins, getInstalledPlugins } from '../../api/plugins';
import { getValue, setSimpleSettingValue } from '../../api/settings';
-import DocLink from '../../components/common/DocLink';
+import DocumentationLink from '../../components/common/DocumentationLink';
+import ListFooter from '../../components/controls/ListFooter';
import Suggestions from '../../components/embed-docs-modal/Suggestions';
import { Location, Router, withRouter } from '../../components/hoc/withRouter';
-import { Alert } from '../../components/ui/Alert';
-import Spinner from '../../components/ui/Spinner';
import { translate } from '../../helpers/l10n';
import { EditionKey } from '../../types/editions';
import { PendingPluginResult, Plugin, RiskConsent } from '../../types/plugins';
import { SettingsKey } from '../../types/settings';
import EditionBoxes from './EditionBoxes';
-import Footer from './Footer';
import Header from './Header';
import PluginsList from './PluginsList';
import Search from './Search';
riskConsent === RiskConsent.Accepted;
return (
- <main className="page page-limited" id="marketplace-page">
- <Suggestions suggestions="marketplace" />
- <Helmet title={translate('marketplace.page')} />
- <Header currentEdition={currentEdition} />
- <EditionBoxes currentEdition={currentEdition} />
- <header className="page-header">
- <h2 className="page-title">{translate('marketplace.page.plugins')}</h2>
- <div className="page-description">
- <p>{translate('marketplace.page.plugins.description')}</p>
- {currentEdition !== EditionKey.community && (
- <Alert className="spacer-top" variant="info">
- <FormattedMessage
- id="marketplace.page.plugins.description2"
- defaultMessage={translate('marketplace.page.plugins.description2')}
- values={{
- link: (
- <DocLink to="/instance-administration/marketplace/">
- {translate('marketplace.page.plugins.description2.link')}
- </DocLink>
- ),
- }}
- />
- </Alert>
- )}
+ <LargeCenteredLayout as="main" id="marketplace-page">
+ <PageContentFontWrapper className="sw-body-sm sw-py-8">
+ <Suggestions suggestions="marketplace" />
+ <Helmet title={translate('marketplace.page')} />
+ <Header currentEdition={currentEdition} />
+ <EditionBoxes currentEdition={currentEdition} />
+
+ <BasicSeparator className="sw-my-6" />
+
+ <div>
+ <SubTitle>{translate('marketplace.page.plugins')}</SubTitle>
+ <div className="sw-mt-2 sw-max-w-abs-600 ">
+ <p>{translate('marketplace.page.plugins.description')}</p>
+ {currentEdition !== EditionKey.community && (
+ <FlagMessage className="sw-mt-2" variant="info">
+ <p>
+ <FormattedMessage
+ id="marketplace.page.plugins.description2"
+ defaultMessage={translate('marketplace.page.plugins.description2')}
+ values={{
+ link: (
+ <DocumentationLink to="/instance-administration/marketplace/">
+ {translate('marketplace.page.plugins.description2.link')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ </p>
+ </FlagMessage>
+ )}
+ </div>
+ </div>
+
+ <PluginRiskConsentBox
+ acknowledgeRisk={this.acknowledgeRisk}
+ currentEdition={currentEdition}
+ riskConsent={riskConsent}
+ />
+
+ <Search
+ query={query}
+ updateCenterActive={this.props.updateCenterActive}
+ updateQuery={this.updateQuery}
+ />
+ <div className="sw-mt-4">
+ <Spinner loading={loadingPlugins}>
+ {filteredPlugins.length === 0 &&
+ translate('marketplace.plugin_list.no_plugins', query.filter)}
+ {filteredPlugins.length > 0 && (
+ <>
+ <PluginsList
+ pending={pendingPlugins}
+ plugins={filteredPlugins}
+ readOnly={!allowActions}
+ refreshPending={this.props.fetchPendingPlugins}
+ />
+ <ListFooter
+ useMIUIButtons
+ count={filteredPlugins.length}
+ total={plugins.length}
+ />
+ </>
+ )}
+ </Spinner>
</div>
- </header>
-
- <PluginRiskConsentBox
- acknowledgeRisk={this.acknowledgeRisk}
- currentEdition={currentEdition}
- riskConsent={riskConsent}
- />
-
- <Search
- query={query}
- updateCenterActive={this.props.updateCenterActive}
- updateQuery={this.updateQuery}
- />
- <Spinner loading={loadingPlugins}>
- {filteredPlugins.length === 0 &&
- translate('marketplace.plugin_list.no_plugins', query.filter)}
- {filteredPlugins.length > 0 && (
- <>
- <PluginsList
- pending={pendingPlugins}
- plugins={filteredPlugins}
- readOnly={!allowActions}
- refreshPending={this.props.fetchPendingPlugins}
- />
- <Footer total={filteredPlugins.length} />
- </>
- )}
- </Spinner>
- </main>
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Card, Link } from 'design-system';
import * as React from 'react';
import { getMarketplaceNavigation } from '../../api/navigation';
-import { getAllEditionsAbove } from '../../helpers/editions';
+import { getAllEditionsAbove, getEditionUrl } from '../../helpers/editions';
+import { translate } from '../../helpers/l10n';
import { EditionKey } from '../../types/editions';
import EditionBox from './components/EditionBox';
}
return (
- <div className="spacer-bottom marketplace-editions">
+ <div className="sw-mt-4 sw-flex sw-gap-4">
{visibleEditions.map((edition) => (
- <EditionBox
- currentEdition={currentEdition}
- edition={edition}
+ <Card
key={edition.key}
- ncloc={ncloc}
- serverId={serverId}
- />
+ className="sw-max-w-1/2 sw-flex-1 sw-flex sw-flex-col sw-justify-between"
+ >
+ <EditionBox edition={edition} />
+
+ <div className="sw-mt-4">
+ <Link to={getEditionUrl(edition, { ncloc, serverId, sourceEdition: currentEdition })}>
+ {translate('marketplace.request_free_trial')}
+ </Link>
+ </div>
+ </Card>
))}
</div>
);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { translateWithParameters } from '../../helpers/l10n';
-
-interface Props {
- total: number;
-}
-
-export default function Footer({ total }: Props) {
- return (
- <footer className="spacer-top note text-center">
- {translateWithParameters('x_show', total)}
- </footer>
- );
-}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Title } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { EditionKey } from '../../types/editions';
currentEdition?: EditionKey;
}
-export default function Header({ currentEdition }: Props) {
+export default function Header({ currentEdition }: Readonly<Props>) {
return (
- <header className="page-header" id="marketplace-header">
- <h2 className="page-title">{translate('marketplace.page')}</h2>
+ <header id="marketplace-header">
+ <Title>{translate('marketplace.page')}</Title>
{currentEdition && (
- <h3 className="page-description">
+ <div className="sw-body-sm-highlight">
{translate('marketplace.page.you_are_running', currentEdition)}
- </h3>
+ </div>
)}
- <p className="page-description">
+ <p className="sw-mt-2">
{currentEdition === 'datacenter'
? translate('marketplace.page.description_best_edition')
: translate('marketplace.page.description')}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Card, Table, TableRow } from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
-import { isAvailablePlugin, isInstalledPlugin, PendingPlugin, Plugin } from '../../types/plugins';
+import { PendingPlugin, Plugin, isAvailablePlugin, isInstalledPlugin } from '../../types/plugins';
import PluginAvailable from './components/PluginAvailable';
import PluginInstalled from './components/PluginInstalled';
return undefined;
}
-export default function PluginsList(props: PluginsListProps) {
+export default function PluginsList(props: Readonly<PluginsListProps>) {
const { pending, plugins, readOnly } = props;
const installedPlugins = plugins.filter(isInstalledPlugin);
+
+ const columns = readOnly ? ['25%', 'auto', '20%'] : ['25%', 'auto', '20%', '20%'];
+
return (
- <div className="boxed-group boxed-group-inner" id="marketplace-plugins">
- <ul aria-label={translate('marketplace.page.plugins')}>
+ <Card id="marketplace-plugins">
+ <Table
+ aria-label={translate('marketplace.page.plugins')}
+ columnCount={columns.length}
+ columnWidths={columns}
+ >
{sortBy(plugins, ({ name }) => name).map((plugin) => (
- <li className="panel panel-vertical" key={plugin.key}>
- <table className="marketplace-plugin-table">
- <tbody>
- {isInstalledPlugin(plugin) && (
- <PluginInstalled
- plugin={plugin}
- readOnly={readOnly}
- refreshPending={props.refreshPending}
- status={getPluginStatus(plugin, pending)}
- />
- )}
+ <TableRow key={plugin.key}>
+ {isInstalledPlugin(plugin) && (
+ <PluginInstalled
+ plugin={plugin}
+ readOnly={readOnly}
+ refreshPending={props.refreshPending}
+ status={getPluginStatus(plugin, pending)}
+ />
+ )}
- {isAvailablePlugin(plugin) && (
- <PluginAvailable
- installedPlugins={installedPlugins}
- plugin={plugin}
- readOnly={readOnly}
- refreshPending={props.refreshPending}
- status={getPluginStatus(plugin, pending)}
- />
- )}
- </tbody>
- </table>
- </li>
+ {isAvailablePlugin(plugin) && (
+ <PluginAvailable
+ installedPlugins={installedPlugins}
+ plugin={plugin}
+ readOnly={readOnly}
+ refreshPending={props.refreshPending}
+ status={getPluginStatus(plugin, pending)}
+ />
+ )}
+ </TableRow>
))}
- </ul>
- </div>
+ </Table>
+ </Card>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { InputSearch, ToggleButton } from 'design-system';
import * as React from 'react';
-import ButtonToggle from '../../components/controls/ButtonToggle';
-import SearchBox from '../../components/controls/SearchBox';
import { translate } from '../../helpers/l10n';
import { Query } from './utils';
},
];
return (
- <div className="big-spacer-bottom" id="marketplace-search">
- <div className="display-inline-block text-top nowrap abs-width-240 spacer-right">
- <ButtonToggle
- onCheck={this.handleFilterChange}
- options={radioOptions}
- value={query.filter}
- />
- </div>
- <SearchBox
+ <div className="sw-mt-6 sw-flex sw-gap-6" id="marketplace-search">
+ <ToggleButton
+ onChange={this.handleFilterChange}
+ options={radioOptions}
+ value={query.filter}
+ />
+ <InputSearch
onChange={this.handleSearch}
placeholder={translate('marketplace.search')}
+ size="large"
value={query.search}
/>
</div>
deTitle: byRole('heading', { name: 'SonarQube logo Developer Edition' }),
eeTitle: byRole('heading', { name: 'SonarQube logo Enterprise Edition' }),
dceTitle: byRole('heading', { name: 'SonarQube logo Data Center Edition' }),
- pluginRow: byRole('list', { name: 'marketplace.page.plugins' }).byRole('table'),
- filterAll: byRole('button', { name: 'marketplace.all' }),
- filterInstalled: byRole('button', { name: 'marketplace.installed' }),
- filterWithUpdates: byRole('button', { name: 'marketplace.updates_only' }),
+ pluginRow: byRole('table', { name: 'marketplace.page.plugins' }).byRole('row'),
+ filterAll: byRole('radio', { name: 'marketplace.all' }),
+ filterInstalled: byRole('radio', { name: 'marketplace.installed' }),
+ filterWithUpdates: byRole('radio', { name: 'marketplace.updates_only' }),
search: byRole('searchbox', { name: 'marketplace.search' }),
clearSearch: byRole('button', { name: 'clear' }),
noPluginsText: byText('marketplace.plugin_list.no_plugins', { exact: false }),
expect(ui.updateButton.query()).not.toBeInTheDocument();
expect(ui.riskConsentMessage.get()).toBeInTheDocument();
expect(ui.riskConsentButton.get()).toBeInTheDocument();
- await act(() => user.click(ui.riskConsentButton.get()));
+ await user.click(ui.riskConsentButton.get());
expect(ui.riskConsentMessage.query()).not.toBeInTheDocument();
expect(rows[0]).toHaveTextContent('ATest_install');
expect(ui.installButton.query(rows[0])).not.toBeInTheDocument();
expect(ui.updateButton.query(rows[0])).not.toBeInTheDocument();
expect(ui.uninstallPending.query(rows[0])).not.toBeInTheDocument();
- await act(() => user.click(ui.uninstallButton.get(rows[0])));
+ await user.click(ui.uninstallButton.get(rows[0]));
expect(await ui.uninstallPending.find(rows[0])).toBeInTheDocument();
expect(ui.uninstallButton.query(rows[0])).not.toBeInTheDocument();
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { SubHeading, UnorderedList } from 'design-system';
import * as React from 'react';
-import Link from '../../../components/common/Link';
-import { getEditionUrl } from '../../../helpers/editions';
-import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
import { Edition, EditionKey } from '../../../types/editions';
interface Props {
- currentEdition?: EditionKey;
edition: Edition;
- ncloc?: number;
- serverId?: string;
}
-export default function EditionBox({ edition, ncloc, serverId, currentEdition }: Props) {
- return (
- <div className="boxed-group boxed-group-inner marketplace-edition">
- {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={`${getBaseUrl()}/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>
+export default function EditionBox({ edition }: Readonly<Props>) {
+ switch (edition.key) {
+ case EditionKey.datacenter:
+ return (
+ <div>
+ <SubHeading as="h2" id="data-center-edition">
+ <img
+ alt="SonarQube logo"
+ className="sw-mr-2"
+ width={16}
+ src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`}
+ />
+ <span>Data Center Edition</span>
+ </SubHeading>
+ <p className="sw-mt-4">
+ <em>Designed for High Availability and Scalability</em>
+ </p>
+ <p className="sw-mt-4">Enterprise Edition functionality plus:</p>
+ <UnorderedList className="sw-ml-8" ticks>
+ <li>Component redundancy</li>
+ <li>Data resiliency</li>
+ <li>Horizontal scalability</li>
+ </UnorderedList>
</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={`${getBaseUrl()}/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={`${getBaseUrl()}/images/alm/github.svg`}
- />
- <img
- alt="GitLab"
- className="little-spacer-left max-width-100"
- src={`${getBaseUrl()}/images/alm/gitlab.svg`}
- />
- <img
- alt="Azure DevOps"
- className="little-spacer-left max-width-100"
- src={`${getBaseUrl()}/images/alm/azure.svg`}
- />
- <img
- alt="Bitbucket"
- className="little-spacer-left max-width-100"
- src={`${getBaseUrl()}/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, PL/SQL, ABAP, TSQL & Swift</li>
- </ul>
- </div>
- </div>
+ );
+
+ case EditionKey.enterprise:
+ return (
+ <div>
+ <SubHeading as="h2" id="enterprise-edition">
+ <img
+ alt="SonarQube logo"
+ className="sw-mr-2"
+ width={16}
+ src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`}
+ />
+ <span>Enterprise Edition</span>
+ </SubHeading>
+ <p className="sw-mt-4">
+ <em>Designed to Meet Enterprise Requirements</em>
+ </p>
+ <p className="sw-mt-4">Developer Edition functionality plus:</p>
+ <UnorderedList className="sw-ml-8" ticks>
+ <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>
+ </UnorderedList>
</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={`${getBaseUrl()}/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>
+ );
+
+ case EditionKey.developer:
+ return (
+ <div>
+ <SubHeading as="h2" id="developer-edition">
+ <img
+ alt="SonarQube logo"
+ className="sw-mr-2"
+ width={16}
+ src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`}
+ />
+ <span>Developer Edition</span>
+ </SubHeading>
+ <p className="sw-mt-4">
+ <em>Built for Developers by Developers</em>
+ </p>
+ <p className="sw-mt-4">Community Edition functionality plus:</p>
+ <UnorderedList className="sw-ml-8" ticks>
+ <li>
+ <span>PR / MR decoration & Quality Gate</span>
+ <img
+ alt="GitHub"
+ className="sw-ml-2"
+ src={`${getBaseUrl()}/images/alm/github.svg`}
+ width={16}
+ />
+ <img
+ alt="GitLab"
+ className="sw-ml-2"
+ src={`${getBaseUrl()}/images/alm/gitlab.svg`}
+ width={16}
+ />
+ <img
+ alt="Azure DevOps"
+ className="sw-ml-2"
+ src={`${getBaseUrl()}/images/alm/azure.svg`}
+ width={16}
+ />
+ <img
+ alt="Bitbucket"
+ className="sw-ml-2"
+ src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
+ width={16}
+ />
+ </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, PL/SQL, ABAP, TSQL & Swift</li>
+ </UnorderedList>
</div>
- )}
- <div className="marketplace-edition-action spacer-top">
- <Link
- to={getEditionUrl(edition, { ncloc, serverId, sourceEdition: currentEdition })}
- target="_blank"
- >
- {translate('marketplace.request_free_trial')}
- </Link>
- </div>
- </div>
- );
+ );
+
+ default:
+ return null;
+ }
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { FormattedMessage } from 'react-intl';
-import Link from '../../../components/common/Link';
-import { ResetButtonLink } from '../../../components/controls/buttons';
-import Modal from '../../../components/controls/Modal';
-import { translate } from '../../../helpers/l10n';
-
-interface Props {
- onClose: () => void;
-}
-
-export default function LicensePromptModal({ onClose }: Props) {
- const header = translate('license.prompt.title');
- return (
- <Modal contentLabel={header} onRequestClose={onClose}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
- <div className="modal-body">
- <FormattedMessage
- defaultMessage={translate('license.prompt.description')}
- id="license.prompt.description"
- values={{
- url: (
- <Link onClick={onClose} to="/admin/extension/license/app">
- {translate('license.prompt.link')}
- </Link>
- ),
- }}
- />
- </div>
- <footer className="modal-foot">
- <ResetButtonLink onClick={onClose}>{translate('cancel')}</ResetButtonLink>
- </footer>
- </Modal>
- );
-}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ ButtonSecondary,
+ CheckIcon,
+ Checkbox,
+ DangerButtonSecondary,
+ Link,
+ Spinner,
+} from 'design-system';
import * as React from 'react';
import { installPlugin, uninstallPlugin, updatePlugin } from '../../../api/plugins';
-import Link from '../../../components/common/Link';
-import Checkbox from '../../../components/controls/Checkbox';
import Tooltip from '../../../components/controls/Tooltip';
-import { Button } from '../../../components/controls/buttons';
-import CheckIcon from '../../../components/icons/CheckIcon';
import { translate } from '../../../helpers/l10n';
import { Plugin, isAvailablePlugin, isInstalledPlugin } from '../../../types/plugins';
import PluginUpdateButton from './PluginUpdateButton';
return (
<div className="it__js-actions">
{isAvailablePlugin(plugin) && plugin.termsAndConditionsUrl && (
- <div className="little-spacer-bottom">
+ <div className="sw-flex sw-items-center sw-flex-wrap sw-mb-2">
<Checkbox
checked={this.state.acceptTerms}
- className="js-terms"
id={'plugin-terms-' + plugin.key}
onCheck={this.handleTermsCheck}
>
- <label className="little-spacer-left" htmlFor={'plugin-terms-' + plugin.key}>
- {translate('marketplace.i_accept_the')}
- </label>
+ <span className="sw-ml-2">{translate('marketplace.i_accept_the')}</span>
</Checkbox>
- <a
- className="js-plugin-terms nowrap little-spacer-left"
- href={plugin.termsAndConditionsUrl}
- target="_blank"
- rel="noopener noreferrer"
- >
+ <Link className="sw-whitespace-nowrap sw-ml-1" to={plugin.termsAndConditionsUrl}>
{translate('marketplace.terms_and_conditions')}
- </a>
+ </Link>
</div>
)}
- {loading && <i className="spinner spacer-right little-spacer-top little-spacer-bottom" />}
+ <Spinner className="sw-my-2" loading={loading} />
{isInstalledPlugin(plugin) && (
<>
- {plugin.updates &&
- plugin.updates.map((update, idx) => (
+ {plugin.updates?.map((update, idx) => (
+ <div className="sw-inline-block sw-mr-2 sw-mb-2" key={idx}>
<PluginUpdateButton
disabled={loading}
- key={idx}
onClick={this.handleUpdate}
update={update}
/>
- ))}
+ </div>
+ ))}
<Tooltip overlay={translate('marketplace.requires_restart')}>
- <Button
- className="js-uninstall button-red little-spacer-left"
- disabled={loading}
- onClick={this.handleUninstall}
- >
+ <DangerButtonSecondary disabled={loading} onClick={this.handleUninstall}>
{translate('marketplace.uninstall')}
- </Button>
+ </DangerButtonSecondary>
</Tooltip>
</>
)}
{isAvailablePlugin(plugin) && (
<Tooltip overlay={translate('marketplace.requires_restart')}>
- <Button
- className="js-install"
+ <ButtonSecondary
disabled={
loading || (plugin.termsAndConditionsUrl != null && !this.state.acceptTerms)
}
onClick={this.handleInstall}
>
{translate('marketplace.install')}
- </Button>
+ </ButtonSecondary>
</Tooltip>
)}
</div>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import styled from '@emotion/styled';
+import { Badge, ContentCell, UnorderedList } from 'design-system';
import * as React from 'react';
import { translateWithParameters } from '../../../helpers/l10n';
import { AvailablePlugin, InstalledPlugin } from '../../../types/plugins';
status?: string;
}
-export default function PluginAvailable(props: PluginAvailableProps) {
+export default function PluginAvailable(props: Readonly<PluginAvailableProps>) {
const { installedPlugins, plugin, readOnly, status } = props;
const installedPluginKeys = installedPlugins.map(({ key }) => key);
return (
- <tr>
+ <>
<PluginDescription plugin={plugin} />
- <td className="text-top big-spacer-right">
- <ul>
- <li className="display-flex-row little-spacer-bottom">
- <div className="pull-left spacer-right">
- <span className="badge badge-success">{plugin.release.version}</span>
- </div>
- <div>
- {plugin.release.description}
- <PluginChangeLogButton
- pluginName={plugin.name}
- release={plugin.release}
- update={plugin.update}
- />
- {plugin.update.requires.length > 0 && (
- <p className="little-spacer-top">
- <strong>
- {translateWithParameters(
- 'marketplace.installing_this_plugin_will_also_install_x',
- plugin.update.requires
- .filter(({ key }) => !installedPluginKeys.includes(key))
- .map((requiredPlugin) => requiredPlugin.name)
- .join(', '),
- )}
- </strong>
- </p>
+ <ContentCell>
+ <div className="sw-mr-2">
+ <Badge variant="new">{plugin.release.version}</Badge>
+ </div>
+ <div className="sw-mr-2">{plugin.release.description}</div>
+ <PluginChangeLogButton
+ pluginName={plugin.name}
+ release={plugin.release}
+ update={plugin.update}
+ />
+ {plugin.update.requires.length > 0 && (
+ <p className="sw-mt-2">
+ <strong className="sw-body-sm-highlight">
+ {translateWithParameters(
+ 'marketplace.installing_this_plugin_will_also_install_x',
+ plugin.update.requires
+ .filter(({ key }) => !installedPluginKeys.includes(key))
+ .map((requiredPlugin) => requiredPlugin.name)
+ .join(', '),
)}
- </div>
- </li>
- </ul>
- </td>
+ </strong>
+ </p>
+ )}
+ </ContentCell>
- <td className="text-top width-20">
- <ul>
+ <ContentCell>
+ <StyledUnorderedList>
<PluginUrls plugin={plugin} />
<PluginLicense license={plugin.license} />
<PluginOrganization plugin={plugin} />
- </ul>
- </td>
+ </StyledUnorderedList>
+ </ContentCell>
{!readOnly && (
- <PluginStatus plugin={plugin} refreshPending={props.refreshPending} status={status} />
+ <ContentCell>
+ <PluginStatus plugin={plugin} refreshPending={props.refreshPending} status={status} />
+ </ContentCell>
)}
- </tr>
+ </>
);
}
+
+const StyledUnorderedList = styled(UnorderedList)`
+ margin-top: 0;
+
+ & li:first {
+ margin-top: 0;
+ }
+`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { UnorderedList } from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
export default function PluginChangeLog({ release, update }: Props) {
return (
- <div className="abs-width-300">
- <b className="sw-leading-6">{translate('changelog')}</b>
- <ul className="js-plugin-changelog-list">
+ <div className="sw-p-4">
+ <span className="sw-body-md-highlight">{translate('changelog')}</span>
+ <UnorderedList>
{update.previousUpdates &&
sortBy(update.previousUpdates, (prevUpdate) => prevUpdate.release?.date).map(
(previousUpdate) =>
) : null,
)}
<PluginChangeLogItem release={release} update={update} />
- </ul>
+ </UnorderedList>
</div>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonSecondary, DropdownToggler } from 'design-system';
import * as React from 'react';
-import { ButtonLink } from '../../../components/controls/buttons';
-import Dropdown from '../../../components/controls/Dropdown';
-import EllipsisIcon from '../../../components/icons/EllipsisIcon';
-import { translateWithParameters } from '../../../helpers/l10n';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Release, Update } from '../../../types/plugins';
import PluginChangeLog from './PluginChangeLog';
update: Update;
}
-export default function PluginChangeLogButton({ pluginName, release, update }: Props) {
+export default function PluginChangeLogButton({ pluginName, release, update }: Readonly<Props>) {
+ const [open, setOpen] = React.useState(false);
+
return (
- <Dropdown
- className="display-inline-block little-spacer-left"
+ <DropdownToggler
+ allowResizing
+ onRequestClose={() => setOpen(false)}
+ open={open}
+ id={`plugin-changelog-${pluginName}`}
overlay={<PluginChangeLog release={release} update={update} />}
>
- <ButtonLink
- className="js-changelog"
+ <ButtonSecondary
aria-label={translateWithParameters(
'marketplace.show_plugin_changelog',
pluginName,
release.version,
)}
+ onClick={() => setOpen((open) => !open)}
>
- <EllipsisIcon />
- </ButtonLink>
- </Dropdown>
+ {translate('see_changelog')}
+ </ButtonSecondary>
+ </DropdownToggler>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Badge, Link, ListItem, Note } from 'design-system';
import * as React from 'react';
import Tooltip from '../../../components/controls/Tooltip';
import DateFormatter from '../../../components/intl/DateFormatter';
export default function PluginChangeLogItem({ release, update }: Props) {
return (
- <li className="big-spacer-bottom">
- <div className="little-spacer-bottom">
+ <ListItem>
+ <div className="sw-mb-2">
{update.status === 'COMPATIBLE' || !update.status ? (
- <span className="js-plugin-changelog-version badge badge-success spacer-right">
+ <Badge variant="new" className="sw-mr-4">
{release.version}
- </span>
+ </Badge>
) : (
<Tooltip overlay={translate('marketplace.update_status', update.status)}>
- <span className="js-plugin-changelog-version badge badge-warning spacer-right">
- {release.version}
+ <span>
+ <Badge className="sw-mr-4">{release.version}</Badge>
</span>
</Tooltip>
)}
- <span className="js-plugin-changelog-date note spacer-right">
+ <Note className="sw-mr-4">
<DateFormatter date={release.date} />
- </span>
+ </Note>
{release.changeLogUrl && (
- <a
- className="js-plugin-changelog-link"
- href={release.changeLogUrl}
- target="_blank"
- rel="noopener noreferrer"
- >
- {translate('marketplace.release_notes')}
- </a>
+ <Link to={release.changeLogUrl}>{translate('marketplace.release_notes')}</Link>
)}
</div>
- <div className="js-plugin-changelog-description">{release.description}</div>
- </li>
+ <p>{release.description}</p>
+ </ListItem>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Badge, CellComponent } from 'design-system';
import * as React from 'react';
import { Plugin } from '../../../types/plugins';
plugin: Plugin;
}
-const PluginDescription = (props: Props) => {
+export default function PluginDescription(props: Readonly<Props>) {
return (
- <td className="text-top width-25 big-spacer-right">
- <div>
- <strong className="js-plugin-name">{props.plugin.name}</strong>
- {props.plugin.category && (
- <span className="js-plugin-category badge spacer-left">{props.plugin.category}</span>
- )}
- </div>
- <div className="js-plugin-description little-spacer-top">{props.plugin.description}</div>
- </td>
+ <CellComponent>
+ <strong className="sw-body-sm-highlight">{props.plugin.name}</strong>
+ {props.plugin.category && <Badge className="sw-ml-2">{props.plugin.category}</Badge>}
+ {props.plugin.description && <div className="sw-mt-2">{props.plugin.description}</div>}
+ </CellComponent>
);
-};
-
-export default PluginDescription;
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import styled from '@emotion/styled';
+import { ContentCell, ListItem, UnorderedList } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
import { InstalledPlugin } from '../../../types/plugins';
status?: string;
}
-export default function PluginInstalled({ plugin, readOnly, refreshPending, status }: Props) {
+export default function PluginInstalled({
+ plugin,
+ readOnly,
+ refreshPending,
+ status,
+}: Readonly<Props>) {
return (
- <tr>
+ <>
<PluginDescription plugin={plugin} />
- <td className="text-top big-spacer-right">
- <ul>
- <li className="little-spacer-bottom">
- <strong className="js-plugin-installed-version little-spacer-right">
- {plugin.version}
- </strong>
+ <ContentCell>
+ <StyledUnorderedList>
+ <ListItem className="sw-mt-0">
+ <strong className="sw-mr-1 sw-body-sm-highlight">{plugin.version}</strong>
{translate('marketplace._installed')}
- </li>
+ </ListItem>
<PluginUpdates pluginName={plugin.name} updates={plugin.updates} />
- </ul>
- </td>
+ </StyledUnorderedList>
+ </ContentCell>
- <td className="text-top width-20">
- <ul>
+ <ContentCell>
+ <StyledUnorderedList>
<PluginUrls plugin={plugin} />
<PluginLicense license={plugin.license} />
<PluginOrganization plugin={plugin} />
- </ul>
- </td>
+ </StyledUnorderedList>
+ </ContentCell>
{!readOnly && (
- <PluginStatus plugin={plugin} refreshPending={refreshPending} status={status} />
+ <ContentCell>
+ <PluginStatus plugin={plugin} refreshPending={refreshPending} status={status} />
+ </ContentCell>
)}
- </tr>
+ </>
);
}
+
+const StyledUnorderedList = styled(UnorderedList)`
+ margin-top: 0;
+
+ & li:first {
+ margin-top: 0;
+ }
+`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ListItem } from 'design-system';
+import { isEmpty } from 'lodash';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import Tooltip from '../../../components/controls/Tooltip';
-import { translate } from '../../../helpers/l10n';
interface Props {
license?: string;
}
-export default function PluginLicense({ license }: Props) {
- if (!license) {
+export default function PluginLicense({ license }: Readonly<Props>) {
+ if (isEmpty(license)) {
return null;
}
return (
- <Tooltip overlay={license}>
- <li className="little-spacer-bottom marketplace-plugin-license">
- <FormattedMessage
- defaultMessage={translate('marketplace.licensed_under_x')}
- id="marketplace.licensed_under_x"
- values={{
- license: <span className="js-plugin-license">{license}</span>,
- }}
- />
- </li>
- </Tooltip>
+ <ListItem>
+ <Tooltip overlay={license}>
+ <div>
+ <FormattedMessage
+ id="marketplace.licensed_under_x"
+ values={{
+ license,
+ }}
+ />
+ </div>
+ </Tooltip>
+ </ListItem>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Link, ListItem } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { translate } from '../../../helpers/l10n';
import { Plugin } from '../../../types/plugins';
export interface PluginOrganizationProps {
plugin: Plugin;
}
-export default function PluginOrganization({ plugin }: PluginOrganizationProps) {
+export default function PluginOrganization({ plugin }: Readonly<PluginOrganizationProps>) {
if (!plugin.organizationName) {
return null;
}
return (
- <li className="little-spacer-bottom">
+ <ListItem>
<FormattedMessage
- defaultMessage={translate('marketplace.developed_by_x')}
id="marketplace.developed_by_x"
values={{
organization: plugin.organizationUrl ? (
- <a
- className="js-plugin-organization"
- href={plugin.organizationUrl}
- target="_blank"
- rel="noopener noreferrer"
- >
- {plugin.organizationName}
- </a>
+ <Link to={plugin.organizationUrl}>{plugin.organizationName}</Link>
) : (
- <span className="js-plugin-organization">{plugin.organizationName}</span>
+ <span>{plugin.organizationName}</span>
),
}}
/>
- </li>
+ </ListItem>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonPrimary, Card, DarkLabel } from 'design-system';
import * as React from 'react';
-import { Button } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';
import { EditionKey } from '../../../types/editions';
import { RiskConsent } from '../../../types/plugins';
riskConsent?: RiskConsent;
}
-export default function PluginRiskConsentBox(props: PluginRiskConsentBoxProps) {
+export default function PluginRiskConsentBox(props: Readonly<PluginRiskConsentBoxProps>) {
const { currentEdition, riskConsent } = props;
if (riskConsent === RiskConsent.Accepted) {
}
return (
- <div className="boxed-group it__plugin_risk_consent_box">
- <h2>{translate('marketplace.risk_consent.title')}</h2>
- <div className="boxed-group-inner">
- <p>{translate('marketplace.risk_consent.description')}</p>
- {currentEdition === EditionKey.community && (
- <p className="spacer-top">{translate('marketplace.risk_consent.installation')}</p>
- )}
- <Button
- className="display-block big-spacer-top button-primary"
- onClick={props.acknowledgeRisk}
- >
- {translate('marketplace.risk_consent.action')}
- </Button>
- </div>
- </div>
+ <Card className="sw-mt-6 it__plugin_risk_consent_box">
+ <DarkLabel>{translate('marketplace.risk_consent.title')}</DarkLabel>
+
+ <p className="sw-mt-2">{translate('marketplace.risk_consent.description')}</p>
+ {currentEdition === EditionKey.community && (
+ <p className="sw-mt-2">{translate('marketplace.risk_consent.installation')}</p>
+ )}
+ <ButtonPrimary className="sw-mt-4" onClick={props.acknowledgeRisk}>
+ {translate('marketplace.risk_consent.action')}
+ </ButtonPrimary>
+ </Card>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { FlagMessage } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
import { Plugin } from '../../../types/plugins';
status?: string;
}
-export default function PluginStatus({ plugin, refreshPending, status }: Props) {
- return (
- <td className="text-top text-right width-20 little-spacer-left">
- {status === 'installing' && (
- <p className="text-success">{translate('marketplace.install_pending')}</p>
- )}
+export default function PluginStatus({ plugin, refreshPending, status }: Readonly<Props>) {
+ switch (status) {
+ case 'installing':
+ return <FlagMessage variant="info">{translate('marketplace.install_pending')}</FlagMessage>;
- {status === 'updating' && (
- <p className="text-success">{translate('marketplace.update_pending')}</p>
- )}
+ case 'updating':
+ return <FlagMessage variant="info">{translate('marketplace.update_pending')}</FlagMessage>;
- {status === 'removing' && (
- <p className="text-danger">{translate('marketplace.uninstall_pending')}</p>
- )}
+ case 'removing':
+ return <FlagMessage variant="info">{translate('marketplace.uninstall_pending')}</FlagMessage>;
- {status == null && <PluginActions plugin={plugin} refreshPending={refreshPending} />}
- </td>
- );
+ default:
+ return <PluginActions plugin={plugin} refreshPending={refreshPending} />;
+ }
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonSecondary } from 'design-system';
import * as React from 'react';
-import { Button } from '../../../components/controls/buttons';
import Tooltip from '../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Update } from '../../../types/plugins';
update: Update;
}
-export default class PluginUpdateButton extends React.PureComponent<Props> {
- handleClick = () => {
- this.props.onClick(this.props.update);
- };
+export default function PluginUpdateButton(props: Readonly<Props>) {
+ const { disabled, onClick, update } = props;
- render() {
- const { disabled, update } = this.props;
- if (update.status !== 'COMPATIBLE' || !update.release) {
- return null;
- }
- return (
- <Tooltip overlay={translate('marketplace.requires_restart')}>
- <Button
- className="js-update little-spacer-bottom"
- disabled={disabled}
- onClick={this.handleClick}
- >
- {translateWithParameters('marketplace.update_to_x', update.release.version)}
- </Button>
- </Tooltip>
- );
+ const handleClick = React.useCallback(() => {
+ onClick(update);
+ }, [onClick, update]);
+
+ if (update.status !== 'COMPATIBLE' || !update.release) {
+ return null;
}
+ return (
+ <Tooltip overlay={translate('marketplace.requires_restart')}>
+ <ButtonSecondary disabled={disabled} onClick={handleClick}>
+ {translateWithParameters('marketplace.update_to_x', update.release.version)}
+ </ButtonSecondary>
+ </Tooltip>
+ );
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Badge, ListItem } from 'design-system';
import * as React from 'react';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
release: Release;
}
-export default function PluginUpdateItem({ release, update, pluginName }: Props) {
+export default function PluginUpdateItem({ release, update, pluginName }: Readonly<Props>) {
return (
- <li className="display-flex-row little-spacer-bottom" key={release.version}>
- <div className="pull-left spacer-right">
+ <ListItem className="sw-flex sw-items-center" key={release.version}>
+ <div className="sw-mr-2">
{update.status === 'COMPATIBLE' ? (
- <span className="js-update-version badge badge-success">{release.version}</span>
+ <Badge variant="new">{release.version}</Badge>
) : (
<Tooltip overlay={translate('marketplace.update_status', update.status)}>
- <span className="js-update-version badge badge-warning">{release.version}</span>
+ <span>
+ <Badge>{release.version}</Badge>
+ </span>
</Tooltip>
)}
</div>
{release.description}
<PluginChangeLogButton pluginName={pluginName} release={release} update={update} />
</div>
- </li>
+ </ListItem>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ListItem, UnorderedList } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
import { Update } from '../../../types/plugins';
updates?: Update[];
}
-export default function PluginUpdates({ pluginName, updates }: PluginUpdatesProps) {
+export default function PluginUpdates({ pluginName, updates }: Readonly<PluginUpdatesProps>) {
if (!updates || updates.length <= 0) {
return null;
}
return (
- <li className="spacer-top">
- <strong>{translate('marketplace.updates')}:</strong>
- <ul className="little-spacer-top">
+ <ListItem>
+ <strong className="sw-body-sm-highlight">{translate('marketplace.updates')}:</strong>
+ <UnorderedList className="sw-mt-2">
{updates.map((update) =>
update.release ? (
<PluginUpdateItem
/>
) : null,
)}
- </ul>
- </li>
+ </UnorderedList>
+ </ListItem>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Link, ListItem } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
import { Plugin } from '../../../types/plugins';
plugin: Plugin;
}
-export default function PluginUrls({ plugin }: Props) {
+export default function PluginUrls({ plugin }: Readonly<Props>) {
if (!plugin.homepageUrl && !plugin.issueTrackerUrl) {
return null;
}
return (
- <li className="little-spacer-bottom">
- <ul className="list-inline">
- {plugin.homepageUrl && (
- <li>
- <a
- className="js-plugin-homepage"
- href={plugin.homepageUrl}
- target="_blank"
- rel="noopener noreferrer"
- >
- {translate('marketplace.homepage')}
- </a>
- </li>
- )}
- {plugin.issueTrackerUrl && (
- <li>
- <a
- className="js-plugin-issues"
- href={plugin.issueTrackerUrl}
- target="_blank"
- rel="noopener noreferrer"
- >
- {translate('marketplace.issue_tracker')}
- </a>
- </li>
- )}
- </ul>
- </li>
+ <ListItem className="sw-flex sw-flex-wrap sw-gap-4">
+ {plugin.homepageUrl && (
+ <Link className="sw-whitespace-nowrap" to={plugin.homepageUrl}>
+ {translate('marketplace.homepage')}
+ </Link>
+ )}
+ {plugin.issueTrackerUrl && (
+ <Link className="sw-whitespace-nowrap" to={plugin.issueTrackerUrl}>
+ {translate('marketplace.issue_tracker')}
+ </Link>
+ )}
+ </ListItem>
);
}
constructor(props: Props) {
super(props);
- this.state = { value: props.value || '' };
+ this.state = { value: props.value ?? '' };
this.debouncedOnChange = debounce(this.props.onChange, DEBOUNCE_DELAY);
}