Merge branch 'develop' into feature/create_gpg_signatures

# Conflicts:
#	CHANGELOG.md
#	yarn.lock
This commit is contained in:
Konstantin Schaper
2020-08-07 15:13:29 +02:00
25 changed files with 1686 additions and 160 deletions

View File

@@ -6,11 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Add generic popover component to ui-components
- Sign PR merges and commits performed through ui with generated private key ([#1285](https://github.com/scm-manager/scm-manager/pull/1285))
- Add generic popover component to ui-components ([#1285](https://github.com/scm-manager/scm-manager/pull/1285))
- Show changeset signatures in ui and add public keys ([#1273](https://github.com/scm-manager/scm-manager/pull/1273))
## [2.3.0] - 2020-07-23
## [2.3.1] - 2020-08-04
### Added
- New api to resolve SCM-Manager root url ([#1276](https://github.com/scm-manager/scm-manager/pull/1276))
### Changed
- Help tooltips are now mutliline by default ([#1271](https://github.com/scm-manager/scm-manager/pull/1271))
### Fixed
- Fixed unnecessary horizontal scrollbar in modal dialogs ([#1271](https://github.com/scm-manager/scm-manager/pull/1271))
- Avoid stacktrace logging when protocol url is accessed outside of request scope ([#1276](https://github.com/scm-manager/scm-manager/pull/1276))
## [2.3.0] - 2020-07-23
### Added
- Add branch link provider to access branch links in plugins ([#1243](https://github.com/scm-manager/scm-manager/pull/1243))
- Add key value input field component ([#1246](https://github.com/scm-manager/scm-manager/pull/1246))
@@ -250,3 +261,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[2.1.1]: https://www.scm-manager.org/download/2.1.1
[2.2.0]: https://www.scm-manager.org/download/2.2.0
[2.3.0]: https://www.scm-manager.org/download/2.3.0
[2.3.1]: https://www.scm-manager.org/download/2.3.1

View File

@@ -25,7 +25,7 @@ The location of the file depends also on the type of installation.
| Type of Installation | Path |
|----------------------|---------|
| Docker | /opt/scm-server/conf/logging.xml |
| Docker | /etc/scm/logging.xml |
| RPM | /etc/scm/logging.xml |
| DEB | /etc/scm/logging.xml |
| Unix | $EXTRACT_PATH/scm-server/conf/logging.xml |

View File

@@ -40,3 +40,7 @@ After changing the configuration, SCM-Manager must be restarted.
### How do I install plugins?
Find the plugin you like to install at [plugins](/plugins#categories) and follow the installation instructions on the install page of the plugin.
### How can I import my existing (git|mercurial|subversion) repository
Please have a look on [these](../import/) detailed instructions.

55
docs/en/import.md Normal file
View File

@@ -0,0 +1,55 @@
---
title: Import existing repositories
subtitle: How to import existing repositories into SCM-Manager
displayToc: true
---
## Git
First you have to clone the old repository with the `mirror` option.
This option ensures that all branches and tags are fetched from the remote repository.
Assuming that your remote repository is accessible under the url `https://hgttg.com/r/git/heart-of-gold`, the clone command should look like this:
```bash
git clone --mirror https://hgttg.com/r/git/heart-of-gold
```
Than you have to create your new repository via the SCM-Manager web interface and copy the url.
In this example we assume that the new repository is available at `https://hitchhiker.com/scm/repo/hgttg/heart-of-gold`. After the new repository is created, we can configure our local repository for the new location and push all refs.
```bash
cd heart-of-gold
git remote set-url origin https://hitchhiker.com/scm/repo/hgttg/heart-of-gold
git push --mirror
```
## Mercurial
To import an existing mercurial repository, we have to create a new repository over the SCM-Manager web interface, clone it, pull from the old repository and push to the new repository.
In this example we assume that the old repository is `https://hgttg.com/r/hg/heart-of-gold` and the newly created is located at `https://hitchhiker.com/scm/repo/hgttg/heart-of-gold`:
```bash
hg clone https://hitchhiker.com/scm/repo/hgttg/heart-of-gold
cd heart-of-gold
hg pull https://hgttg.com/r/hg/heart-of-gold
hg push
```
## Subversion
Subversion is not as easy as mercurial or git.
For subversion we have to locate the old repository on the filesystem and create a dump with the `svnadmin` tool.
```bash
svnadmin dump /path/to/repo > oldrepo.dump
```
Now we have to create a new repository via the SCM-Manager web interface.
After the repository is created, we have to find its location on the filesystem.
This could be done by finding the directory with the newest timestamp in your scm home directory under `repositories`.
You can check whether you have found the correct directory by having a look at the file `metadata.xml`. Here you should find the namespace and the name of the repository created.
Now its time to import the dump from the old repository:
```bash
svnadmin load /path/to/scm-home/repositories/id/data < oldrepo.dump
```

View File

@@ -2,6 +2,7 @@
entries:
- /installation/
- /migrate-scm-manager-from-v1/
- /import/
- /faq/
- /known-issues/

View File

@@ -14,9 +14,12 @@
"deploy": "ui-scripts publish",
"set-version": "ui-scripts version"
},
"dependencies": {},
"devDependencies": {
"babel-plugin-reflow": "^0.2.7",
"lerna": "^3.17.0"
"husky": "^4.2.5",
"lerna": "^3.17.0",
"lint-staged": "^10.2.11"
},
"resolutions": {
"babel-core": "7.0.0-bridge.0",
@@ -32,5 +35,12 @@
"preset": "@scm-manager/jest-preset"
},
"prettier": "@scm-manager/prettier-config",
"dependencies": {}
"husky": {
"hooks": {
"pre-commit": "lint-staged --verbose"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": "eslint"
}
}

View File

@@ -0,0 +1,53 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
import java.net.URL;
/**
* RootURL is able to return the root url of the SCM-Manager instance,
* regardless of the scope (web request, async hook, ssh command, etc).
*
* @since 2.3.1
*/
public interface RootURL {
/**
* Returns the root url of the SCM-Manager instance.
*
* @return root url
*/
URL get();
/**
* Returns the root url of the SCM-Manager instance as string.
*
* @return root url as string
*/
default String getAsString() {
return get().toExternalForm();
}
}

View File

@@ -25,6 +25,7 @@
package sonia.scm.repository.spi;
import lombok.extern.slf4j.Slf4j;
import sonia.scm.RootURL;
import sonia.scm.api.v2.resources.ScmPathInfoStore;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.Repository;
@@ -37,6 +38,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Supplier;
import static java.util.Optional.empty;
import static java.util.Optional.of;
@@ -45,16 +47,36 @@ import static java.util.Optional.of;
public abstract class InitializingHttpScmProtocolWrapper implements ScmProtocolProvider<HttpScmProtocol> {
private final Provider<? extends ScmProviderHttpServlet> delegateProvider;
private final Provider<ScmPathInfoStore> pathInfoStore;
private final ScmConfiguration scmConfiguration;
private final Supplier<String> basePathSupplier;
private volatile boolean isInitialized = false;
/**
* Constructs a new {@link InitializingHttpScmProtocolWrapper}.
*
* @param delegateProvider injection provider for the servlet delegate
* @param pathInfoStore url info store
* @param scmConfiguration scm-manager main configuration
*
* @deprecated use {@link InitializingHttpScmProtocolWrapper(Provider, RootURL)} instead.
*/
@Deprecated
protected InitializingHttpScmProtocolWrapper(Provider<? extends ScmProviderHttpServlet> delegateProvider, Provider<ScmPathInfoStore> pathInfoStore, ScmConfiguration scmConfiguration) {
this.delegateProvider = delegateProvider;
this.pathInfoStore = pathInfoStore;
this.scmConfiguration = scmConfiguration;
this.basePathSupplier = new LegacySupplier(pathInfoStore, scmConfiguration);
}
/**
* Constructs a new {@link InitializingHttpScmProtocolWrapper}.
*
* @param delegateProvider injection provider for the servlet delegate
* @param rootURL root url
*
* @since 2.3.1
*/
public InitializingHttpScmProtocolWrapper(Provider<? extends ScmProviderHttpServlet> delegateProvider, RootURL rootURL) {
this.delegateProvider = delegateProvider;
this.basePathSupplier = rootURL::getAsString;
}
protected void initializeServlet(ServletConfig config, ScmProviderHttpServlet httpServlet) throws ServletException {
@@ -64,12 +86,25 @@ public abstract class InitializingHttpScmProtocolWrapper implements ScmProtocolP
@Override
public HttpScmProtocol get(Repository repository) {
if (!repository.getType().equals(getType())) {
throw new IllegalArgumentException(String.format("cannot handle repository with type %s with protocol for type %s", repository.getType(), getType()));
throw new IllegalArgumentException(
String.format("cannot handle repository with type %s with protocol for type %s", repository.getType(), getType())
);
}
return new ProtocolWrapper(repository, computeBasePath());
return new ProtocolWrapper(repository, basePathSupplier.get());
}
private String computeBasePath() {
private static class LegacySupplier implements Supplier<String> {
private final Provider<ScmPathInfoStore> pathInfoStore;
private final ScmConfiguration scmConfiguration;
private LegacySupplier(Provider<ScmPathInfoStore> pathInfoStore, ScmConfiguration scmConfiguration) {
this.pathInfoStore = pathInfoStore;
this.scmConfiguration = scmConfiguration;
}
@Override
public String get() {
return getPathFromScmPathInfoIfAvailable().orElse(getPathFromConfiguration());
}
@@ -90,6 +125,8 @@ public abstract class InitializingHttpScmProtocolWrapper implements ScmProtocolP
return scmConfiguration.getBaseUrl();
}
}
private class ProtocolWrapper extends HttpScmProtocol {
public ProtocolWrapper(Repository repository, String basePath) {

View File

@@ -925,11 +925,16 @@ public final class HttpUtil
@VisibleForTesting
static String createForwardedBaseUrl(HttpServletRequest request)
{
String proto = getHeader(request, HEADER_X_FORWARDED_PROTO,
request.getScheme());
String fhost = getHeader(request, HEADER_X_FORWARDED_HOST, null);
if (fhost == null) {
throw new IllegalStateException(
String.format("request has no %s header and does not look like it is forwarded", HEADER_X_FORWARDED_HOST)
);
}
String proto = getHeader(request, HEADER_X_FORWARDED_PROTO, request.getScheme());
String host;
String fhost = getHeader(request, HEADER_X_FORWARDED_HOST,
request.getScheme());
String port = request.getHeader(HEADER_X_FORWARDED_PORT);
int s = fhost.indexOf(SEPARATOR_PORT);

View File

@@ -26,10 +26,14 @@ package sonia.scm.repository.spi;
import com.google.inject.ProvisionException;
import com.google.inject.util.Providers;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.OngoingStubbing;
import sonia.scm.RootURL;
import sonia.scm.api.v2.resources.ScmPathInfo;
import sonia.scm.api.v2.resources.ScmPathInfoStore;
import sonia.scm.config.ScmConfiguration;
@@ -44,22 +48,53 @@ import java.io.IOException;
import java.net.URI;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.*;
public class InitializingHttpScmProtocolWrapperTest {
@ExtendWith(MockitoExtension.class)
class InitializingHttpScmProtocolWrapperTest {
private static final Repository REPOSITORY = new Repository("", "git", "space", "name");
@Mock
private ScmProviderHttpServlet delegateServlet;
private InitializingHttpScmProtocolWrapper wrapper;
@Nested
class WithRootURL {
@Mock
private RootURL rootURL;
@BeforeEach
void init() {
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(delegateServlet), rootURL) {
@Override
public String getType() {
return "git";
}
};
when(rootURL.getAsString()).thenReturn("https://hitchhiker.com/scm");
}
@Test
void shouldReturnUrlFromRootURL() {
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
assertEquals("https://hitchhiker.com/scm/repo/space/name", httpScmProtocol.getUrl());
}
}
@Nested
class WithPathInfoStore {
@Mock
private ScmPathInfoStore pathInfoStore;
@Mock
private ScmConfiguration scmConfiguration;
private Provider<ScmPathInfoStore> pathInfoStoreProvider;
@Mock
@@ -69,25 +104,22 @@ public class InitializingHttpScmProtocolWrapperTest {
@Mock
private ServletConfig servletConfig;
private InitializingHttpScmProtocolWrapper wrapper;
@Before
public void init() {
initMocks(this);
@BeforeEach
void init() {
pathInfoStoreProvider = mock(Provider.class);
when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
lenient().when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(this.delegateServlet), pathInfoStoreProvider, scmConfiguration) {
wrapper = new InitializingHttpScmProtocolWrapper(Providers.of(delegateServlet), pathInfoStoreProvider, scmConfiguration) {
@Override
public String getType() {
return "git";
}
};
when(scmConfiguration.getBaseUrl()).thenReturn("http://example.com/scm");
lenient().when(scmConfiguration.getBaseUrl()).thenReturn("http://example.com/scm");
}
@Test
public void shouldUsePathFromPathInfo() {
void shouldUsePathFromPathInfo() {
mockSetPathInfo();
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
@@ -96,14 +128,14 @@ public class InitializingHttpScmProtocolWrapperTest {
}
@Test
public void shouldUseConfigurationWhenPathInfoNotSet() {
void shouldUseConfigurationWhenPathInfoNotSet() {
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
assertEquals("http://example.com/scm/repo/space/name", httpScmProtocol.getUrl());
}
@Test
public void shouldUseConfigurationWhenNotInRequestScope() {
void shouldUseConfigurationWhenNotInRequestScope() {
when(pathInfoStoreProvider.get()).thenThrow(new ProvisionException("test"));
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
@@ -112,7 +144,7 @@ public class InitializingHttpScmProtocolWrapperTest {
}
@Test
public void shouldInitializeAndDelegateRequestThroughFilter() throws ServletException, IOException {
void shouldInitializeAndDelegateRequestThroughFilter() throws ServletException, IOException {
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
httpScmProtocol.serve(request, response, servletConfig);
@@ -122,7 +154,7 @@ public class InitializingHttpScmProtocolWrapperTest {
}
@Test
public void shouldInitializeOnlyOnce() throws ServletException, IOException {
void shouldInitializeOnlyOnce() throws ServletException, IOException {
HttpScmProtocol httpScmProtocol = wrapper.get(REPOSITORY);
httpScmProtocol.serve(request, response, servletConfig);
@@ -132,13 +164,19 @@ public class InitializingHttpScmProtocolWrapperTest {
verify(delegateServlet, times(2)).service(request, response, REPOSITORY);
}
@Test(expected = IllegalArgumentException.class)
public void shouldFailForIllegalScmType() {
HttpScmProtocol httpScmProtocol = wrapper.get(new Repository("", "other", "space", "name"));
@Test
void shouldFailForIllegalScmType() {
Repository repository = new Repository("", "other", "space", "name");
assertThrows(
IllegalArgumentException.class,
() -> wrapper.get(repository)
);
}
private OngoingStubbing<ScmPathInfo> mockSetPathInfo() {
return when(pathInfoStore.get()).thenReturn(() -> URI.create("http://example.com/scm/api/"));
}
}
}

View File

@@ -234,6 +234,12 @@ public class HttpUtilTest
HttpUtil.createForwardedBaseUrl(request));
}
@Test(expected = IllegalStateException.class)
public void shouldTrowIllegalStateExceptionWithoutForwardedHostHeader() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpUtil.createForwardedBaseUrl(request);
}
/**
* Method description
*

View File

@@ -24,22 +24,20 @@
package sonia.scm.web;
import sonia.scm.api.v2.resources.ScmPathInfoStore;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.RootURL;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.spi.InitializingHttpScmProtocolWrapper;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
@Singleton
@Extension
public class GitScmProtocolProviderWrapper extends InitializingHttpScmProtocolWrapper {
@Inject
public GitScmProtocolProviderWrapper(ScmGitServletProvider servletProvider, Provider<ScmPathInfoStore> uriInfoStore, ScmConfiguration scmConfiguration) {
super(servletProvider, uriInfoStore, scmConfiguration);
public GitScmProtocolProviderWrapper(ScmGitServletProvider servletProvider, RootURL rootURL) {
super(servletProvider, rootURL);
}
@Override

View File

@@ -24,22 +24,21 @@
package sonia.scm.web;
import sonia.scm.api.v2.resources.ScmPathInfoStore;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.RootURL;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.spi.InitializingHttpScmProtocolWrapper;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
@Singleton
@Extension
public class HgScmProtocolProviderWrapper extends InitializingHttpScmProtocolWrapper {
@Inject
public HgScmProtocolProviderWrapper(HgCGIServletProvider servletProvider, Provider<ScmPathInfoStore> uriInfoStore, ScmConfiguration scmConfiguration) {
super(servletProvider, uriInfoStore, scmConfiguration);
public HgScmProtocolProviderWrapper(HgCGIServletProvider servletProvider, RootURL rootURL) {
super(servletProvider, rootURL);
}
@Override

View File

@@ -24,15 +24,13 @@
package sonia.scm.web;
import sonia.scm.api.v2.resources.ScmPathInfoStore;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.RootURL;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.SvnRepositoryHandler;
import sonia.scm.repository.spi.InitializingHttpScmProtocolWrapper;
import sonia.scm.repository.spi.ScmProviderHttpServlet;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
@@ -45,19 +43,18 @@ public class SvnScmProtocolProviderWrapper extends InitializingHttpScmProtocolWr
public static final String PARAMETER_SVN_PARENTPATH = "SVNParentPath";
@Inject
public SvnScmProtocolProviderWrapper(SvnDAVServletProvider servletProvider, RootURL rootURL) {
super(servletProvider, rootURL);
}
@Override
public String getType() {
return SvnRepositoryHandler.TYPE_NAME;
}
@Inject
public SvnScmProtocolProviderWrapper(SvnDAVServletProvider servletProvider, Provider<ScmPathInfoStore> uriInfoStore, ScmConfiguration scmConfiguration) {
super(servletProvider, uriInfoStore, scmConfiguration);
}
@Override
protected void initializeServlet(ServletConfig config, ScmProviderHttpServlet httpServlet) throws ServletException {
super.initializeServlet(new SvnConfigEnhancer(config), httpServlet);
}

View File

@@ -0,0 +1,58 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import styled from "styled-components";
import * as React from "react";
import { storiesOf } from "@storybook/react";
import Help from "./Help";
const Wrapper = styled.div`
margin: 5rem;
`;
const Spacing = styled.div`
margin-top: 1rem;
`;
const longContent =
"Cleverness nuclear genuine static irresponsibility invited President Zaphod\n" +
"Beeblebrox hyperspace ship. Another custard through computer-generated universe\n" +
"shapes field strong disaster parties Russells ancestors infinite colour\n" +
"imaginative generator sweep.";
storiesOf("Help", module)
.addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>)
.add("Default", () => <Help message="This is a help message" />)
.add("Multiline", () => (
<>
<Spacing>
<label>With multiline (default):</label>
<Help message={longContent} />
</Spacing>
<Spacing>
<label>Without multiline:</label>
<Help message={longContent} multiline={false} />
</Spacing>
</>
));

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React from "react";
import React, { FC } from "react";
import classNames from "classnames";
import styled from "styled-components";
import Tooltip from "./Tooltip";
@@ -29,6 +29,7 @@ import HelpIcon from "./HelpIcon";
type Props = {
message: string;
multiline?: boolean;
className?: string;
};
@@ -37,13 +38,17 @@ const HelpTooltip = styled(Tooltip)`
padding-left: 3px;
`;
export default class Help extends React.Component<Props> {
render() {
const { message, className } = this.props;
return (
<HelpTooltip className={classNames("is-inline-block", className)} message={message}>
const Help: FC<Props> = ({ message, multiline, className }) => (
<HelpTooltip
className={classNames("is-inline-block", multiline ? "has-tooltip-multiline" : undefined, className)}
message={message}
>
<HelpIcon />
</HelpTooltip>
);
}
}
);
Help.defaultProps = {
multiline: true
};
export default Help;

View File

@@ -38956,6 +38956,65 @@ exports[`Storyshots Forms|Checkbox Disabled 1`] = `
</div>
`;
exports[`Storyshots Forms|Checkbox With HelpText 1`] = `
<div
className="Checkboxstories__Spacing-sc-1bg8q8e-0 kJtXav"
>
<div
className="field"
>
<div
className="control"
onClick={[Function]}
>
<label
className="checkbox"
>
<i
className="is-outlined fa-square has-text-black far"
/>
Classic helpText
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="This is a classic help text."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
</div>
</div>
<div
className="field"
>
<div
className="control"
onClick={[Function]}
>
<label
className="checkbox"
>
<i
className="is-outlined fa-check-square has-text-link fa"
/>
Long helpText
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
</div>
</div>
</div>
`;
exports[`Storyshots Forms|DropDown Default 1`] = `
<div
className="select"
@@ -39065,6 +39124,51 @@ exports[`Storyshots Forms|Radio Disabled 1`] = `
</div>
`;
exports[`Storyshots Forms|Radio With HelpText 1`] = `
<div
className="Radiostories__RadioList-sc-18hvwhd-1 fSTSuW"
>
<label
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
>
<input
checked={false}
onChange={[Function]}
type="radio"
/>
Classic helpText
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="This is a classic help text."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
<label
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
>
<input
checked={true}
onChange={[Function]}
type="radio"
/>
Long helpText
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
</div>
`;
exports[`Storyshots Forms|Textarea OnCancel 1`] = `
<div
className="Textareastories__Spacing-lk5v3m-0 hldael"
@@ -39142,6 +39246,64 @@ exports[`Storyshots Forms|Textarea OnSubmit 1`] = `
</div>
`;
exports[`Storyshots Help Default 1`] = `
<div
className="Helpstories__Wrapper-yq79zu-0 fkZuPN"
>
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="This is a help message"
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</div>
`;
exports[`Storyshots Help Multiline 1`] = `
<div
className="Helpstories__Wrapper-yq79zu-0 fkZuPN"
>
<div
className="Helpstories__Spacing-yq79zu-1 jVjwc"
>
<label>
With multiline (default):
</label>
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="Cleverness nuclear genuine static irresponsibility invited President Zaphod
Beeblebrox hyperspace ship. Another custard through computer-generated universe
shapes field strong disaster parties Russells ancestors infinite colour
imaginative generator sweep."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</div>
<div
className="Helpstories__Spacing-yq79zu-1 jVjwc"
>
<label>
Without multiline:
</label>
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block"
data-tooltip="Cleverness nuclear genuine static irresponsibility invited President Zaphod
Beeblebrox hyperspace ship. Another custard through computer-generated universe
shapes field strong disaster parties Russells ancestors infinite colour
imaginative generator sweep."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</div>
</div>
`;
exports[`Storyshots Icon Colors 1`] = `
<div
className="Iconstories__Wrapper-sc-1g657fe-0 culAJk"
@@ -41869,6 +42031,44 @@ exports[`Storyshots Modal|ConfirmAlert Default 1`] = `
</div>
`;
exports[`Storyshots Modal|Modal Closeable 1`] = `
<div
className="modal is-active"
>
<div
className="modal-background"
/>
<div
className="modal-card"
>
<header
className="modal-card-head has-background-light"
>
<p
className="modal-card-title is-marginless"
>
Hitchhiker Modal
</p>
<button
aria-label="close"
className="delete"
onClick={[Function]}
/>
</header>
<section
className="modal-card-body"
>
<p>
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
ordinary mob.
</p>
</section>
</div>
</div>
`;
exports[`Storyshots Modal|Modal Default 1`] = `
<div
className="modal is-active"
@@ -41897,7 +42097,434 @@ exports[`Storyshots Modal|Modal Default 1`] = `
className="modal-card-body"
>
<p>
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly ordinary mob.
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
ordinary mob.
</p>
</section>
</div>
</div>
`;
exports[`Storyshots Modal|Modal Long content 1`] = `
<div
className="modal is-active"
>
<div
className="modal-background"
/>
<div
className="modal-card"
>
<header
className="modal-card-head has-background-light"
>
<p
className="modal-card-title is-marginless"
>
Hitchhiker Modal
</p>
<button
aria-label="close"
className="delete"
onClick={[Function]}
/>
</header>
<section
className="modal-card-body"
>
<h1
className="title"
>
Marvin
</h1>
<h2
className="subtitle"
>
The Paranoid Android
</h2>
<hr />
<div
className="notification is-info"
>
The following content comes from the awesome
<a
href="https://hitchhikers.fandom.com/wiki/Main_Page"
rel="noopener noreferrer"
target="_blank"
>
Hitchhikers Wiki
</a>
</div>
<hr />
<div
className="has-text-centered"
>
<img
alt="Marvin"
src="https://vignette.wikia.nocookie.net/hitchhikers/images/a/a4/Marvin.jpg/revision/latest/scale-to-width-down/150?cb=20100530114055"
/>
</div>
<hr />
<p
className="content"
>
Marvin, more fully known as Marvin the Paranoid Android, is an incredibly brilliant but overwhelmingly depressed robot manufactured by the Sirius Cybernetics Corporation and unwilling servant to the crew of the Heart of Gold.
</p>
<hr />
<div
className="content"
>
<h4>
Physical Appearance
</h4>
<p>
In the novels, Marvin is described thusly: "...though it was beautifully constructed and polished it looked somehow as if the various parts of its more or less humanoid body didn't quite fit properly. In fact, they fit perfectly well, but something in its bearing suggested that they might have fitted better."
</p>
<p>
On the radio show, there's no physical description of Marvin, though his voice is digitally altered to sound more robotic, and any scene that focuses on him is accompanied by sounds of mechanical clanking and hissing.
</p>
<p>
In the TV series, Marvin is built in the style of a 1950's robot similar to Robbie the Robot from Forbidden Planet or Twiki from Buck Rogers. His body is blocky and angular, with a pair of clamp-claw hands, shuffling feet and a squarish head with a dour face.
</p>
<p>
In the movie, Marvin is a short, stout robot built of smooth, white plastic. His arms are much longer than his legs, and his head is a massive sphere with only a pair of triangle eyes for a face. His large head and simian-like proportions give Marvin a perpetual slouch, adding to his melancholy personality. At the start of the film his eyes glow, but at the end he is shot but unharmed, leaving a hole in his head and dimming his eyes. This is probably the most depressing and unacceptable manifestation of Marvin ever conceived, and thus paradoxically the most accurate.
</p>
</div>
<hr />
<div
className="content"
>
<h4>
Personality
</h4>
<p>
Marvin the robot has a prototype version of the Genuine People Personality (GPP) software from SCC, allowing him sentience and the ability to feel emotions and develop a personality. He's also incredibly smart, having a "brain the size of a planet" capable of computing extremely complex mathematics, as well as solving difficult problems and operating high-tech devices.
</p>
<p>
However, despite being so smart, Marvin is typically made to perform menial tasks and labour such as escorting people, opening doors, picking up pieces of paper, and other tasks well beneath his skills. Even extremely hard tasks, such as computing for the vast Krikkit robot army, are trivial for Marvin. All this leaves him extremely bored, frustrated, and overwhelmingly depressed. Because of this, all modern GPP-capable machines, such as Eddie the computer and the Heart of Gold's automatic doors, are programmed to be extremely cheerful and happy, much to Marvin's disgust.
</p>
<p>
Marvin hates everyone and everything he comes into contact with, having no respect for anybody and will criticise and insult others at any opportunity, or otherwise rant and complain for hours on end about his own problems, such as the terrible pain he suffers in all the diodes down his left side. His contempt for everyone is often justified, as almost every person he comes across, even those who consider him a friend, (such as Arthur and Trillian, who treat him more kindly than Ford and Zaphod) treat Marvin as an expendable servant, even sending him to his death more than once (such as when Zaphod ordered Marvin to fight the gigantic, heavy-duty Frogstar Scout Robot Class D so he could escape). Being a robot, he still does what he's told (he won't enjoy it, nor will he let you forget it, but he'll do it anyway), though he'd much rather sulk in a corner by himself.
</p>
<p>
Several times in the series Marvin ends up alone and isolated for extremely long periods of time, sometimes spanning millions of years, either by sheer bad luck (such as the explosion that propelled everyone but Marvin to Milliways in the far-off future) or because his unpleasantly depressing personality drives them away or, in more than one case, makes them commit suicide. In his spare time (which he has a lot of), Marvin will attempt to occupy himself by composing songs and writing poetry. Of course, none of them are particularly cheerful, or even that good.
</p>
</div>
</section>
</div>
</div>
`;
exports[`Storyshots Modal|Modal With form elements 1`] = `
<div
className="modal is-active"
>
<div
className="modal-background"
/>
<div
className="modal-card"
>
<header
className="modal-card-head has-background-light"
>
<p
className="modal-card-title is-marginless"
>
Hitchhiker Modal
</p>
<button
aria-label="close"
className="delete"
onClick={[Function]}
/>
</header>
<section
className="modal-card-body"
>
<div
className="Modalstories__RadioList-sc-2lb0wg-1 hFfBOw"
>
<label
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
>
<input
checked={true}
onChange={[Function]}
type="radio"
/>
One
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="The first one"
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
<label
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
>
<input
checked={false}
onChange={[Function]}
type="radio"
/>
Two
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="The second one"
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
</div>
<hr />
<p>
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
ordinary mob.
</p>
<hr />
<div
className="field"
>
<label
className="label"
>
Text
</label>
<div
className="control"
>
<textarea
className="textarea"
disabled={false}
onChange={[Function]}
onKeyDown={[Function]}
/>
</div>
</div>
<hr />
<div
className="field is-grouped"
>
<div
className="control"
>
<button
className="button is-default"
onClick={[Function]}
type="button"
>
One
</button>
</div>
<div
className="control"
>
<button
className="button is-default"
onClick={[Function]}
type="button"
>
Two
</button>
</div>
</div>
</section>
</div>
</div>
`;
exports[`Storyshots Modal|Modal With long tooltips 1`] = `
<div
className="modal is-active"
>
<div
className="modal-background"
/>
<div
className="modal-card"
>
<header
className="modal-card-head has-background-light"
>
<p
className="modal-card-title is-marginless"
>
Hitchhiker Modal
</p>
<button
aria-label="close"
className="delete"
onClick={[Function]}
/>
</header>
<section
className="modal-card-body"
>
<div
className="notification is-info"
>
This story exists because we had a problem, that long tooltips causes a horizontal scrollbar on the modal.
</div>
<hr />
<p>
The following elements will have a verly long help text, which has triggered the scrollbar in the past.
</p>
<hr />
<div
className="Modalstories__TopAndBottomMargin-sc-2lb0wg-0 bfocSI"
>
<div
className="field"
>
<div
className="control"
onClick={[Function]}
>
<label
className="checkbox"
>
<i
className="is-outlined fa-check-square has-text-link fa"
/>
Checkbox
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
ordinary mob."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
</div>
</div>
</div>
<hr />
<div
className="Modalstories__TopAndBottomMargin-sc-2lb0wg-0 bfocSI"
>
<label
className="Radio__StyledRadio-ays4vp-0 hrHCWE radio"
>
<input
checked={false}
onChange={[Function]}
type="radio"
/>
Radio button
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
ordinary mob."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
</div>
<hr />
<div
className="Modalstories__TopAndBottomMargin-sc-2lb0wg-0 bfocSI"
>
<div
className="field"
>
<label
className="label"
>
Input
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
ordinary mob."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
<div
className="control"
>
<input
className="input"
onChange={[Function]}
onKeyPress={[Function]}
placeholder=""
type="text"
/>
</div>
</div>
</div>
<hr />
<div
className="Modalstories__TopAndBottomMargin-sc-2lb0wg-0 bfocSI"
>
<div
className="field"
>
<label
className="label"
>
Textarea
<span
className="tooltip has-tooltip-right Help__HelpTooltip-ykmmew-0 cYhfno is-inline-block has-tooltip-multiline"
data-tooltip="Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
ordinary mob."
>
<i
className="fas fa-question-circle has-text-blue-light"
/>
</span>
</label>
<div
className="control"
>
<textarea
className="textarea"
disabled={false}
onChange={[Function]}
onKeyDown={[Function]}
/>
</div>
</div>
</div>
<hr />
<p>
If this modal has no horizontal scrollbar the issue is fixed
</p>
</section>
</div>

View File

@@ -42,4 +42,14 @@ storiesOf("Forms|Checkbox", module)
<Spacing>
<Checkbox label="Checked but disabled" checked={true} disabled={true} />
</Spacing>
))
.add("With HelpText", () => (
<Spacing>
<Checkbox label="Classic helpText" checked={false} helpText="This is a classic help text." />
<Checkbox
label="Long helpText"
checked={true}
helpText="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
/>
</Spacing>
));

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { ChangeEvent } from "react";
import React from "react";
import { Help } from "../index";
import LabelWithHelpIcon from "./LabelWithHelpIcon";
import TriStateCheckbox from "./TriStateCheckbox";

View File

@@ -30,6 +30,15 @@ const Spacing = styled.div`
padding: 2em;
`;
const RadioList = styled.div`
display: flex;
flex-direction: column;
> label:not(:last-child) {
margin-bottom: 0.75rem;
}
padding: 2em;
`;
storiesOf("Forms|Radio", module)
.add("Default", () => (
<Spacing>
@@ -41,4 +50,14 @@ storiesOf("Forms|Radio", module)
<Spacing>
<Radio label="Checked but disabled" checked={true} disabled={true} />
</Spacing>
))
.add("With HelpText", () => (
<RadioList>
<Radio label="Classic helpText" checked={false} helpText="This is a classic help text." />
<Radio
label="Long helpText"
checked={true}
helpText="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
/>
</RadioList>
));

View File

@@ -24,29 +24,190 @@
import { storiesOf } from "@storybook/react";
import { MemoryRouter } from "react-router-dom";
import * as React from "react";
import { useState } from "react";
import React, { useState, FC } from "react";
import Modal from "./Modal";
import Checkbox from "../forms/Checkbox";
import styled from "styled-components";
import ExternalLink from "../navigation/ExternalLink";
import { Radio, Textarea, InputField } from "../forms";
import { ButtonGroup, Button } from "../buttons";
import Notification from "../Notification";
const body = (
<p>
Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
const TopAndBottomMargin = styled.div`
margin: 0.75rem 0; // only for aesthetic reasons
`;
const RadioList = styled.div`
display: flex;
flex-direction: column;
> label:not(:last-child) {
margin-bottom: 0.6em;
}
`;
const text = `Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows
hostesss really oblong Infinite Improbability thing into the starship against which behavior accordance.with
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
ordinary mob.
</p>
);
ordinary mob.`;
// eslint-disable-next-line @typescript-eslint/no-empty-function
const doNothing = () => {};
storiesOf("Modal|Modal", module)
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.add("Default", () => <CloseableModal />);
.add("Default", () => (
<NonCloseableModal>
<p>{text}</p>
</NonCloseableModal>
))
.add("Closeable", () => (
<CloseableModal>
<p>{text}</p>
</CloseableModal>
))
.add("With form elements", () => (
<NonCloseableModal>
<RadioList>
<Radio label="One" checked={true} helpText="The first one" />
<Radio label="Two" checked={false} helpText="The second one" />
</RadioList>
<hr />
<p>{text}</p>
<hr />
<Textarea label="Text" onChange={doNothing} />
<hr />
<ButtonGroup>
<Button label="One" />
<Button label="Two" />
</ButtonGroup>
</NonCloseableModal>
))
.add("With long tooltips", () => {
return (
<NonCloseableModal>
<Notification type="info">
This story exists because we had a problem, that long tooltips causes a horizontal scrollbar on the modal.
</Notification>
<hr />
<p>The following elements will have a verly long help text, which has triggered the scrollbar in the past.</p>
<hr />
<TopAndBottomMargin>
<Checkbox label="Checkbox" checked={true} helpText={text} />
</TopAndBottomMargin>
<hr />
<TopAndBottomMargin>
<Radio label="Radio button" checked={false} helpText={text} />
</TopAndBottomMargin>
<hr />
<TopAndBottomMargin>
<InputField onChange={doNothing} label="Input" helpText={text} />
</TopAndBottomMargin>
<hr />
<TopAndBottomMargin>
<Textarea onChange={doNothing} label="Textarea" helpText={text} />
</TopAndBottomMargin>
<hr />
<p>If this modal has no horizontal scrollbar the issue is fixed</p>
</NonCloseableModal>
);
})
.add("Long content", () => (
<NonCloseableModal>
<h1 className="title">Marvin</h1>
<h2 className="subtitle">The Paranoid Android</h2>
<hr />
<Notification type="info">
The following content comes from the awesome{" "}
<ExternalLink to="https://hitchhikers.fandom.com/wiki/Main_Page">Hitchhikers Wiki</ExternalLink>
</Notification>
<hr />
<div className="has-text-centered">
<img
alt="Marvin"
src="https://vignette.wikia.nocookie.net/hitchhikers/images/a/a4/Marvin.jpg/revision/latest/scale-to-width-down/150?cb=20100530114055"
/>
</div>
<hr />
<p className="content">
Marvin, more fully known as Marvin the Paranoid Android, is an incredibly brilliant but overwhelmingly depressed
robot manufactured by the Sirius Cybernetics Corporation and unwilling servant to the crew of the Heart of Gold.
</p>
<hr />
<div className="content">
<h4>Physical Appearance</h4>
<p>
In the novels, Marvin is described thusly: "...though it was beautifully constructed and polished it looked
somehow as if the various parts of its more or less humanoid body didn't quite fit properly. In fact, they fit
perfectly well, but something in its bearing suggested that they might have fitted better."
</p>
<p>
On the radio show, there's no physical description of Marvin, though his voice is digitally altered to sound
more robotic, and any scene that focuses on him is accompanied by sounds of mechanical clanking and hissing.
</p>
<p>
In the TV series, Marvin is built in the style of a 1950's robot similar to Robbie the Robot from Forbidden
Planet or Twiki from Buck Rogers. His body is blocky and angular, with a pair of clamp-claw hands, shuffling
feet and a squarish head with a dour face.
</p>
<p>
In the movie, Marvin is a short, stout robot built of smooth, white plastic. His arms are much longer than his
legs, and his head is a massive sphere with only a pair of triangle eyes for a face. His large head and
simian-like proportions give Marvin a perpetual slouch, adding to his melancholy personality. At the start of
the film his eyes glow, but at the end he is shot but unharmed, leaving a hole in his head and dimming his
eyes. This is probably the most depressing and unacceptable manifestation of Marvin ever conceived, and thus
paradoxically the most accurate.
</p>
</div>
<hr />
<div className="content">
<h4>Personality</h4>
<p>
Marvin the robot has a prototype version of the Genuine People Personality (GPP) software from SCC, allowing
him sentience and the ability to feel emotions and develop a personality. He's also incredibly smart, having a
"brain the size of a planet" capable of computing extremely complex mathematics, as well as solving difficult
problems and operating high-tech devices.
</p>
<p>
However, despite being so smart, Marvin is typically made to perform menial tasks and labour such as escorting
people, opening doors, picking up pieces of paper, and other tasks well beneath his skills. Even extremely
hard tasks, such as computing for the vast Krikkit robot army, are trivial for Marvin. All this leaves him
extremely bored, frustrated, and overwhelmingly depressed. Because of this, all modern GPP-capable machines,
such as Eddie the computer and the Heart of Gold's automatic doors, are programmed to be extremely cheerful
and happy, much to Marvin's disgust.
</p>
<p>
Marvin hates everyone and everything he comes into contact with, having no respect for anybody and will
criticise and insult others at any opportunity, or otherwise rant and complain for hours on end about his own
problems, such as the terrible pain he suffers in all the diodes down his left side. His contempt for everyone
is often justified, as almost every person he comes across, even those who consider him a friend, (such as
Arthur and Trillian, who treat him more kindly than Ford and Zaphod) treat Marvin as an expendable servant,
even sending him to his death more than once (such as when Zaphod ordered Marvin to fight the gigantic,
heavy-duty Frogstar Scout Robot Class D so he could escape). Being a robot, he still does what he's told (he
won't enjoy it, nor will he let you forget it, but he'll do it anyway), though he'd much rather sulk in a
corner by himself.
</p>
<p>
Several times in the series Marvin ends up alone and isolated for extremely long periods of time, sometimes
spanning millions of years, either by sheer bad luck (such as the explosion that propelled everyone but Marvin
to Milliways in the far-off future) or because his unpleasantly depressing personality drives them away or, in
more than one case, makes them commit suicide. In his spare time (which he has a lot of), Marvin will attempt
to occupy himself by composing songs and writing poetry. Of course, none of them are particularly cheerful, or
even that good.
</p>
</div>
</NonCloseableModal>
));
const CloseableModal = () => {
const NonCloseableModal: FC = ({ children }) => {
return <Modal body={children} closeFunction={doNothing} active={true} title={"Hitchhiker Modal"} />;
};
const CloseableModal: FC = ({ children }) => {
const [show, setShow] = useState(true);
const toggleModal = () => {
setShow(!show);
};
return <Modal body={body} closeFunction={toggleModal} active={show} title={"Hitchhiker Modal"} />;
return <Modal body={children} closeFunction={toggleModal} active={show} title={"Hitchhiker Modal"} />;
};

View File

@@ -0,0 +1,84 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
import com.google.inject.OutOfScopeException;
import com.google.inject.ProvisionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.util.HttpUtil;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Optional;
/**
* Default implementation of {@link RootURL}.
*
* @since 2.3.1
*/
public class DefaultRootURL implements RootURL {
private static final Logger LOG = LoggerFactory.getLogger(DefaultRootURL.class);
private final Provider<HttpServletRequest> requestProvider;
private final ScmConfiguration configuration;
@Inject
public DefaultRootURL(Provider<HttpServletRequest> requestProvider, ScmConfiguration configuration) {
this.requestProvider = requestProvider;
this.configuration = configuration;
}
@Override
public URL get() {
String url = fromRequest().orElse(configuration.getBaseUrl());
if (url == null) {
throw new IllegalStateException("The configured base url is empty. This can only happened if SCM-Manager has not received any requests.");
}
try {
return new URL(url);
} catch (MalformedURLException e) {
throw new IllegalStateException(String.format("base url \"%s\" is malformed", url), e);
}
}
private Optional<String> fromRequest() {
try {
HttpServletRequest request = requestProvider.get();
return Optional.of(HttpUtil.getCompleteUrl(request));
} catch (ProvisionException ex) {
if (ex.getCause() instanceof OutOfScopeException) {
LOG.debug("could not find request, fall back to base url from configuration");
return Optional.empty();
}
throw ex;
}
}
}

View File

@@ -33,8 +33,10 @@ import com.google.inject.throwingproviders.ThrowingProviderBinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.Default;
import sonia.scm.DefaultRootURL;
import sonia.scm.PushStateDispatcher;
import sonia.scm.PushStateDispatcherProvider;
import sonia.scm.RootURL;
import sonia.scm.Undecorated;
import sonia.scm.api.rest.ObjectMapperProvider;
import sonia.scm.api.v2.resources.BranchLinkProvider;
@@ -239,6 +241,9 @@ class ScmServletModule extends ServletModule {
// bind api link provider
bind(BranchLinkProvider.class).to(DefaultBranchLinkProvider.class);
// bind url helper
bind(RootURL.class).to(DefaultRootURL.class);
}
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {

View File

@@ -0,0 +1,134 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
import com.google.inject.OutOfScopeException;
import com.google.inject.ProvisionException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.util.HttpUtil;
import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class DefaultRootURLTest {
private static final String URL_CONFIG = "https://hitchhiker.com/from-configuration";
private static final String URL_REQUEST = "https://hitchhiker.com/from-request";
@Mock
private Provider<HttpServletRequest> requestProvider;
@Mock
private HttpServletRequest request;
private ScmConfiguration configuration;
private RootURL rootURL;
@BeforeEach
void init() {
configuration = new ScmConfiguration();
rootURL = new DefaultRootURL(requestProvider, configuration);
}
@Test
void shouldUseRootURLFromRequest() {
bindRequestUrl();
assertThat(rootURL.getAsString()).isEqualTo(URL_REQUEST);
}
private void bindRequestUrl() {
when(requestProvider.get()).thenReturn(request);
when(request.getRequestURL()).thenReturn(new StringBuffer(URL_REQUEST));
when(request.getRequestURI()).thenReturn("/from-request");
when(request.getContextPath()).thenReturn("/from-request");
}
@Test
void shouldUseRootURLFromConfiguration() {
bindNonHttpScope();
configuration.setBaseUrl(URL_CONFIG);
assertThat(rootURL.getAsString()).isEqualTo(URL_CONFIG);
}
private void bindNonHttpScope() {
when(requestProvider.get()).thenThrow(
new ProvisionException("no request available", new OutOfScopeException("out of scope"))
);
}
@Test
void shouldThrowNonOutOfScopeProvisioningExceptions() {
when(requestProvider.get()).thenThrow(
new ProvisionException("something ugly happened", new IllegalStateException("some wrong state"))
);
assertThrows(ProvisionException.class, () -> rootURL.get());
}
@Test
void shouldThrowIllegalStateExceptionForMalformedBaseUrl() {
bindNonHttpScope();
configuration.setBaseUrl("non_url");
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> rootURL.get());
assertThat(exception.getMessage()).contains("malformed", "non_url");
assertThat(exception.getCause()).isInstanceOf(MalformedURLException.class);
}
@Test
void shouldThrowIllegalStateExceptionIfBaseURLIsNotConfigured() {
bindNonHttpScope();
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> rootURL.get());
assertThat(exception.getMessage()).contains("empty");
}
@Test
void shouldUseRootURLFromForwardedRequest() {
bindForwardedRequestUrl();
assertThat(rootURL.get()).hasHost("hitchhiker.com");
}
private void bindForwardedRequestUrl() {
when(requestProvider.get()).thenReturn(request);
when(request.getHeader(HttpUtil.HEADER_X_FORWARDED_HOST)).thenReturn("hitchhiker.com");
when(request.getScheme()).thenReturn("https");
when(request.getServerPort()).thenReturn(443);
when(request.getContextPath()).thenReturn("/from-request");
}
}

224
yarn.lock
View File

@@ -985,9 +985,9 @@
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.1":
version "7.10.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c"
integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==
version "7.11.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.0.tgz#f10245877042a815e07f7e693faff0ae9d3a2aac"
integrity sha512-qArkXsjJq7H+T86WrIFV0Fnu/tNOkZ4cgXmjkzAu3b/58D5mFIO8JH/y77t7C9q0OdDRdh9s7Ue5GasYssxtXw==
dependencies:
regenerator-runtime "^0.13.4"
@@ -3781,12 +3781,17 @@ ansi-colors@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-escapes@^3.0.0, ansi-escapes@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
ansi-escapes@^4.2.1:
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
@@ -4084,6 +4089,11 @@ astral-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
async-each@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
@@ -5162,7 +5172,7 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0:
chalk@^4.0.0, chalk@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
@@ -5327,6 +5337,14 @@ cli-table3@0.5.1:
optionalDependencies:
colors "^1.1.2"
cli-truncate@2.1.0, cli-truncate@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
dependencies:
slice-ansi "^3.0.0"
string-width "^4.2.0"
cli-width@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
@@ -5501,6 +5519,11 @@ commander@^4.0.1, commander@^4.1.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
commander@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -5514,6 +5537,11 @@ compare-func@^1.3.1:
array-ify "^1.0.0"
dot-prop "^3.0.0"
compare-versions@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@@ -6780,6 +6808,13 @@ enhanced-resolve@^4.1.0:
memory-fs "^0.5.0"
tapable "^1.0.0"
enquirer@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
dependencies:
ansi-colors "^4.1.1"
entities@^1.1.1, entities@^1.1.2, entities@~1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
@@ -7320,6 +7355,21 @@ execa@^4.0.0:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
execa@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
dependencies:
cross-spawn "^7.0.0"
get-stream "^5.0.0"
human-signals "^1.1.1"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.0"
onetime "^5.1.0"
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
exit@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -7560,7 +7610,7 @@ figures@^2.0.0:
dependencies:
escape-string-regexp "^1.0.5"
figures@^3.0.0:
figures@^3.0.0, figures@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
@@ -7684,6 +7734,13 @@ find-up@^4.0.0, find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
find-versions@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e"
integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==
dependencies:
semver-regex "^2.0.0"
findup-sync@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
@@ -7923,6 +7980,11 @@ get-caller-file@^2.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
get-package-type@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
@@ -8644,6 +8706,22 @@ humanize-ms@^1.2.1:
dependencies:
ms "^2.0.0"
husky@^4.2.5:
version "4.2.5"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==
dependencies:
chalk "^4.0.0"
ci-info "^2.0.0"
compare-versions "^3.6.0"
cosmiconfig "^6.0.0"
find-versions "^3.2.0"
opencollective-postinstall "^2.0.2"
pkg-dir "^4.2.0"
please-upgrade-node "^3.2.0"
slash "^3.0.0"
which-pm-runs "^1.0.0"
i18next-browser-languagedetector@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.2.0.tgz#82e35d31f88a1d7c2b6d5913bf8c8481cd40aafb"
@@ -9209,7 +9287,7 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-obj@^1.0.0:
is-obj@^1.0.0, is-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
@@ -9274,6 +9352,11 @@ is-regex@^1.0.4, is-regex@^1.0.5:
dependencies:
has "^1.0.3"
is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
is-resolvable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
@@ -10601,6 +10684,41 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
lint-staged@^10.2.11:
version "10.2.11"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720"
integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==
dependencies:
chalk "^4.0.0"
cli-truncate "2.1.0"
commander "^5.1.0"
cosmiconfig "^6.0.0"
debug "^4.1.1"
dedent "^0.7.0"
enquirer "^2.3.5"
execa "^4.0.1"
listr2 "^2.1.0"
log-symbols "^4.0.0"
micromatch "^4.0.2"
normalize-path "^3.0.0"
please-upgrade-node "^3.2.0"
string-argv "0.3.1"
stringify-object "^3.3.0"
listr2@^2.1.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.4.1.tgz#006fc94ae77b3195403cbf3a4a563e2d6366224f"
integrity sha512-8pYsCZCztr5+KAjReLyBeGhLV0vaQ2Du/eMe/ux9QAfQl7efiWejM1IWjALh0zHIRYuIbhQ8N2KztZ4ci56pnQ==
dependencies:
chalk "^4.1.0"
cli-truncate "^2.1.0"
figures "^3.2.0"
indent-string "^4.0.0"
log-update "^4.0.0"
p-map "^4.0.0"
rxjs "^6.6.0"
through "^2.3.8"
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -10803,6 +10921,23 @@ lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
log-symbols@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
dependencies:
chalk "^4.0.0"
log-update@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
dependencies:
ansi-escapes "^4.3.0"
cli-cursor "^3.1.0"
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
loglevel@^1.6.8:
version "1.6.8"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
@@ -11933,6 +12068,11 @@ open@^7.0.0:
is-docker "^2.0.0"
is-wsl "^2.1.1"
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
opn@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
@@ -12100,6 +12240,13 @@ p-map@^3.0.0:
dependencies:
aggregate-error "^3.0.0"
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
dependencies:
aggregate-error "^3.0.0"
p-pipe@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9"
@@ -12462,6 +12609,13 @@ pkg-up@2.0.0, pkg-up@^2.0.0:
dependencies:
find-up "^2.1.0"
please-upgrade-node@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
dependencies:
semver-compare "^1.0.0"
pn@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
@@ -14230,6 +14384,13 @@ rxjs@^6.4.0, rxjs@^6.5.3:
dependencies:
tslib "^1.9.0"
rxjs@^6.6.0:
version "6.6.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2"
integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -14351,6 +14512,16 @@ selfsigned@^1.10.7:
dependencies:
node-forge "0.9.0"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
semver-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338"
integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -14611,6 +14782,24 @@ slice-ansi@^2.1.0:
astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
slice-ansi@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
dependencies:
ansi-styles "^4.0.0"
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
slice-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
dependencies:
ansi-styles "^4.0.0"
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
slide@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
@@ -14956,6 +15145,11 @@ strict-uri-encode@^1.0.0:
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
string-argv@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-length@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
@@ -15092,6 +15286,15 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
stringify-object@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
dependencies:
get-own-enumerable-property-symbols "^3.0.0"
is-obj "^1.0.1"
is-regexp "^1.0.0"
strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
@@ -15488,7 +15691,7 @@ through2@^3.0.0:
dependencies:
readable-stream "2 || 3"
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6:
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -16440,6 +16643,11 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which-pm-runs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"