diff --git a/CHANGELOG.md b/CHANGELOG.md
index eb72250afd..f69732ae4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Generation of email addresses for users, where none is configured ([#1370](https://github.com/scm-manager/scm-manager/pull/1370))
- Automatic user converter for external users ([#1380](https://github.com/scm-manager/scm-manager/pull/1380))
+- Source code fullscreen view ([#1376](https://github.com/scm-manager/scm-manager/pull/1376))
+
+## [2.6.3] - 2020-10-16
+### Fixed
+- Missing default permission to manage public gpg keys ([#1377](https://github.com/scm-manager/scm-manager/pull/1377))
## [2.7.1] - 2020-10-14
### Fixed
@@ -363,3 +368,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[2.6.0]: https://www.scm-manager.org/download/2.6.0
[2.6.1]: https://www.scm-manager.org/download/2.6.1
[2.6.2]: https://www.scm-manager.org/download/2.6.2
+[2.6.3]: https://www.scm-manager.org/download/2.6.3
+[2.7.0]: https://www.scm-manager.org/download/2.7.0
+[2.7.1]: https://www.scm-manager.org/download/2.7.1
diff --git a/Jenkinsfile b/Jenkinsfile
index 2ef5929e09..c6dc70b8d7 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -7,7 +7,8 @@ import com.cloudogu.ces.cesbuildlib.*
node('docker') {
- mainBranch = 'develop'
+ developmentBranch = 'develop'
+ mainBranch = 'master'
properties([
// Keep only the last 10 build to preserve space
@@ -51,9 +52,9 @@ node('docker') {
sh "git config 'remote.origin.fetch' '+refs/heads/*:refs/remotes/origin/*'"
sh "git fetch --all"
- // merge release branch into master
- sh "git checkout master"
- sh "git reset --hard origin/master"
+ // merge release branch into main branch
+ sh "git checkout ${mainBranch}"
+ sh "git reset --hard origin/${mainBranch}"
sh "git merge --ff-only ${env.BRANCH_NAME}"
// set tag
@@ -87,7 +88,7 @@ node('docker') {
sonarQube.analyzeWith(mvn)
}
- if (isBuildSuccessful() && (isMainBranch() || isReleaseBranch())) {
+ if (isBuildSuccessful() && (isDevelopmentBranch() || isReleaseBranch())) {
def commitHash = git.getCommitHash()
def imageVersion = mvn.getVersion()
@@ -143,20 +144,28 @@ node('docker') {
}
stage('Presentation Environment') {
- build job: 'scm-manager/next-scm.cloudogu.com', propagate: false, wait: false, parameters: [
- string(name: 'changeset', value: commitHash),
- string(name: 'imageTag', value: imageVersion)
- ]
+ // we don't use developmentBranch, because we only want the lastest version of develop branch on
+ // next-scm. We don't want a support branch or something similar on the presentation environment.
+ if ("develop".equals(env.BRANCH_NAME)) {
+ build job: 'scm-manager/next-scm.cloudogu.com', propagate: false, wait: false, parameters: [
+ string(name: 'changeset', value: commitHash),
+ string(name: 'imageTag', value: imageVersion)
+ ]
+ }
}
if (isReleaseBranch()) {
stage('Update Repository') {
// merge changes into develop
- sh "git checkout develop"
+ sh "git checkout ${developmentBranch}"
+
// TODO what if we have a conflict
// e.g.: someone has edited the changelog during the release
- sh "git merge master"
+ if (!developmentBranch.equals(mainBranch)) {
+ sh "git merge ${mainBranch}"
+ }
+
// set versions for maven packages
mvn "build-helper:parse-version versions:set -DgenerateBackupPoms=false -DnewVersion='\${parsedVersion.majorVersion}.\${parsedVersion.nextMinorVersion}.0-SNAPSHOT'"
@@ -176,8 +185,10 @@ node('docker') {
// push changes back to remote repository
withCredentials([usernamePassword(credentialsId: 'cesmarvin-github', usernameVariable: 'GIT_AUTH_USR', passwordVariable: 'GIT_AUTH_PSW')]) {
- sh "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" push origin master --tags"
- sh "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" push origin develop --tags"
+ sh "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" push origin ${mainBranch} --tags"
+ if (!developmentBranch.equals(mainBranch)) {
+ sh "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" push origin develop --tags"
+ }
sh "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" push origin :${env.BRANCH_NAME}"
}
}
@@ -189,6 +200,7 @@ node('docker') {
}
}
+String developmentBranch
String mainBranch
Maven setupMavenBuild() {
@@ -201,7 +213,7 @@ Maven setupMavenBuild() {
mvn.additionalArgs += " -Dscm-it.logbackConfiguration=${logConf}"
mvn.additionalArgs += " -Dsonar.coverage.exclusions=**/*.test.ts,**/*.test.tsx,**/*.stories.tsx"
- if (isMainBranch() || isReleaseBranch()) {
+ if (isDevelopmentBranch() || isReleaseBranch()) {
// Release starts javadoc, which takes very long, so do only for certain branches
mvn.additionalArgs += ' -DperformRelease'
// JDK8 is more strict, we should fix this before the next release. Right now, this is just not the focus, yet.
@@ -218,8 +230,8 @@ String getReleaseVersion() {
return env.BRANCH_NAME.substring("release/".length());
}
-boolean isMainBranch() {
- return mainBranch.equals(env.BRANCH_NAME)
+boolean isDevelopmentBranch() {
+ return developmentBranch.equals(env.BRANCH_NAME)
}
void withGPGEnvironment(def closure) {
diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap
index eec24b0966..55f0977dc9 100644
--- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap
+++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap
@@ -3951,6 +3951,23 @@ exports[`Storyshots Diff Binaries 1`] = `
+
@@ -4219,6 +4236,23 @@ exports[`Storyshots Diff Collapsed 1`] = `
+
@@ -4315,6 +4349,23 @@ exports[`Storyshots Diff Collapsed 1`] = `
+
@@ -4411,6 +4462,23 @@ exports[`Storyshots Diff Collapsed 1`] = `
+
@@ -4507,6 +4575,23 @@ exports[`Storyshots Diff Collapsed 1`] = `
+
@@ -4603,6 +4688,23 @@ exports[`Storyshots Diff Collapsed 1`] = `
+
@@ -4699,6 +4801,23 @@ exports[`Storyshots Diff Collapsed 1`] = `
+
@@ -4804,6 +4923,23 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
+
@@ -4862,6 +4998,23 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
+
@@ -5703,6 +5856,23 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
+
@@ -6140,6 +6310,23 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
+
@@ -6577,6 +6764,23 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
+
@@ -6635,6 +6839,23 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
+
@@ -6702,6 +6923,23 @@ exports[`Storyshots Diff Default 1`] = `
+
@@ -7266,6 +7504,23 @@ exports[`Storyshots Diff Default 1`] = `
+
@@ -8107,6 +8362,23 @@ exports[`Storyshots Diff Default 1`] = `
+
@@ -8544,6 +8816,23 @@ exports[`Storyshots Diff Default 1`] = `
+
@@ -8981,6 +9270,23 @@ exports[`Storyshots Diff Default 1`] = `
+
@@ -10025,6 +10331,23 @@ exports[`Storyshots Diff Default 1`] = `
+
@@ -10546,6 +10869,23 @@ exports[`Storyshots Diff Expandable 1`] = `
+
@@ -11147,6 +11487,23 @@ exports[`Storyshots Diff Expandable 1`] = `
+
@@ -12085,6 +12442,23 @@ exports[`Storyshots Diff Expandable 1`] = `
+
@@ -12594,6 +12968,23 @@ exports[`Storyshots Diff Expandable 1`] = `
+
@@ -13103,6 +13494,23 @@ exports[`Storyshots Diff Expandable 1`] = `
+
@@ -14302,6 +14710,23 @@ exports[`Storyshots Diff Expandable 1`] = `
+
@@ -14860,6 +15285,23 @@ exports[`Storyshots Diff File Annotation 1`] = `
+
@@ -15428,6 +15870,23 @@ exports[`Storyshots Diff File Annotation 1`] = `
+
@@ -16273,6 +16732,23 @@ exports[`Storyshots Diff File Annotation 1`] = `
+
@@ -16714,6 +17190,23 @@ exports[`Storyshots Diff File Annotation 1`] = `
+
@@ -17155,6 +17648,23 @@ exports[`Storyshots Diff File Annotation 1`] = `
+
@@ -18203,6 +18713,23 @@ exports[`Storyshots Diff File Annotation 1`] = `
+
@@ -18728,6 +19255,23 @@ exports[`Storyshots Diff File Controls 1`] = `
+
@@ -19310,6 +19854,23 @@ exports[`Storyshots Diff File Controls 1`] = `
+
@@ -20169,6 +20730,23 @@ exports[`Storyshots Diff File Controls 1`] = `
+
@@ -20624,6 +21202,23 @@ exports[`Storyshots Diff File Controls 1`] = `
+
@@ -21079,6 +21674,23 @@ exports[`Storyshots Diff File Controls 1`] = `
+
@@ -22141,6 +22753,23 @@ exports[`Storyshots Diff File Controls 1`] = `
+
@@ -22680,6 +23309,23 @@ exports[`Storyshots Diff Hunks 1`] = `
+
@@ -23508,6 +24154,23 @@ exports[`Storyshots Diff Line Annotation 1`] = `
+
@@ -24084,6 +24747,23 @@ exports[`Storyshots Diff Line Annotation 1`] = `
+
@@ -24937,6 +25617,23 @@ exports[`Storyshots Diff Line Annotation 1`] = `
+
@@ -25374,6 +26071,23 @@ exports[`Storyshots Diff Line Annotation 1`] = `
+
@@ -25811,6 +26525,23 @@ exports[`Storyshots Diff Line Annotation 1`] = `
+
@@ -26855,6 +27586,23 @@ exports[`Storyshots Diff Line Annotation 1`] = `
+
@@ -27388,6 +28136,23 @@ exports[`Storyshots Diff OnClick 1`] = `
+
@@ -27992,6 +28757,23 @@ exports[`Storyshots Diff OnClick 1`] = `
+
@@ -28895,6 +29677,23 @@ exports[`Storyshots Diff OnClick 1`] = `
+
@@ -29362,6 +30161,23 @@ exports[`Storyshots Diff OnClick 1`] = `
+
@@ -29829,6 +30645,23 @@ exports[`Storyshots Diff OnClick 1`] = `
+
@@ -30949,6 +31782,23 @@ exports[`Storyshots Diff OnClick 1`] = `
+
@@ -31506,6 +32356,23 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
+
@@ -32163,6 +33030,23 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
+
@@ -33094,6 +33978,23 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
+
@@ -33583,6 +34484,23 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
+
@@ -34072,6 +34990,23 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
+
@@ -35285,6 +36220,23 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
+
@@ -35879,6 +36831,23 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
+
@@ -36443,6 +37412,23 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
+
@@ -37284,6 +38270,23 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
+
@@ -37721,6 +38724,23 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
+
@@ -38158,6 +39178,23 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
+
@@ -39202,6 +40239,23 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
+
@@ -39723,6 +40777,23 @@ exports[`Storyshots Diff WithLinkToFile 1`] = `
+
@@ -40324,6 +41395,23 @@ exports[`Storyshots Diff WithLinkToFile 1`] = `
+
@@ -41262,6 +42350,23 @@ exports[`Storyshots Diff WithLinkToFile 1`] = `
+
@@ -41771,6 +42876,23 @@ exports[`Storyshots Diff WithLinkToFile 1`] = `
+
@@ -42280,6 +43402,23 @@ exports[`Storyshots Diff WithLinkToFile 1`] = `
+
@@ -43479,6 +44618,23 @@ exports[`Storyshots Diff WithLinkToFile 1`] = `
+
diff --git a/scm-ui/ui-components/src/buttons/OpenInFullscreenButton.tsx b/scm-ui/ui-components/src/buttons/OpenInFullscreenButton.tsx
new file mode 100644
index 0000000000..c1cd1c6da2
--- /dev/null
+++ b/scm-ui/ui-components/src/buttons/OpenInFullscreenButton.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 * as React from "react";
+import { FC, ReactNode, useState } from "react";
+import { useTranslation } from "react-i18next";
+import styled from "styled-components";
+import FullscreenModal from "../modals/FullscreenModal";
+import Tooltip from "../Tooltip";
+
+type Props = {
+ modalTitle: string;
+ modalBody: ReactNode;
+ tooltipStyle?: "tooltipComponent" | "htmlTitle";
+};
+
+const Button = styled.a`
+ width: 50px;
+ &:hover {
+ color: #33b2e8;
+ }
+`;
+
+const OpenInFullscreenButton: FC = ({ modalTitle, modalBody, tooltipStyle = "tooltipComponent" }) => {
+ const [t] = useTranslation("repos");
+ const [showModal, setShowModal] = useState(false);
+
+ const tooltip = t("diff.fullscreen.open");
+ const content = (
+ <>
+ setShowModal(true)}
+ >
+
+
+ {showModal && (
+ setShowModal(false)}
+ body={modalBody}
+ active={showModal}
+ />
+ )}
+ >
+ );
+
+ if (tooltipStyle === "htmlTitle") {
+ return <>{content}>;
+ }
+ return (
+
+ {content}
+
+ );
+};
+
+export default OpenInFullscreenButton;
diff --git a/scm-ui/ui-components/src/buttons/index.ts b/scm-ui/ui-components/src/buttons/index.ts
index 3949b333fe..cc7307572d 100644
--- a/scm-ui/ui-components/src/buttons/index.ts
+++ b/scm-ui/ui-components/src/buttons/index.ts
@@ -33,4 +33,5 @@ export { default as SubmitButton } from "./SubmitButton";
export { default as DownloadButton } from "./DownloadButton";
export { default as ButtonGroup } from "./ButtonGroup";
export { default as ButtonAddons } from "./ButtonAddons";
+export { default as OpenInFullscreenButton } from "./OpenInFullscreenButton";
export { default as RemoveEntryOfTableButton } from "./RemoveEntryOfTableButton";
diff --git a/scm-ui/ui-components/src/modals/FullscreenModal.tsx b/scm-ui/ui-components/src/modals/FullscreenModal.tsx
new file mode 100644
index 0000000000..72951da5ca
--- /dev/null
+++ b/scm-ui/ui-components/src/modals/FullscreenModal.tsx
@@ -0,0 +1,52 @@
+/*
+ * 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 * as React from "react";
+import { FC, ReactNode } from "react";
+import { useTranslation } from "react-i18next";
+import { Modal } from "./Modal";
+import Button from "../buttons/Button";
+import styled from "styled-components";
+
+type Props = {
+ title: string;
+ closeFunction: () => void;
+ body: ReactNode;
+ active: boolean;
+};
+
+const FullSizedModal = styled(Modal)`
+ & .modal-card {
+ width: 98%;
+ max-height: 97vh;
+ }
+`;
+
+const FullscreenModal: FC = ({ title, closeFunction, body, active }) => {
+ const [t] = useTranslation("repos");
+ const footer = ;
+
+ return ;
+};
+
+export default FullscreenModal;
diff --git a/scm-ui/ui-components/src/modals/Modal.tsx b/scm-ui/ui-components/src/modals/Modal.tsx
index 87badb5d4b..21825e5c75 100644
--- a/scm-ui/ui-components/src/modals/Modal.tsx
+++ b/scm-ui/ui-components/src/modals/Modal.tsx
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
import * as React from "react";
-import {FC} from "react";
+import { FC } from "react";
import classNames from "classnames";
import usePortalRootElement from "../usePortalRootElement";
import ReactDOM from "react-dom";
@@ -53,7 +53,7 @@ export const Modal: FC = ({ title, closeFunction, body, footer, active, c
const modalElement = (
-
+
{title}
diff --git a/scm-ui/ui-components/src/modals/index.ts b/scm-ui/ui-components/src/modals/index.ts
index 8b79102558..3487d548a3 100644
--- a/scm-ui/ui-components/src/modals/index.ts
+++ b/scm-ui/ui-components/src/modals/index.ts
@@ -26,3 +26,4 @@
export { default as ConfirmAlert, confirmAlert } from "./ConfirmAlert";
export { default as Modal } from "./Modal";
+export { default as FullscreenModal } from "./FullscreenModal";
diff --git a/scm-ui/ui-components/src/repos/DiffFile.tsx b/scm-ui/ui-components/src/repos/DiffFile.tsx
index b4ec64a8e8..e98d6fc39f 100644
--- a/scm-ui/ui-components/src/repos/DiffFile.tsx
+++ b/scm-ui/ui-components/src/repos/DiffFile.tsx
@@ -33,7 +33,7 @@ import Icon from "../Icon";
import { Change, ChangeEvent, DiffObjectProps, File, Hunk as HunkType } from "./DiffTypes";
import TokenizedDiffView from "./TokenizedDiffView";
import DiffButton from "./DiffButton";
-import { MenuContext } from "@scm-manager/ui-components";
+import { MenuContext, OpenInFullscreenButton } from "@scm-manager/ui-components";
import DiffExpander, { ExpandableHunk } from "./DiffExpander";
import HunkExpandLink from "./HunkExpandLink";
import { Modal } from "../modals";
@@ -91,6 +91,15 @@ const ChangeTypeTag = styled(Tag)`
margin-left: 0.75rem;
`;
+const MarginlessModalContent = styled.div`
+ margin: -1.25rem;
+
+ & .panel-block {
+ flex-direction: column;
+ align-items: stretch;
+ }
+`;
+
class DiffFile extends React.Component {
static defaultProps: Partial = {
defaultCollapse: false,
@@ -406,27 +415,34 @@ class DiffFile extends React.Component {
const { file, collapsed, sideBySide, diffExpander, expansionError } = this.state;
const viewType = sideBySide ? "split" : "unified";
- let body = null;
+ const fileAnnotations = fileAnnotationFactory ? fileAnnotationFactory(file) : null;
+ const innerContent = (
+
+ {fileAnnotations}
+
+ {(hunks: HunkType[]) =>
+ hunks?.map((hunk, n) => {
+ return this.renderHunk(file, diffExpander.getHunk(n), n);
+ })
+ }
+
+
+ );
let icon = "angle-right";
+ let body = null;
if (!collapsed) {
- const fileAnnotations = fileAnnotationFactory ? fileAnnotationFactory(file) : null;
icon = "angle-down";
- body = (
-
- {fileAnnotations}
-
- {(hunks: HunkType[]) =>
- hunks?.map((hunk, n) => {
- return this.renderHunk(file, diffExpander.getHunk(n), n);
- })
- }
-
-
- );
+ body = innerContent;
}
const collapseIcon = this.hasContent(file) ? : null;
const fileControls = fileControlFactory ? fileControlFactory(file, this.setCollapse) : null;
- const sideBySideToggle = file.hunks && file.hunks.length && (
+ const openInFullscreen = file?.hunks?.length ? (
+ {innerContent}}
+ />
+ ) : null;
+ const sideBySideToggle = file?.hunks?.length && (
{({ setCollapsed }) => (
{
{sideBySideToggle}
+ {openInFullscreen}
{fileControls}
@@ -465,7 +482,11 @@ class DiffFile extends React.Component {
}
return (
-
+
{errorModal}
diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json
index ae0f222765..d3ec72b23f 100644
--- a/scm-ui/ui-webapp/public/locales/de/repos.json
+++ b/scm-ui/ui-webapp/public/locales/de/repos.json
@@ -269,7 +269,11 @@
"expandLastBottomByLines": "Bis zu {{count}} weitere Zeilen laden",
"expandLastBottomComplete": "Alle verbleibenden Zeilen laden",
"expanding": "Zeilen werden geladen ...",
- "expansionFailed": "Fehler beim Laden der zusätzlichen Zeilen"
+ "expansionFailed": "Fehler beim Laden der zusätzlichen Zeilen",
+ "fullscreen": {
+ "open": "In Vollbildansicht öffnen",
+ "close": "Schließen"
+ }
},
"fileUpload": {
"clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.",
diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json
index 3b05c1f48c..5f83ad7ba1 100644
--- a/scm-ui/ui-webapp/public/locales/en/repos.json
+++ b/scm-ui/ui-webapp/public/locales/en/repos.json
@@ -276,7 +276,11 @@
"expandLastBottomByLines": "load up to {{count}} more lines",
"expandLastBottomComplete": "load all remaining lines",
"expanding": "loading lines ...",
- "expansionFailed": "Error while loading additional lines"
+ "expansionFailed": "Error while loading additional lines",
+ "fullscreen": {
+ "open": "Open in Fullscreen",
+ "close": "Close"
+ }
},
"fileUpload": {
"clickHere": "Click here to select your file",
diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/SourcecodeViewer.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/SourcecodeViewer.tsx
index 313e4c9ac8..2eae04c8a6 100644
--- a/scm-ui/ui-webapp/src/repos/sources/components/content/SourcecodeViewer.tsx
+++ b/scm-ui/ui-webapp/src/repos/sources/components/content/SourcecodeViewer.tsx
@@ -21,87 +21,47 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-import React from "react";
-import { WithTranslation, withTranslation } from "react-i18next";
+import React, { FC, useEffect, useState } from "react";
import { apiClient, ErrorNotification, Loading, SyntaxHighlighter } from "@scm-manager/ui-components";
import { File, Link } from "@scm-manager/ui-types";
-type Props = WithTranslation & {
+type Props = {
file: File;
language: string;
};
-type State = {
- content: string;
- error?: Error;
- loaded: boolean;
- currentFileRevision: string;
-};
+const SourcecodeViewer: FC = ({ file, language }) => {
+ const [content, setContent] = useState("");
+ const [error, setError] = useState(undefined);
+ const [loading, setLoading] = useState(true);
+ const [currentFileRevision, setCurrentFileRevision] = useState("");
-class SourcecodeViewer extends React.Component {
- constructor(props: Props) {
- super(props);
-
- this.state = {
- content: "",
- loaded: false,
- currentFileRevision: ""
- };
- }
-
- componentDidMount() {
- this.fetchContentIfChanged();
- }
-
- componentDidUpdate() {
- this.fetchContentIfChanged();
- }
-
- private fetchContentIfChanged() {
- const { file } = this.props;
- const { currentFileRevision } = this.state;
+ useEffect(() => {
if (file.revision !== currentFileRevision) {
- this.fetchContent();
+ getContent((file._links.self as Link).href)
+ .then(content => {
+ setContent(content);
+ setCurrentFileRevision(file.revision);
+ setLoading(false);
+ })
+ .catch(setError);
}
+ }, [currentFileRevision, file]);
+
+ if (error) {
+ return ;
}
- fetchContent = () => {
- const { file } = this.props;
- getContent((file._links.self as Link).href)
- .then(content => {
- this.setState({
- content,
- loaded: true,
- currentFileRevision: file.revision
- });
- })
- .catch(error => {
- this.setState({
- error,
- loaded: true
- });
- });
- };
-
- render() {
- const { content, error, loaded } = this.state;
- const language = this.props.language;
-
- if (error) {
- return ;
- }
-
- if (!loaded) {
- return ;
- }
-
- if (!content) {
- return null;
- }
-
- return ;
+ if (loading) {
+ return ;
}
-}
+
+ if (!content) {
+ return null;
+ }
+
+ return ;
+};
export function getLanguage(language: string) {
return language.toLowerCase();
@@ -111,4 +71,4 @@ export function getContent(url: string) {
return apiClient.get(url).then(response => response.text());
}
-export default withTranslation("repos")(SourcecodeViewer);
+export default SourcecodeViewer;
diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx
index 43ecf40e2b..03f2d6dc84 100644
--- a/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx
+++ b/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx
@@ -21,14 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-import React from "react";
+import React, { ReactNode } from "react";
import { connect } from "react-redux";
import { WithTranslation, withTranslation } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { File, Repository } from "@scm-manager/ui-types";
-import { DateFromNow, ErrorNotification, FileSize, Icon } from "@scm-manager/ui-components";
+import { DateFromNow, ErrorNotification, FileSize, Icon, OpenInFullscreenButton } from "@scm-manager/ui-components";
import { getSources } from "../modules/sources";
import FileButtonAddons from "../components/content/FileButtonAddons";
import SourcesView from "./SourcesView";
@@ -82,6 +82,12 @@ const LighterGreyBackgroundTable = styled.table`
background-color: #fbfbfb;
`;
+const BorderLessDiv = styled.div`
+ margin: -1.25rem;
+ border: none;
+ box-shadow: none;
+`;
+
export type SourceViewSelection = "source" | "history" | "annotations";
class Content extends React.Component {
@@ -106,7 +112,7 @@ class Content extends React.Component {
});
};
- showHeader() {
+ showHeader(content: ReactNode) {
const { repository, file, revision } = this.props;
const { selected, collapsed } = this.state;
const icon = collapsed ? "angle-right" : "angle-down";
@@ -129,6 +135,11 @@ class Content extends React.Component {
{selector}
+
{content}}
+ tooltipStyle="htmlTitle"
+ />
{
const { file, revision, repository, path, breadcrumb } = this.props;
const { selected, errorFromExtension } = this.state;
- const header = this.showHeader();
let content;
switch (selected) {
case "source":
- content = (
-
- );
+ content = ;
break;
case "history":
- content = (
-
- );
+ content = ;
break;
case "annotations":
- content = (
-
- );
+ content = ;
}
+ const header = this.showHeader(content);
const moreInformation = this.showMoreInformation();
return (
diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
index b924769b6c..fcd54bac7d 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
@@ -251,6 +251,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
builder.add(getGroupAutocompletePermission());
builder.add(getChangeOwnPasswordPermission(user));
builder.add(getApiKeyPermission(user));
+ builder.add(getPublicKeyPermission(user));
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(ImmutableSet.of(Role.USER));
@@ -267,6 +268,10 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
return UserPermissions.changePassword(user).asShiroString();
}
+ private String getPublicKeyPermission(User user) {
+ return UserPermissions.changePublicKeys(user).asShiroString();
+ }
+
private String getApiKeyPermission(User user) {
return UserPermissions.changeApiKeys(user).asShiroString();
}
diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java
index 93cce78932..16015187bd 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java
@@ -167,8 +167,8 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.contains(Role.USER));
- assertThat(authInfo.getStringPermissions(), hasSize(5));
- assertThat(authInfo.getStringPermissions(), containsInAnyOrder("user:autocomplete", "group:autocomplete", "user:changePassword:trillian", "user:read:trillian", "user:changeApiKeys:trillian"));
+ assertThat(authInfo.getStringPermissions(), hasSize(6));
+ assertThat(authInfo.getStringPermissions(), containsInAnyOrder("user:autocomplete", "group:autocomplete", "user:changePassword:trillian", "user:read:trillian", "user:changeApiKeys:trillian", "user:changePublicKeys:trillian"));
assertThat(authInfo.getObjectPermissions(), nullValue());
}
@@ -212,7 +212,7 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
assertThat(authInfo.getObjectPermissions(), nullValue());
- assertThat(authInfo.getStringPermissions(), containsInAnyOrder("user:autocomplete", "group:autocomplete", "user:changePassword:trillian", "repository:read,pull:one", "repository:read,pull,push:two", "user:read:trillian", "user:changeApiKeys:trillian"));
+ assertThat(authInfo.getStringPermissions(), containsInAnyOrder("user:autocomplete", "group:autocomplete", "user:changePassword:trillian", "repository:read,pull:one", "repository:read,pull,push:two", "user:read:trillian", "user:changeApiKeys:trillian", "user:changePublicKeys:trillian"));
}
/**
@@ -244,7 +244,7 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
assertThat(authInfo.getObjectPermissions(), nullValue());
- assertThat(authInfo.getStringPermissions(), containsInAnyOrder("user:autocomplete", "group:autocomplete", "user:changePassword:trillian", "repository:read,pull:one", "repository:read,pull,push:two", "user:read:trillian", "user:changeApiKeys:trillian"));
+ assertThat(authInfo.getStringPermissions(), containsInAnyOrder("user:autocomplete", "group:autocomplete", "user:changePassword:trillian", "repository:read,pull:one", "repository:read,pull,push:two", "user:read:trillian", "user:changeApiKeys:trillian", "user:changePublicKeys:trillian"));
}
/**
@@ -288,7 +288,9 @@ public class DefaultAuthorizationCollectorTest {
"repository:system:one",
"repository:group:two",
"user:read:trillian",
- "user:changeApiKeys:trillian"));
+ "user:changeApiKeys:trillian",
+ "user:changePublicKeys:trillian"
+ ));
}
/**
@@ -335,7 +337,7 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
assertThat(authInfo.getObjectPermissions(), nullValue());
- assertThat(authInfo.getStringPermissions(), containsInAnyOrder("one:one", "two:two", "user:read:trillian", "user:autocomplete", "group:autocomplete", "user:changePassword:trillian", "user:changeApiKeys:trillian"));
+ assertThat(authInfo.getStringPermissions(), containsInAnyOrder("one:one", "two:two", "user:read:trillian", "user:autocomplete", "group:autocomplete", "user:changePassword:trillian", "user:changeApiKeys:trillian", "user:changePublicKeys:trillian"));
}
private void authenticate(User user, String group, String... groups) {