From 78260a708f18fd1bec5244d8f1e49348b5027021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 15 Oct 2018 14:20:04 +0200 Subject: [PATCH 01/44] create new branch From af81f239e1ed30f7711fa442f8e6a927d2240726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 15 Oct 2018 16:45:44 +0200 Subject: [PATCH 02/44] add head request --- scm-ui-components/packages/ui-components/src/apiclient.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scm-ui-components/packages/ui-components/src/apiclient.js b/scm-ui-components/packages/ui-components/src/apiclient.js index 0b57abeada..4c648a59f6 100644 --- a/scm-ui-components/packages/ui-components/src/apiclient.js +++ b/scm-ui-components/packages/ui-components/src/apiclient.js @@ -48,6 +48,10 @@ class ApiClient { return this.httpRequestWithJSONBody("PUT", url, contentType, payload); } + head(url: string, payload: any, contentType: string = "application/json") { + return this.httpRequestWithJSONBody("HEAD", url, contentType, payload); + } + delete(url: string): Promise { let options: RequestOptions = { method: "DELETE" From c23fa319a09af428b46188a7e0da1869be7b3c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 15 Oct 2018 16:45:54 +0200 Subject: [PATCH 03/44] add mockup for content classes --- .../src/repos/content/components/Content.js | 39 +++++++++++++++++++ .../repos/content/components/Content.test.js | 23 +++++++++++ .../content/components/DownloadViewer.js | 29 ++++++++++++++ .../repos/content/components/ImageViewer.js | 29 ++++++++++++++ .../content/components/SourcecodeViewer.js | 39 +++++++++++++++++++ .../components/SourcecodeViewer.test.js | 22 +++++++++++ 6 files changed, 181 insertions(+) create mode 100644 scm-ui/src/repos/content/components/Content.js create mode 100644 scm-ui/src/repos/content/components/Content.test.js create mode 100644 scm-ui/src/repos/content/components/DownloadViewer.js create mode 100644 scm-ui/src/repos/content/components/ImageViewer.js create mode 100644 scm-ui/src/repos/content/components/SourcecodeViewer.js create mode 100644 scm-ui/src/repos/content/components/SourcecodeViewer.test.js diff --git a/scm-ui/src/repos/content/components/Content.js b/scm-ui/src/repos/content/components/Content.js new file mode 100644 index 0000000000..e99692974f --- /dev/null +++ b/scm-ui/src/repos/content/components/Content.js @@ -0,0 +1,39 @@ +// @flow +import React from "react"; +import { translate } from "react-i18next"; +import { apiClient } from "@scm-manager/ui-components"; + +type Props = { + t: string => string +}; + +type State = { + contentType: string +}; + +class Content extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + contentType: "" + }; + } + + componentDidMount() {} + + render() { + return null; + } +} + +export function getContentType(url: string) { + return apiClient + .head(url) + .then(response => response) + .catch(err => { + return null; + }); +} + +export default translate("repos")(Content); diff --git a/scm-ui/src/repos/content/components/Content.test.js b/scm-ui/src/repos/content/components/Content.test.js new file mode 100644 index 0000000000..a1828b0872 --- /dev/null +++ b/scm-ui/src/repos/content/components/Content.test.js @@ -0,0 +1,23 @@ +//@flow +import fetchMock from "fetch-mock"; +import { getContentType } from "./Content"; + +describe("get content type", () => { + const CONTENT_URL = "/repositories/scmadmin/TestRepo/content/testContent"; + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + xit("should return content", done => { + fetchMock.head("/api/v2" + CONTENT_URL, { + "Content-Type": "text/plain" + }); + + getContentType(CONTENT_URL).then(content => { + expect(content).toBe("This is a testContent"); + done(); + }); + }); +}); diff --git a/scm-ui/src/repos/content/components/DownloadViewer.js b/scm-ui/src/repos/content/components/DownloadViewer.js new file mode 100644 index 0000000000..f2e10b376c --- /dev/null +++ b/scm-ui/src/repos/content/components/DownloadViewer.js @@ -0,0 +1,29 @@ +// @flow +import React from "react"; +import { translate } from "react-i18next"; + +type Props = { + t: string => string +}; + +type State = { + content: string +}; + +class DownloadViewer extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + content: "" + }; + } + + componentDidMount() {} + + render() { + return null; + } +} + +export default translate("repos")(DownloadViewer); diff --git a/scm-ui/src/repos/content/components/ImageViewer.js b/scm-ui/src/repos/content/components/ImageViewer.js new file mode 100644 index 0000000000..de4fc840fa --- /dev/null +++ b/scm-ui/src/repos/content/components/ImageViewer.js @@ -0,0 +1,29 @@ +// @flow +import React from "react"; +import { translate } from "react-i18next"; + +type Props = { + t: string => string +}; + +type State = { + content: string +}; + +class ImageViewer extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + content: "" + }; + } + + componentDidMount() {} + + render() { + return null; + } +} + +export default translate("repos")(ImageViewer); diff --git a/scm-ui/src/repos/content/components/SourcecodeViewer.js b/scm-ui/src/repos/content/components/SourcecodeViewer.js new file mode 100644 index 0000000000..6303bdd874 --- /dev/null +++ b/scm-ui/src/repos/content/components/SourcecodeViewer.js @@ -0,0 +1,39 @@ +// @flow +import React from "react"; +import { translate } from "react-i18next"; +import { apiClient } from "@scm-manager/ui-components"; + +type Props = { + t: string => string +}; + +type State = { + content: string +}; + +class SourcecodeViewer extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + content: "" + }; + } + + componentDidMount() {} + + render() { + return null; + } +} + +export function getContent(url: string) { + return apiClient + .get(url) + .then(response => response.text()) + .catch(err => { + return null; + }); +} + +export default translate("repos")(SourcecodeViewer); diff --git a/scm-ui/src/repos/content/components/SourcecodeViewer.test.js b/scm-ui/src/repos/content/components/SourcecodeViewer.test.js new file mode 100644 index 0000000000..0007db50ba --- /dev/null +++ b/scm-ui/src/repos/content/components/SourcecodeViewer.test.js @@ -0,0 +1,22 @@ +//@flow +import fetchMock from "fetch-mock"; +import { getContent } from "./SourcecodeViewer"; + +describe("get content", () => { + const CONTENT_URL = + "/repositories/scmadmin/TestRepo/content/testContent"; + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it("should return content", done => { + fetchMock.getOnce("/api/v2" + CONTENT_URL, "This is a testContent"); + + getContent(CONTENT_URL).then(content => { + expect(content).toBe("This is a testContent"); + done(); + }); + }); +}); From 41cc61277ff16ff8e98778aba09995d8a9abf4ca Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 16 Oct 2018 14:14:16 +0200 Subject: [PATCH 04/44] added system property to disable extraction of core plugins --- .../scm/boot/BootstrapContextListener.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java index 4f7a00ce56..bf7583b2a2 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java @@ -148,13 +148,15 @@ public class BootstrapContextListener implements ServletContextListener { context = sce.getServletContext(); - PluginIndex index = readCorePluginIndex(context); - File pluginDirectory = getPluginDirectory(); try { - extractCorePlugins(context, pluginDirectory, index); + if (!isCorePluginExtractionDisabled()) { + extractCorePlugins(context, pluginDirectory); + } else { + logger.info("core plugin extraction is disabled"); + } ClassLoader cl = ClassLoaders.getContextClassLoader(BootstrapContextListener.class); @@ -181,6 +183,10 @@ public class BootstrapContextListener implements ServletContextListener } } + private boolean isCorePluginExtractionDisabled() { + return Boolean.getBoolean("sonia.scm.boot.disable-core-plugin-extraction"); + } + /** * Restart the whole webapp context. * @@ -269,17 +275,15 @@ public class BootstrapContextListener implements ServletContextListener * * @param context * @param pluginDirectory - * @param lines - * @param index * * @throws IOException */ - private void extractCorePlugins(ServletContext context, File pluginDirectory, - PluginIndex index) - throws IOException + private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException { IOUtil.mkdirs(pluginDirectory); + PluginIndex index = readCorePluginIndex(context); + for (PluginIndexEntry entry : index) { extractCorePlugin(context, pluginDirectory, entry); From f684a074042e44bfaff21214e8def4d88b40d7a8 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 23 Oct 2018 15:47:20 +0200 Subject: [PATCH 05/44] update legman to version 1.4.0 to fix conflict with guava version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index df49023265..383ed6186d 100644 --- a/pom.xml +++ b/pom.xml @@ -757,7 +757,7 @@ 4.0 - 1.3.0 + 1.4.0 9.2.10.v20150310 From 4823232ff0eacc21912c1563ee2fe6a94fedb217 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 23 Oct 2018 16:28:53 +0200 Subject: [PATCH 06/44] added RestartServlet, which is able to trigger a complete restart of the context --- .../main/java/sonia/scm/ResteasyModule.java | 20 +++ .../java/sonia/scm/ScmContextListener.java | 1 + .../scm/boot/BootstrapContextFilter.java | 43 +++--- .../scm/boot/BootstrapContextListener.java | 28 ---- .../java/sonia/scm/boot/RestartServlet.java | 97 +++++++++++++ .../sonia/scm/boot/ServletContextCleaner.java | 59 ++++++++ .../src/main/resources/logback.default.xml | 7 +- scm-webapp/src/main/webapp/WEB-INF/web.xml | 23 --- .../sonia/scm/boot/RestartServletTest.java | 133 ++++++++++++++++++ .../scm/boot/ServletContextCleanerTest.java | 53 +++++++ 10 files changed, 389 insertions(+), 75 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/ResteasyModule.java create mode 100644 scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java create mode 100644 scm-webapp/src/main/java/sonia/scm/boot/ServletContextCleaner.java create mode 100644 scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/boot/ServletContextCleanerTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/ResteasyModule.java b/scm-webapp/src/main/java/sonia/scm/ResteasyModule.java new file mode 100644 index 0000000000..a85c3b6d06 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/ResteasyModule.java @@ -0,0 +1,20 @@ +package sonia.scm; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.servlet.ServletModule; +import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher; +import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; + +import javax.inject.Singleton; +import java.util.Map; + +public class ResteasyModule extends ServletModule { + + @Override + protected void configureServlets() { + bind(HttpServletDispatcher.class).in(Singleton.class); + + Map initParams = ImmutableMap.of(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, "/api"); + serve("/api/*").with(HttpServletDispatcher.class, initParams); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index 5a087a1c70..8ae005e826 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -126,6 +126,7 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList ClassOverrides overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader()); List moduleList = Lists.newArrayList(); + moduleList.add(new ResteasyModule()); moduleList.add(new ScmInitializerModule()); moduleList.add(new ScmEventBusModule()); moduleList.add(new EagerSingletonModule()); diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextFilter.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextFilter.java index bfa2218e76..aec8e2d653 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextFilter.java @@ -34,22 +34,19 @@ package sonia.scm.boot; //~--- non-JDK imports -------------------------------------------------------- import com.github.legman.Subscribe; - import com.google.inject.servlet.GuiceFilter; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.SCMContext; import sonia.scm.Stage; import sonia.scm.event.ScmEventBus; -//~--- JDK imports ------------------------------------------------------------ - import javax.servlet.FilterConfig; import javax.servlet.ServletContextEvent; import javax.servlet.ServletException; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -65,6 +62,8 @@ public class BootstrapContextFilter extends GuiceFilter //~--- methods -------------------------------------------------------------- + private final BootstrapContextListener listener = new BootstrapContextListener(); + /** * Restart the whole webapp context. * @@ -85,29 +84,20 @@ public class BootstrapContextFilter extends GuiceFilter } else { - - logger.warn( - "destroy filter pipeline, because of a received restart event"); + logger.warn("destroy filter pipeline, because of a received restart event"); destroy(); - logger.warn( - "reinitialize filter pipeline, because of a received restart event"); - super.init(filterConfig); + + logger.warn("reinitialize filter pipeline, because of a received restart event"); + initGuice(); } } - /** - * Method description - * - * - * @param filterConfig - * - * @throws ServletException - */ @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; - super.init(filterConfig); + + initGuice(); if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT) { @@ -116,6 +106,19 @@ public class BootstrapContextFilter extends GuiceFilter } } + public void initGuice() throws ServletException { + super.init(filterConfig); + + listener.contextInitialized(new ServletContextEvent(filterConfig.getServletContext())); + } + + @Override + public void destroy() { + super.destroy(); + listener.contextDestroyed(new ServletContextEvent(filterConfig.getServletContext())); + ServletContextCleaner.cleanup(filterConfig.getServletContext()); + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java index bf7583b2a2..afaa28bfe8 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java @@ -187,40 +187,12 @@ public class BootstrapContextListener implements ServletContextListener return Boolean.getBoolean("sonia.scm.boot.disable-core-plugin-extraction"); } - /** - * Restart the whole webapp context. - * - * - * @param event restart event - */ - @Subscribe - public void handleRestartEvent(RestartEvent event) - { - logger.warn("received restart event from {} with reason: {}", - event.getCause(), event.getReason()); - - if (context == null) - { - logger.error("context is null, scm-manager is not initialized"); - } - else - { - ServletContextEvent sce = new ServletContextEvent(context); - - logger.warn("destroy context, because of a received restart event"); - contextDestroyed(sce); - logger.warn("reinitialize context, because of a received restart event"); - contextInitialized(sce); - } - } - /** * Method description * * * @param context * @param pluginDirectory - * @param name * @param entry * * @throws IOException diff --git a/scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java b/scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java new file mode 100644 index 0000000000..c7177cc459 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/boot/RestartServlet.java @@ -0,0 +1,97 @@ +package sonia.scm.boot; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.Priority; +import sonia.scm.SCMContext; +import sonia.scm.Stage; +import sonia.scm.event.ScmEventBus; +import sonia.scm.filter.WebElement; + +import javax.inject.Inject; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This servlet sends a {@link RestartEvent} to the {@link ScmEventBus} which causes scm-manager to restart the context. + * The {@link RestartServlet} can be used for reloading java code or for installing plugins without a complete restart. + * At the moment the Servlet accepts only request, if scm-manager was started in the {@link Stage#DEVELOPMENT} stage. + * + * @since 2.0.0 + */ +@Priority(0) +@WebElement("/restart") +public class RestartServlet extends HttpServlet { + + private static final Logger LOG = LoggerFactory.getLogger(RestartServlet.class); + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final AtomicBoolean restarting = new AtomicBoolean(); + + private final ScmEventBus eventBus; + private final Stage stage; + + @Inject + public RestartServlet() { + this(ScmEventBus.getInstance(), SCMContext.getContext().getStage()); + } + + RestartServlet(ScmEventBus eventBus, Stage stage) { + this.eventBus = eventBus; + this.stage = stage; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + LOG.info("received sendRestartEvent request"); + + if (isRestartAllowed()) { + + try (InputStream requestInput = req.getInputStream()) { + Reason reason = objectMapper.readValue(requestInput, Reason.class); + sendRestartEvent(resp, reason); + } catch (IOException ex) { + LOG.warn("failed to trigger sendRestartEvent event", ex); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + } else { + LOG.debug("received restart event in non development stage"); + resp.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); + } + } + + private boolean isRestartAllowed() { + return stage == Stage.DEVELOPMENT; + } + + private void sendRestartEvent(HttpServletResponse response, Reason reason) { + if ( restarting.compareAndSet(false, true) ) { + LOG.info("trigger sendRestartEvent, because of {}", reason.getMessage()); + eventBus.post(new RestartEvent(RestartServlet.class, reason.getMessage())); + + response.setStatus(HttpServletResponse.SC_ACCEPTED); + } else { + LOG.warn("scm-manager restarts already"); + response.setStatus(HttpServletResponse.SC_CONFLICT); + } + } + + public static class Reason { + + private String message; + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/boot/ServletContextCleaner.java b/scm-webapp/src/main/java/sonia/scm/boot/ServletContextCleaner.java new file mode 100644 index 0000000000..8b152ce329 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/boot/ServletContextCleaner.java @@ -0,0 +1,59 @@ +package sonia.scm.boot; + +import com.google.common.collect.ImmutableSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletContext; +import java.util.Enumeration; +import java.util.Set; + +/** + * Remove cached resources from {@link ServletContext} to allow a clean restart of scm-manager without stale or + * duplicated data. + * + * @since 2.0.0 + */ +final class ServletContextCleaner { + + private static final Logger LOG = LoggerFactory.getLogger(ServletContextCleaner.class); + + private static final Set REMOVE_PREFIX = ImmutableSet.of( + "org.jboss.resteasy", + "resteasy", + "org.apache.shiro", + "sonia.scm" + ); + + private ServletContextCleaner() { + } + + /** + * Remove cached attributes from {@link ServletContext}. + * + * @param servletContext servlet context + */ + static void cleanup(ServletContext servletContext) { + LOG.info("remove cached attributes from context"); + + Enumeration attributeNames = servletContext.getAttributeNames(); + while( attributeNames.hasMoreElements()) { + String name = attributeNames.nextElement(); + if (shouldRemove(name)) { + LOG.info("remove attribute {} from servlet context", name); + servletContext.removeAttribute(name); + } else { + LOG.info("keep attribute {} in servlet context", name); + } + } + } + + private static boolean shouldRemove(String name) { + for (String prefix : REMOVE_PREFIX) { + if (name.startsWith(prefix)) { + return true; + } + } + return false; + } +} diff --git a/scm-webapp/src/main/resources/logback.default.xml b/scm-webapp/src/main/resources/logback.default.xml index 33e914d04d..b12b9e0890 100644 --- a/scm-webapp/src/main/resources/logback.default.xml +++ b/scm-webapp/src/main/resources/logback.default.xml @@ -65,9 +65,6 @@ - - - @@ -93,7 +90,9 @@ --> - + + + diff --git a/scm-webapp/src/main/webapp/WEB-INF/web.xml b/scm-webapp/src/main/webapp/WEB-INF/web.xml index 8abba66aa3..e69eb9fb2a 100644 --- a/scm-webapp/src/main/webapp/WEB-INF/web.xml +++ b/scm-webapp/src/main/webapp/WEB-INF/web.xml @@ -41,10 +41,6 @@ - - sonia.scm.boot.BootstrapContextListener - - BootstrapFilter sonia.scm.boot.BootstrapContextFilter @@ -55,25 +51,6 @@ /* - - - - resteasy.servlet.mapping.prefix - /api - - - - Resteasy - - org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher - - - - - Resteasy - /api/* - - From 463a93553deedb6463e358551c83fe8b0cd137f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 09:32:30 +0100 Subject: [PATCH 17/44] refactoring content view --- .../components/content/DownloadViewer.js | 38 ++------- .../src/repos/sources/containers/Content.js | 77 +++++++++++++++---- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/scm-ui/src/repos/sources/components/content/DownloadViewer.js b/scm-ui/src/repos/sources/components/content/DownloadViewer.js index d9a96fd886..b902c1f0b3 100644 --- a/scm-ui/src/repos/sources/components/content/DownloadViewer.js +++ b/scm-ui/src/repos/sources/components/content/DownloadViewer.js @@ -2,7 +2,7 @@ import React from "react"; import { translate } from "react-i18next"; import type { File } from "@scm-manager/ui-types"; -import { DownloadButton, DateFromNow } from "@scm-manager/ui-components"; +import { DownloadButton } from "@scm-manager/ui-components"; type Props = { t: string => string, @@ -12,39 +12,13 @@ type Props = { class DownloadViewer extends React.Component { render() { - const { t, file, revision } = this.props; + const { t, file } = this.props; return (
-
-
-

{file.name}

-
-
-
- -
-
- - - - - - - - - - - - - - - -
{t("sources.description")}{file.description}
{t("sources.lastModified")} - -
{t("sources.branch")}{revision}
+
); } diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 9506e637aa..68e02aece2 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -1,20 +1,23 @@ // @flow import React from "react"; -import { translate } from "react-i18next"; +import { Interpolate, translate } from "react-i18next"; import { apiClient } from "@scm-manager/ui-components"; import { getSources } from "../modules/sources"; -import type { - Repository, - File -} from "@scm-manager/ui-types"; +import type { Repository, File } from "@scm-manager/ui-types"; import { ErrorNotification, - Loading + Loading, + DateFromNow } from "@scm-manager/ui-components"; import { connect } from "react-redux"; import ImageViewer from "../components/content/ImageViewer"; import SourcecodeViewer from "../components/content/SourcecodeViewer"; import DownloadViewer from "../components/content/DownloadViewer"; +import FileSize from "../components/FileSize"; +import AvatarWrapper from "../../components/changesets/AvatarWrapper"; +import classNames from "classnames"; +import AvatarImage from "../../components/changesets/AvatarImage"; +import ChangesetAuthor from "../../components/changesets/ChangesetAuthor"; type Props = { t: string => string, @@ -67,9 +70,47 @@ class Content extends React.Component { .catch(err => {}); } - render() { + showHeader() { + const { file, revision, t } = this.props; + const date = ; + const fileSize = file.directory ? "" : ; + + return ( +
+

{file.name}

+
+
+

+ {file.description.split("\n").map((item, key) => { + return ( + + {item} +
+
+ ); + })} +

+
+
{date}
+
+
+ ); + } + + showContent() { const { file, revision } = this.props; const contentType = this.state.contentType; + if (contentType.startsWith("image")) { + return ; + } else if (contentType.startsWith("text")) { + return ; + } else { + return ; + } + } + + render() { + const { file } = this.props; const error = this.state.error; const hasError = this.state.hasError; @@ -79,15 +120,23 @@ class Content extends React.Component { if (hasError) { return ; } - if (contentType.startsWith("image")) { - return ; - } - if (contentType.startsWith("text")) { - return ; - } + const header = this.showHeader(); + const content = this.showContent(); + const fileSize = file.directory ? "" : ; - return ; + return ( +
+ {header} + +
+ ); } } From 866409c8df0a77e3830acc4568c1cf2c0705de6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 09:59:04 +0100 Subject: [PATCH 18/44] center download button --- .../components/content/DownloadViewer.js | 5 ++-- .../src/repos/sources/containers/Content.js | 23 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/scm-ui/src/repos/sources/components/content/DownloadViewer.js b/scm-ui/src/repos/sources/components/content/DownloadViewer.js index b902c1f0b3..8355a61e32 100644 --- a/scm-ui/src/repos/sources/components/content/DownloadViewer.js +++ b/scm-ui/src/repos/sources/components/content/DownloadViewer.js @@ -7,14 +7,15 @@ import { DownloadButton } from "@scm-manager/ui-components"; type Props = { t: string => string, file: File, - revision: string + revision: string, + classes: any }; class DownloadViewer extends React.Component { render() { const { t, file } = this.props; return ( -
+
string, @@ -39,6 +37,12 @@ type State = { hasError: boolean }; +const styles = { + toCenterContent: { + display: "block" + } +}; + class Content extends React.Component { constructor(props: Props) { super(props); @@ -71,9 +75,8 @@ class Content extends React.Component { } showHeader() { - const { file, revision, t } = this.props; + const { file } = this.props; const date = ; - const fileSize = file.directory ? "" : ; return (
@@ -110,7 +113,7 @@ class Content extends React.Component { } render() { - const { file } = this.props; + const { file, classes } = this.props; const error = this.state.error; const hasError = this.state.hasError; @@ -133,7 +136,9 @@ class Content extends React.Component {
{file.name}
{fileSize}
-
{content}
+
+ {content} +
); @@ -161,4 +166,6 @@ const mapStateToProps = (state: any, ownProps: Props) => { }; }; -export default connect(mapStateToProps)(translate("repos")(Content)); +export default injectSheet(styles)( + connect(mapStateToProps)(translate("repos")(Content)) +); From abad7d905c59292223a6bcdea03ea1233d389c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 10:46:16 +0100 Subject: [PATCH 19/44] show image --- .../components/content/DownloadViewer.js | 4 +-- .../sources/components/content/ImageViewer.js | 29 ++++++++----------- .../src/repos/sources/containers/Content.js | 2 +- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/scm-ui/src/repos/sources/components/content/DownloadViewer.js b/scm-ui/src/repos/sources/components/content/DownloadViewer.js index 8355a61e32..4b84d7a53d 100644 --- a/scm-ui/src/repos/sources/components/content/DownloadViewer.js +++ b/scm-ui/src/repos/sources/components/content/DownloadViewer.js @@ -6,9 +6,7 @@ import { DownloadButton } from "@scm-manager/ui-components"; type Props = { t: string => string, - file: File, - revision: string, - classes: any + file: File }; class DownloadViewer extends React.Component { diff --git a/scm-ui/src/repos/sources/components/content/ImageViewer.js b/scm-ui/src/repos/sources/components/content/ImageViewer.js index 0b5eae20d7..ca0fc64a3c 100644 --- a/scm-ui/src/repos/sources/components/content/ImageViewer.js +++ b/scm-ui/src/repos/sources/components/content/ImageViewer.js @@ -1,28 +1,23 @@ // @flow import React from "react"; import { translate } from "react-i18next"; +import type { File } from "@scm-manager/ui-types"; type Props = { - t: string => string + t: string => string, + file: File }; -type State = { - content: string -}; - -class ImageViewer extends React.Component { - constructor(props: Props) { - super(props); - - this.state = { - content: "" - }; - } - - componentDidMount() {} - +class ImageViewer extends React.Component { render() { - return "ImageViewer"; + const { file } = this.props; + return ( +
+
+ +
+
+ ); } } diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 2ceb702896..f3d9871b33 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -104,7 +104,7 @@ class Content extends React.Component { const { file, revision } = this.props; const contentType = this.state.contentType; if (contentType.startsWith("image")) { - return ; + return ; } else if (contentType.startsWith("text")) { return ; } else { From 370bcd1845b375d7f628a456b24fc3f0276938c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 11:50:55 +0100 Subject: [PATCH 20/44] show source code --- scm-ui/package.json | 1 + .../components/content/SourcecodeViewer.js | 82 +++++++++- .../content/SourcecodeViewer.test.js | 14 +- .../src/repos/sources/containers/Content.js | 9 +- .../repos/sources/containers/Content.test.js | 2 +- scm-ui/yarn.lock | 150 +++++++++++++++++- 6 files changed, 243 insertions(+), 15 deletions(-) diff --git a/scm-ui/package.json b/scm-ui/package.json index de42ff2a36..f88fe75c85 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -23,6 +23,7 @@ "react-redux": "^5.0.7", "react-router-dom": "^4.3.1", "react-router-redux": "^5.0.0-alpha.9", + "react-syntax-highlighter": "^9.0.1", "redux": "^4.0.0", "redux-devtools-extension": "^2.13.5", "redux-logger": "^3.0.6", diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js index dacbbd6283..d1b6dadb61 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js @@ -1,14 +1,23 @@ // @flow import React from "react"; import { translate } from "react-i18next"; -import { apiClient } from "../../../../../../scm-ui-components/packages/ui-components/src/index"; +import { apiClient } from "@scm-manager/ui-components"; +import type { File } from "@scm-manager/ui-types"; +import { ErrorNotification, Loading } from "@scm-manager/ui-components"; +import SyntaxHighlighter from "react-syntax-highlighter"; +import { arduinoLight } from "react-syntax-highlighter/styles/hljs"; type Props = { - t: string => string + t: string => string, + file: File, + contentType: string }; type State = { - content: string + content: string, + error: Error, + hasError: boolean, + loaded: boolean }; class SourcecodeViewer extends React.Component { @@ -16,23 +25,82 @@ class SourcecodeViewer extends React.Component { super(props); this.state = { - content: "" + content: "", + error: new Error(), + hasError: false, + loaded: false }; } - componentDidMount() {} + componentDidMount() { + const { file } = this.props; + getContent(file._links.self.href) + .then(result => { + if (result.error) { + this.setState({ + ...this.state, + hasError: true, + error: result.error, + loaded: true + }); + } else { + this.setState({ + ...this.state, + content: result, + loaded: true + }); + } + }) + .catch(err => {}); + } render() { - return "sourceCodeViewer"; + const content = this.state.content; + const error = this.state.error; + const hasError = this.state.hasError; + const loaded = this.state.loaded; + const { contentType } = this.props; + + if (hasError) { + return ; + } + + if (!loaded) { + return ; + } + + if (!content) { + return null; + } + + return ( + + {content} + + ); } } +export function getLanguage(contentType: string) { + return contentType.substring( + contentType.indexOf("/") + 1, + contentType.length + ); +} + export function getContent(url: string) { return apiClient .get(url) .then(response => response.text()) + .then(content => { + return content; + }) .catch(err => { - return null; + return { error: err }; }); } diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js index 0007db50ba..a318b8854a 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js @@ -1,10 +1,9 @@ //@flow import fetchMock from "fetch-mock"; -import { getContent } from "./SourcecodeViewer"; +import { getContent, getLanguage } from "./SourcecodeViewer"; describe("get content", () => { - const CONTENT_URL = - "/repositories/scmadmin/TestRepo/content/testContent"; + const CONTENT_URL = "/repositories/scmadmin/TestRepo/content/testContent"; afterEach(() => { fetchMock.reset(); @@ -20,3 +19,12 @@ describe("get content", () => { }); }); }); + +describe("get correct language Type", () => { + it("should return javascript", () => { + expect(getLanguage("application/javascript")).toBe("javascript"); + }); + it("should return text", () => { + expect(getLanguage("text/plain")).toBe("plain"); + }); +}); diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index f3d9871b33..a9f2e46df9 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { Interpolate, translate } from "react-i18next"; +import { translate } from "react-i18next"; import { apiClient } from "@scm-manager/ui-components"; import { getSources } from "../modules/sources"; import type { Repository, File } from "@scm-manager/ui-types"; @@ -105,8 +105,11 @@ class Content extends React.Component { const contentType = this.state.contentType; if (contentType.startsWith("image")) { return ; - } else if (contentType.startsWith("text")) { - return ; + } else if ( + contentType.startsWith("text") || + contentType.startsWith("application") + ) { + return ; } else { return ; } diff --git a/scm-ui/src/repos/sources/containers/Content.test.js b/scm-ui/src/repos/sources/containers/Content.test.js index ef115accd4..8de2f48b18 100644 --- a/scm-ui/src/repos/sources/containers/Content.test.js +++ b/scm-ui/src/repos/sources/containers/Content.test.js @@ -20,7 +20,7 @@ describe("get content type", () => { }); getContentType(CONTENT_URL).then(content => { - expect(content).toBe("application/text"); + expect(content.type).toBe("application/text"); done(); }); }); diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 18b198622c..ec4789b544 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -1181,7 +1181,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1739,6 +1739,18 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +character-entities-legacy@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c" + +character-entities@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363" + +character-reference-invalid@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -1840,6 +1852,14 @@ cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" +clipboard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.1.tgz#a12481e1c13d8a50f5f036b0560fe5d16d74e46a" + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -1946,6 +1966,12 @@ combined-stream@^1.0.5, combined-stream@~1.0.5, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +comma-separated-tokens@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz#b13793131d9ea2d2431cf5b507ddec258f0ce0db" + dependencies: + trim "0.0.1" + commander@^2.11.0, commander@^2.17.1, commander@^2.2.0, commander@^2.9.0: version "2.18.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" @@ -2327,6 +2353,10 @@ delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -3068,6 +3098,12 @@ fast-xml-parser@^3.12.0: dependencies: nimnjs "^1.3.2" +fault@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa" + dependencies: + format "^0.2.2" + fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" @@ -3280,6 +3316,10 @@ form-data@~2.3.1, form-data@~2.3.2: combined-stream "1.0.6" mime-types "^2.1.12" +format@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -3569,6 +3609,12 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + dependencies: + delegate "^3.1.2" + got@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" @@ -3814,6 +3860,19 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hast-util-parse-selector@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.0.tgz#2175f18cdd697308fc3431d5c29a9e48dfa4817a" + +hastscript@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-4.1.0.tgz#ea5593fa6f6709101fc790ced818393ddaa045ce" + dependencies: + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.2.0" + property-information "^4.0.0" + space-separated-tokens "^1.0.0" + hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" @@ -3823,6 +3882,10 @@ hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" +highlight.js@~9.12.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" + history@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" @@ -4115,6 +4178,17 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-alphabetical@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41" + +is-alphanumerical@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40" + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -4165,6 +4239,10 @@ is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" +is-decimal@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff" + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -4251,6 +4329,10 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835" + is-in-browser@^1.0.2, is-in-browser@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" @@ -5320,6 +5402,13 @@ lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" +lowlight@~1.9.1: + version "1.9.2" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.2.tgz#0b9127e3cec2c3021b7795dd81005c709a42fdd1" + dependencies: + fault "^1.0.2" + highlight.js "~9.12.0" + lru-cache@2: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" @@ -6175,6 +6264,17 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" +parse-entities@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4" + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -6422,6 +6522,12 @@ pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" +prismjs@^1.8.4, prismjs@~1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9" + optionalDependencies: + clipboard "^2.0.0" + private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -6456,6 +6562,12 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2: loose-envify "^1.3.1" object-assign "^4.1.1" +property-information@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-4.2.0.tgz#f0e66e07cbd6fed31d96844d958d153ad3eb486e" + dependencies: + xtend "^4.0.1" + ps-tree@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" @@ -6653,6 +6765,16 @@ react-router@^4.2.0, react-router@^4.3.1: prop-types "^15.6.1" warning "^4.0.1" +react-syntax-highlighter@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-9.0.1.tgz#cad91692e1976f68290f24762ac3451b1fec3d26" + dependencies: + babel-runtime "^6.18.0" + highlight.js "~9.12.0" + lowlight "~1.9.1" + prismjs "^1.8.4" + refractor "^2.4.1" + react-test-renderer@^16.0.0-0, react-test-renderer@^16.4.1: version "16.5.2" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.2.tgz#92e9d2c6f763b9821b2e0b22f994ee675068b5ae" @@ -6811,6 +6933,14 @@ redux@^4.0.0: loose-envify "^1.1.0" symbol-observable "^1.2.0" +refractor@^2.4.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.6.0.tgz#6b0d88f654c8534eefed3329a35bc7bb74ae0979" + dependencies: + hastscript "^4.0.0" + parse-entities "^1.1.2" + prismjs "~1.15.0" + regenerate-unicode-properties@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" @@ -7172,6 +7302,10 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + "semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" @@ -7440,6 +7574,12 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" +space-separated-tokens@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412" + dependencies: + trim "0.0.1" + sparkles@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" @@ -7834,6 +7974,10 @@ timers-ext@^0.1.5: es5-ext "~0.10.46" next-tick "1" +tiny-emitter@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -7917,6 +8061,10 @@ trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + "true-case-path@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" From 7734b68d0bf212aae5c3d34fbf96a67f22c2f049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 13:19:16 +0100 Subject: [PATCH 21/44] add source code viewer for different languages --- .../components/content/SourcecodeViewer.js | 21 ++++++++++++++++++- .../content/SourcecodeViewer.test.js | 11 +++++++++- .../src/repos/sources/containers/Content.js | 1 + 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js index d1b6dadb61..8c0d27661b 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js @@ -86,10 +86,29 @@ class SourcecodeViewer extends React.Component { } export function getLanguage(contentType: string) { - return contentType.substring( + const language = contentType.substring( contentType.indexOf("/") + 1, contentType.length ); + + let languageType; + + switch (language) { + case "x-go": + languageType = "go"; + break; + case "x-java-source": + languageType = "java"; + break; + case "x-web-markdown": + languageType = "markdown"; + break; + default: + languageType = language; + } + + console.log(languageType); + return languageType; } export function getContent(url: string) { diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js index a318b8854a..a33dba454b 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js @@ -20,11 +20,20 @@ describe("get content", () => { }); }); -describe("get correct language Type", () => { +describe("get correct language type", () => { it("should return javascript", () => { expect(getLanguage("application/javascript")).toBe("javascript"); }); it("should return text", () => { expect(getLanguage("text/plain")).toBe("plain"); }); + it("should return go", () => { + expect(getLanguage("text/x-go")).toBe("go"); + }); + it("should return java", () => { + expect(getLanguage("text/x-java-source")).toBe("java"); + }); + it("should return markdown", () => { + expect(getLanguage("text/x-web-markdown")).toBe("markdown"); + }); }); diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index a9f2e46df9..23e33c407d 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -152,6 +152,7 @@ export function getContentType(url: string, state: any) { return apiClient .head(url) .then(response => { + console.log(response.headers.get("Content-Type")); return { type: response.headers.get("Content-Type") }; }) .catch(err => { From a9d7cceb22fc42b342953a9e7ff375b20bfb5985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 13:42:56 +0100 Subject: [PATCH 22/44] eslint fixes --- scm-ui/src/config/containers/GlobalConfig.js | 2 +- scm-ui/src/config/modules/config.test.js | 1 - scm-ui/src/containers/App.js | 3 +-- scm-ui/src/containers/Logout.js | 2 +- scm-ui/src/groups/containers/AddGroup.js | 3 +-- scm-ui/src/index.js | 3 +-- scm-ui/src/modules/auth.js | 6 +++--- .../sources/components/content/ImageViewer.js | 2 +- .../components/content/SourcecodeViewer.js | 1 - .../src/repos/sources/containers/Content.js | 1 - .../src/repos/sources/modules/sources.test.js | 21 ------------------- 11 files changed, 9 insertions(+), 36 deletions(-) diff --git a/scm-ui/src/config/containers/GlobalConfig.js b/scm-ui/src/config/containers/GlobalConfig.js index 252e880a42..6046aa4a09 100644 --- a/scm-ui/src/config/containers/GlobalConfig.js +++ b/scm-ui/src/config/containers/GlobalConfig.js @@ -14,7 +14,7 @@ import { modifyConfigReset } from "../modules/config"; import { connect } from "react-redux"; -import type { Config, Link } from "@scm-manager/ui-types"; +import type { Config } from "@scm-manager/ui-types"; import ConfigForm from "../components/form/ConfigForm"; import { getConfigLink } from "../../modules/indexResource"; diff --git a/scm-ui/src/config/modules/config.test.js b/scm-ui/src/config/modules/config.test.js index 12c6b347c3..b6c97826b0 100644 --- a/scm-ui/src/config/modules/config.test.js +++ b/scm-ui/src/config/modules/config.test.js @@ -22,7 +22,6 @@ import reducer, { getConfig, getConfigUpdatePermission } from "./config"; -import { getConfigLink } from "../../modules/indexResource"; const CONFIG_URL = "/config"; const URL = "/api/v2" + CONFIG_URL; diff --git a/scm-ui/src/containers/App.js b/scm-ui/src/containers/App.js index 768b1776d4..50fc805eb2 100644 --- a/scm-ui/src/containers/App.js +++ b/scm-ui/src/containers/App.js @@ -19,9 +19,8 @@ import { Footer, Header } from "@scm-manager/ui-components"; -import type { Me, Link } from "@scm-manager/ui-types"; +import type { Me } from "@scm-manager/ui-types"; import { - fetchIndexResources, getConfigLink, getFetchIndexResourcesFailure, getGroupsLink, diff --git a/scm-ui/src/containers/Logout.js b/scm-ui/src/containers/Logout.js index 7875a6b92a..fe6662da42 100644 --- a/scm-ui/src/containers/Logout.js +++ b/scm-ui/src/containers/Logout.js @@ -11,7 +11,7 @@ import { getLogoutFailure } from "../modules/auth"; import { Loading, ErrorPage } from "@scm-manager/ui-components"; -import { fetchIndexResources, getLogoutLink } from "../modules/indexResource"; +import { getLogoutLink } from "../modules/indexResource"; type Props = { authenticated: boolean, diff --git a/scm-ui/src/groups/containers/AddGroup.js b/scm-ui/src/groups/containers/AddGroup.js index bcb19846b8..9b13ac0309 100644 --- a/scm-ui/src/groups/containers/AddGroup.js +++ b/scm-ui/src/groups/containers/AddGroup.js @@ -9,8 +9,7 @@ import { createGroup, isCreateGroupPending, getCreateGroupFailure, - createGroupReset, - getCreateGroupLink + createGroupReset } from "../modules/groups"; import type { Group } from "@scm-manager/ui-types"; import type { History } from "history"; diff --git a/scm-ui/src/index.js b/scm-ui/src/index.js index 3ecd38e6d0..08e3e8a58c 100644 --- a/scm-ui/src/index.js +++ b/scm-ui/src/index.js @@ -14,7 +14,6 @@ import type { BrowserHistory } from "history/createBrowserHistory"; import createReduxStore from "./createReduxStore"; import { ConnectedRouter } from "react-router-redux"; -import PluginLoader from "./containers/PluginLoader"; import { urls } from "@scm-manager/ui-components"; @@ -37,7 +36,7 @@ ReactDOM.render( {/* ConnectedRouter will use the store from Provider automatically */} - + , diff --git a/scm-ui/src/modules/auth.js b/scm-ui/src/modules/auth.js index fd5068aeb8..fe636ac9d3 100644 --- a/scm-ui/src/modules/auth.js +++ b/scm-ui/src/modules/auth.js @@ -7,8 +7,8 @@ import { isPending } from "./pending"; import { getFailure } from "./failure"; import { callFetchIndexResources, - FETCH_INDEXRESOURCES_SUCCESS, - fetchIndexResources, fetchIndexResourcesPending, + fetchIndexResources, + fetchIndexResourcesPending, fetchIndexResourcesSuccess } from "./indexResource"; @@ -156,7 +156,7 @@ export const login = ( return apiClient .post(loginLink, login_data) .then(response => { - dispatch(fetchIndexResourcesPending()) + dispatch(fetchIndexResourcesPending()); return callFetchIndexResources(); }) .then(response => { diff --git a/scm-ui/src/repos/sources/components/content/ImageViewer.js b/scm-ui/src/repos/sources/components/content/ImageViewer.js index ca0fc64a3c..67003fa357 100644 --- a/scm-ui/src/repos/sources/components/content/ImageViewer.js +++ b/scm-ui/src/repos/sources/components/content/ImageViewer.js @@ -14,7 +14,7 @@ class ImageViewer extends React.Component { return (
- + {file._links.self.href}
); diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js index 8c0d27661b..3917e851b9 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js @@ -107,7 +107,6 @@ export function getLanguage(contentType: string) { languageType = language; } - console.log(languageType); return languageType; } diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 23e33c407d..a9f2e46df9 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -152,7 +152,6 @@ export function getContentType(url: string, state: any) { return apiClient .head(url) .then(response => { - console.log(response.headers.get("Content-Type")); return { type: response.headers.get("Content-Type") }; }) .catch(err => { diff --git a/scm-ui/src/repos/sources/modules/sources.test.js b/scm-ui/src/repos/sources/modules/sources.test.js index f6336bba53..1a5c81e908 100644 --- a/scm-ui/src/repos/sources/modules/sources.test.js +++ b/scm-ui/src/repos/sources/modules/sources.test.js @@ -95,27 +95,6 @@ const noDirectory: File = { _embedded: collection }; -const directory: File = { - name: "package.json", - path: "package.json", - directory: false, - description: "bump version", - length: 780, - lastModified: "2017-07-31T11:17:19Z", - revision: "abc", - _links: { - self: { - href: - "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/content/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/package.json" - }, - history: { - href: - "http://localhost:8081/scm/rest/api/v2/repositories/scm/core/sources/history/76aae4bb4ceacf0e88938eb5b6832738b7d537b4/package.json" - } - }, - _embedded: {} -}; - describe("sources fetch", () => { const mockStore = configureMockStore([thunk]); From 9b10d1d4ea21d660ae6acc0db565f25e27494224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 13:56:16 +0100 Subject: [PATCH 23/44] check if description exists before using it --- .../src/repos/sources/containers/Content.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index a9f2e46df9..4a91e57cf2 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -77,23 +77,24 @@ class Content extends React.Component { showHeader() { const { file } = this.props; const date = ; + const description = file.description ? ( +

+ {file.description.split("\n").map((item, key) => { + return ( + + {item} +
+
+ ); + })} +

+ ) : null; return (

{file.name}

-
-

- {file.description.split("\n").map((item, key) => { - return ( - - {item} -
-
- ); - })} -

-
+
{description}
{date}
From 188bb54094b6f983eff705dd8b73b28e18e77ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 14:11:37 +0100 Subject: [PATCH 24/44] show branch in content view --- scm-ui/src/repos/sources/containers/Content.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 4a91e57cf2..c6c3cecd30 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -75,7 +75,7 @@ class Content extends React.Component { } showHeader() { - const { file } = this.props; + const { file, revision } = this.props; const date = ; const description = file.description ? (

@@ -89,10 +89,16 @@ class Content extends React.Component { })}

) : null; + const branch = "[" + revision + "]"; return (
-

{file.name}

+
+
+

{file.name}

+
+
{branch}
+
{description}
{date}
From 06ca66d9f17e4be53ad810cd0cf9fc570bdebab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 14:53:24 +0100 Subject: [PATCH 25/44] use header instead of content type --- .../components/content/SourcecodeViewer.js | 60 +++++++++++-------- .../content/SourcecodeViewer.test.js | 36 +++++++---- 2 files changed, 57 insertions(+), 39 deletions(-) diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js index 3917e851b9..648c99d29a 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js @@ -15,6 +15,7 @@ type Props = { type State = { content: string, + language: string, error: Error, hasError: boolean, loaded: boolean @@ -26,6 +27,7 @@ class SourcecodeViewer extends React.Component { this.state = { content: "", + language: "", error: new Error(), hasError: false, loaded: false @@ -34,6 +36,22 @@ class SourcecodeViewer extends React.Component { componentDidMount() { const { file } = this.props; + getProgrammingLanguage(file._links.self.href) + .then(result => { + if (result.error) { + this.setState({ + ...this.state, + hasError: true, + error: result.error + }); + } else { + this.setState({ + ...this.state, + language: result.language + }); + } + }) + .catch(err => {}); getContent(file._links.self.href) .then(result => { if (result.error) { @@ -59,7 +77,7 @@ class SourcecodeViewer extends React.Component { const error = this.state.error; const hasError = this.state.hasError; const loaded = this.state.loaded; - const { contentType } = this.props; + const language = this.state.language; if (hasError) { return ; @@ -76,7 +94,7 @@ class SourcecodeViewer extends React.Component { return ( {content} @@ -85,37 +103,27 @@ class SourcecodeViewer extends React.Component { } } -export function getLanguage(contentType: string) { - const language = contentType.substring( - contentType.indexOf("/") + 1, - contentType.length - ); +export function getLanguage(language: string) { + return language.toLowerCase(); +} - let languageType; - - switch (language) { - case "x-go": - languageType = "go"; - break; - case "x-java-source": - languageType = "java"; - break; - case "x-web-markdown": - languageType = "markdown"; - break; - default: - languageType = language; - } - - return languageType; +export function getProgrammingLanguage(url: string) { + return apiClient + .head(url) + .then(response => { + return { language: response.headers.get("Language") }; + }) + .catch(err => { + return { error: err }; + }); } export function getContent(url: string) { return apiClient .get(url) .then(response => response.text()) - .then(content => { - return content; + .then(response => { + return response; }) .catch(err => { return { error: err }; diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js index a33dba454b..b132721e3b 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js @@ -1,6 +1,10 @@ //@flow import fetchMock from "fetch-mock"; -import { getContent, getLanguage } from "./SourcecodeViewer"; +import { + getContent, + getLanguage, + getProgrammingLanguage +} from "./SourcecodeViewer"; describe("get content", () => { const CONTENT_URL = "/repositories/scmadmin/TestRepo/content/testContent"; @@ -18,22 +22,28 @@ describe("get content", () => { done(); }); }); + + it("should return language", done => { + let headers = { + Language: "JAVA" + }; + + fetchMock.head("/api/v2" + CONTENT_URL, { + headers + }); + + getProgrammingLanguage(CONTENT_URL).then(content => { + expect(content.language).toBe("JAVA"); + done(); + }); + }); }); describe("get correct language type", () => { it("should return javascript", () => { - expect(getLanguage("application/javascript")).toBe("javascript"); + expect(getLanguage("JAVASCRIPT")).toBe("javascript"); }); - it("should return text", () => { - expect(getLanguage("text/plain")).toBe("plain"); - }); - it("should return go", () => { - expect(getLanguage("text/x-go")).toBe("go"); - }); - it("should return java", () => { - expect(getLanguage("text/x-java-source")).toBe("java"); - }); - it("should return markdown", () => { - expect(getLanguage("text/x-web-markdown")).toBe("markdown"); + it("should return nothing for plain text", () => { + expect(getLanguage("")).toBe(""); }); }); From 1b60095373103024c3aefbf09c7f9133d32b6705 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 29 Oct 2018 14:55:56 +0100 Subject: [PATCH 26/44] do not cache resource urls in development stage, to avoid stale data --- .../plugin/DefaultUberWebResourceLoader.java | 43 +++++++++++++------ .../DefaultUberWebResourceLoaderTest.java | 16 +++++-- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultUberWebResourceLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultUberWebResourceLoader.java index 115a63fc2f..6b886dd5ce 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultUberWebResourceLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultUberWebResourceLoader.java @@ -41,6 +41,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.SCMContext; +import sonia.scm.Stage; import javax.servlet.ServletContext; import java.net.MalformedURLException; @@ -69,19 +71,21 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader //~--- constructors --------------------------------------------------------- - /** - * Constructs ... - * - * - * @param servletContext - * @param plugins - */ - public DefaultUberWebResourceLoader(ServletContext servletContext, - Iterable plugins) - { + public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable plugins) { + this(servletContext, plugins, SCMContext.getContext().getStage()); + } + + public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable plugins, Stage stage) { this.servletContext = servletContext; this.plugins = plugins; - this.cache = CacheBuilder.newBuilder().build(); + this.cache = createCache(stage); + } + + private final Cache createCache(Stage stage) { + if (stage == Stage.DEVELOPMENT) { + return null; + } + return CacheBuilder.newBuilder().build(); } //~--- get methods ---------------------------------------------------------- @@ -97,7 +101,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader @Override public URL getResource(String path) { - URL resource = cache.getIfPresent(path); + URL resource = getFromCache(path); if (resource == null) { @@ -105,7 +109,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader if (resource != null) { - cache.put(path, resource); + addToCache(path, resource); } } else @@ -116,6 +120,19 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader return resource; } + private URL getFromCache(String path) { + if (cache != null) { + return cache.getIfPresent(path); + } + return null; + } + + private void addToCache(String path, URL url) { + if (cache != null) { + cache.put(path, url); + } + } + /** * Method description * diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultUberWebResourceLoaderTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultUberWebResourceLoaderTest.java index 42b038ab17..9e5ebccbfd 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultUberWebResourceLoaderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultUberWebResourceLoaderTest.java @@ -42,6 +42,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.Stage; import javax.servlet.ServletContext; import java.io.File; @@ -96,14 +97,12 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase * Method description * * - * @throws MalformedURLException */ @Test - public void testGetResourceFromCache() throws MalformedURLException - { + public void testGetResourceFromCache() { DefaultUberWebResourceLoader resourceLoader = new DefaultUberWebResourceLoader(servletContext, - new ArrayList()); + new ArrayList(), Stage.PRODUCTION); resourceLoader.getCache().put("/myresource", GITHUB); @@ -112,6 +111,15 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase assertSame(GITHUB, resource); } + @Test + public void testGetResourceCacheIsDisableInStageDevelopment() throws MalformedURLException { + DefaultUberWebResourceLoader resourceLoader = new DefaultUberWebResourceLoader(servletContext, new ArrayList<>(), Stage.DEVELOPMENT); + when(servletContext.getResource("/scm")).thenAnswer(invocation -> new URL("https://scm-manager.org")); + URL url = resourceLoader.getResource("/scm"); + URL secondUrl = resourceLoader.getResource("/scm"); + assertNotSame(url, secondUrl); + } + /** * Method description * From 7a60e976a10cf423e0d3df4751e4ba869e0009fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 15:05:59 +0100 Subject: [PATCH 27/44] change name of programming language in header --- .../main/java/sonia/scm/api/v2/resources/ContentResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java index dc7c305823..99ed29e6f6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java @@ -136,7 +136,7 @@ public class ContentResource { private void appendContentHeader(String path, byte[] head, Response.ResponseBuilder responseBuilder) { ContentType contentType = ContentTypes.detect(path, head); responseBuilder.header("Content-Type", contentType.getRaw()); - contentType.getLanguage().ifPresent(language -> responseBuilder.header("Language", language)); + contentType.getLanguage().ifPresent(language -> responseBuilder.header("X-Programming-Language", language)); } private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException, PathNotFoundException, RevisionNotFoundException { From 17f1d9aa824bd5732c79c3d66a97bf90da34d3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 29 Oct 2018 15:21:48 +0100 Subject: [PATCH 28/44] Correct link --- scm-ui/src/repos/components/list/RepositoryEntry.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/components/list/RepositoryEntry.js b/scm-ui/src/repos/components/list/RepositoryEntry.js index ef1df0d3d9..bc170144aa 100644 --- a/scm-ui/src/repos/components/list/RepositoryEntry.js +++ b/scm-ui/src/repos/components/list/RepositoryEntry.js @@ -1,9 +1,9 @@ //@flow import React from "react"; -import {Link} from "react-router-dom"; +import { Link } from "react-router-dom"; import injectSheet from "react-jss"; -import type {Repository} from "@scm-manager/ui-types"; -import {DateFromNow} from "@scm-manager/ui-components"; +import type { Repository } from "@scm-manager/ui-types"; +import { DateFromNow } from "@scm-manager/ui-components"; import RepositoryEntryLink from "./RepositoryEntryLink"; import classNames from "classnames"; import RepositoryAvatar from "./RepositoryAvatar"; @@ -45,7 +45,7 @@ class RepositoryEntry extends React.Component { return ( ); } From e67f68c364da6ca231c6fe149a3773dd8ea1faaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 15:27:21 +0100 Subject: [PATCH 29/44] use correct header --- scm-ui/src/repos/sources/components/content/SourcecodeViewer.js | 2 +- .../repos/sources/components/content/SourcecodeViewer.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js index 648c99d29a..d679c3be3d 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js @@ -111,7 +111,7 @@ export function getProgrammingLanguage(url: string) { return apiClient .head(url) .then(response => { - return { language: response.headers.get("Language") }; + return { language: response.headers.get("X-Programming-Language") }; }) .catch(err => { return { error: err }; diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js index b132721e3b..68c9cba00a 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js @@ -25,7 +25,7 @@ describe("get content", () => { it("should return language", done => { let headers = { - Language: "JAVA" + "X-Programming-Language": "JAVA" }; fetchMock.head("/api/v2" + CONTENT_URL, { From dd0eda6c4b5ee297f769095959762460d825fd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 15:57:31 +0100 Subject: [PATCH 30/44] check language for deciding which viewer to see --- .../components/content/SourcecodeViewer.js | 33 +---------------- .../content/SourcecodeViewer.test.js | 18 +-------- .../src/repos/sources/containers/Content.js | 37 ++++++++++++------- .../repos/sources/containers/Content.test.js | 4 +- 4 files changed, 29 insertions(+), 63 deletions(-) diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js index d679c3be3d..c19338e639 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.js @@ -10,12 +10,11 @@ import { arduinoLight } from "react-syntax-highlighter/styles/hljs"; type Props = { t: string => string, file: File, - contentType: string + language: string }; type State = { content: string, - language: string, error: Error, hasError: boolean, loaded: boolean @@ -27,7 +26,6 @@ class SourcecodeViewer extends React.Component { this.state = { content: "", - language: "", error: new Error(), hasError: false, loaded: false @@ -36,22 +34,6 @@ class SourcecodeViewer extends React.Component { componentDidMount() { const { file } = this.props; - getProgrammingLanguage(file._links.self.href) - .then(result => { - if (result.error) { - this.setState({ - ...this.state, - hasError: true, - error: result.error - }); - } else { - this.setState({ - ...this.state, - language: result.language - }); - } - }) - .catch(err => {}); getContent(file._links.self.href) .then(result => { if (result.error) { @@ -77,7 +59,7 @@ class SourcecodeViewer extends React.Component { const error = this.state.error; const hasError = this.state.hasError; const loaded = this.state.loaded; - const language = this.state.language; + const language = this.props.language; if (hasError) { return ; @@ -107,17 +89,6 @@ export function getLanguage(language: string) { return language.toLowerCase(); } -export function getProgrammingLanguage(url: string) { - return apiClient - .head(url) - .then(response => { - return { language: response.headers.get("X-Programming-Language") }; - }) - .catch(err => { - return { error: err }; - }); -} - export function getContent(url: string) { return apiClient .get(url) diff --git a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js index 68c9cba00a..11e701f626 100644 --- a/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js +++ b/scm-ui/src/repos/sources/components/content/SourcecodeViewer.test.js @@ -2,8 +2,7 @@ import fetchMock from "fetch-mock"; import { getContent, - getLanguage, - getProgrammingLanguage + getLanguage } from "./SourcecodeViewer"; describe("get content", () => { @@ -22,21 +21,6 @@ describe("get content", () => { done(); }); }); - - it("should return language", done => { - let headers = { - "X-Programming-Language": "JAVA" - }; - - fetchMock.head("/api/v2" + CONTENT_URL, { - headers - }); - - getProgrammingLanguage(CONTENT_URL).then(content => { - expect(content.language).toBe("JAVA"); - done(); - }); - }); }); describe("get correct language type", () => { diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index c6c3cecd30..4a66eb3670 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -33,8 +33,10 @@ type Props = { type State = { contentType: string, + language: string, error: Error, - hasError: boolean + hasError: boolean, + loaded: boolean }; const styles = { @@ -49,8 +51,10 @@ class Content extends React.Component { this.state = { contentType: "", + language: "", error: new Error(), - hasError: false + hasError: false, + loaded: false }; } @@ -62,12 +66,15 @@ class Content extends React.Component { this.setState({ ...this.state, hasError: true, - error: result.error + error: result.error, + loaded: true }); } else { this.setState({ ...this.state, - contentType: result.type + contentType: result.type, + language: result.language, + loaded: true }); } }) @@ -108,17 +115,15 @@ class Content extends React.Component { } showContent() { - const { file, revision } = this.props; + const { file } = this.props; const contentType = this.state.contentType; - if (contentType.startsWith("image")) { + const language = this.state.language; + if (contentType.startsWith("image/")) { return ; - } else if ( - contentType.startsWith("text") || - contentType.startsWith("application") - ) { - return ; + } else if (language) { + return ; } else { - return ; + return ; } } @@ -126,8 +131,9 @@ class Content extends React.Component { const { file, classes } = this.props; const error = this.state.error; const hasError = this.state.hasError; + const loaded = this.state.loaded; - if (!file) { + if (!file || !loaded) { return ; } if (hasError) { @@ -159,7 +165,10 @@ export function getContentType(url: string, state: any) { return apiClient .head(url) .then(response => { - return { type: response.headers.get("Content-Type") }; + return { + type: response.headers.get("Content-Type"), + language: response.headers.get("X-Programming-Language") + }; }) .catch(err => { return { error: err }; diff --git a/scm-ui/src/repos/sources/containers/Content.test.js b/scm-ui/src/repos/sources/containers/Content.test.js index 8de2f48b18..36116b35aa 100644 --- a/scm-ui/src/repos/sources/containers/Content.test.js +++ b/scm-ui/src/repos/sources/containers/Content.test.js @@ -12,7 +12,8 @@ describe("get content type", () => { it("should return content", done => { let headers = { - "Content-Type": "application/text" + "Content-Type": "application/text", + "X-Programming-Language": "JAVA" }; fetchMock.head("/api/v2" + CONTENT_URL, { @@ -21,6 +22,7 @@ describe("get content type", () => { getContentType(CONTENT_URL).then(content => { expect(content.type).toBe("application/text"); + expect(content.language).toBe("JAVA"); done(); }); }); From f87cd922374bb75c3671659e1226bfb37d328f77 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 29 Oct 2018 16:35:53 +0100 Subject: [PATCH 31/44] added type definitions for react-i18next --- .../flow-typed/npm/react-i18next_v7.x.x.js | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 scm-ui-components/packages/ui-components/flow-typed/npm/react-i18next_v7.x.x.js diff --git a/scm-ui-components/packages/ui-components/flow-typed/npm/react-i18next_v7.x.x.js b/scm-ui-components/packages/ui-components/flow-typed/npm/react-i18next_v7.x.x.js new file mode 100644 index 0000000000..93d27674d7 --- /dev/null +++ b/scm-ui-components/packages/ui-components/flow-typed/npm/react-i18next_v7.x.x.js @@ -0,0 +1,95 @@ +// flow-typed signature: 65d42f62f8de603dcc631ea5a6b00580 +// flow-typed version: f3f13164e0/react-i18next_v7.x.x/flow_>=v0.64.x + +declare module "react-i18next" { + declare type TFunction = (key?: ?string, data?: ?Object) => string; + + declare type TranslatorProps = {| + t: TFunction, + i18nLoadedAt: Date, + i18n: Object + |}; + + declare type TranslatorPropsVoid = { + t: TFunction | void, + i18nLoadedAt: Date | void, + i18n: Object | void + }; + + declare type Translator> = ( + WrappedComponent: Component + ) => React$ComponentType< + $Diff, TranslatorPropsVoid> + >; + + declare type TranslateOptions = $Shape<{ + wait: boolean, + nsMode: "default" | "fallback", + bindi18n: false | string, + bindStore: false | string, + withRef: boolean, + translateFuncName: string, + i18n: Object, + usePureComponent: boolean + }>; + + declare function translate>( + namespaces?: | string + | Array + | (($Diff) => string | Array), + options?: TranslateOptions + ): Translator; + + declare type I18nProps = { + i18n?: Object, + ns?: string | Array, + children: (t: TFunction, { i18n: Object, t: TFunction }) => React$Node, + initialI18nStore?: Object, + initialLanguage?: string + }; + declare var I18n: React$ComponentType; + + declare type InterpolateProps = { + className?: string, + dangerouslySetInnerHTMLPartElement?: string, + i18n?: Object, + i18nKey?: string, + options?: Object, + parent?: string, + style?: Object, + t?: TFunction, + useDangerouslySetInnerHTML?: boolean + }; + declare var Interpolate: React$ComponentType; + + declare type TransProps = { + count?: number, + parent?: string, + i18n?: Object, + i18nKey?: string, + t?: TFunction + }; + declare var Trans: React$ComponentType; + + declare type ProviderProps = { i18n: Object, children: React$Element<*> }; + declare var I18nextProvider: React$ComponentType; + + declare type NamespacesProps = { + components: Array>, + i18n: { loadNamespaces: Function } + }; + declare function loadNamespaces(props: NamespacesProps): Promise; + + declare var reactI18nextModule: { + type: "3rdParty", + init: (instance: Object) => void + }; + + declare function setDefaults(options: TranslateOptions): void; + + declare function getDefaults(): TranslateOptions; + + declare function getI18n(): Object; + + declare function setI18n(instance: Object): void; +} From 8a10ac27e351b4428678a11e6913f126968b316e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 29 Oct 2018 16:38:11 +0100 Subject: [PATCH 32/44] update ui-bundler to 0.0.19 and smp-maven-plugin to 1.0.0-alpha-3 --- scm-plugins/pom.xml | 95 ++++--------------- scm-plugins/scm-git-plugin/package.json | 7 +- scm-plugins/scm-git-plugin/pom.xml | 40 +++----- scm-plugins/scm-git-plugin/yarn.lock | 6 +- scm-plugins/scm-hg-plugin/package.json | 2 +- scm-plugins/scm-hg-plugin/pom.xml | 42 +++----- scm-plugins/scm-hg-plugin/yarn.lock | 7 +- scm-plugins/scm-legacy-plugin/pom.xml | 17 +++- scm-plugins/scm-svn-plugin/package.json | 2 +- scm-plugins/scm-svn-plugin/pom.xml | 42 +++----- scm-plugins/scm-svn-plugin/yarn.lock | 6 +- .../packages/ui-components/package.json | 2 +- .../packages/ui-components/yarn.lock | 6 +- .../packages/ui-types/package.json | 2 +- scm-ui-components/packages/ui-types/yarn.lock | 6 +- scm-ui/package.json | 2 +- scm-ui/yarn.lock | 6 +- 17 files changed, 98 insertions(+), 192 deletions(-) diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 16a74549e5..94d751dff7 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -113,92 +113,35 @@ sonia.scm.maven smp-maven-plugin - 1.0.0-alpha-2 + 1.0.0-alpha-3 true - true + + @scm-manager/ui-types + @scm-manager/ui-components + - - - fix-descriptor - process-resources - - fix-descriptor - - - - append-dependencies - process-classes - - append-dependencies - - - - - com.github.sdorra - buildfrontend-maven-plugin - - - ${nodejs.version} - - - YARN - ${yarn.version} - - false - - - - - install - process-resources - - install - - - - build - compile - - run - - - - + + com.github.sdorra + buildfrontend-maven-plugin + + + ${nodejs.version} + + + YARN + ${yarn.version} + + false + + - - release - - - - - sonia.maven - web-compressor - 1.4 - - - compile - - compress-directory - - - - - true - ${project.build.directory}/classes - - - - - - - doc diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 8f0ab90d34..602d6d2ff0 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -3,12 +3,15 @@ "license" : "BSD-3-Clause", "main": "src/main/js/index.js", "scripts": { - "build": "ui-bundler plugin" + "build": "ui-bundler plugin", + "watch": "ui-bundler plugin -w", + "lint": "ui-bundler lint", + "flow": "flow check" }, "dependencies": { "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.17" + "@scm-manager/ui-bundler": "^0.0.19" } } diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 11e2a40bd0..bf8769daca 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -43,11 +43,20 @@ - - + + sonia.scm.maven + smp-maven-plugin + true + + true + + + + + org.apache.maven.plugins maven-jar-plugin @@ -61,33 +70,6 @@ - - com.github.sdorra - buildfrontend-maven-plugin - - - link-ui-types - process-sources - - install-link - - - @scm-manager/ui-types - - - - link-ui-components - process-sources - - install-link - - - @scm-manager/ui-components - - - - - diff --git a/scm-plugins/scm-git-plugin/yarn.lock b/scm-plugins/scm-git-plugin/yarn.lock index c1abfd6640..b85f672a8e 100644 --- a/scm-plugins/scm-git-plugin/yarn.lock +++ b/scm-plugins/scm-git-plugin/yarn.lock @@ -707,9 +707,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" +"@scm-manager/ui-bundler@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index a2ebd0e6ad..1398432412 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.17" + "@scm-manager/ui-bundler": "^0.0.19" } } diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 3da97d09e8..8dcc653642 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -31,8 +31,6 @@ - - @@ -54,7 +52,18 @@ true - + + + sonia.scm.maven + smp-maven-plugin + true + + true + + + + + org.apache.maven.plugins maven-jar-plugin @@ -68,33 +77,6 @@ - - com.github.sdorra - buildfrontend-maven-plugin - - - link-ui-types - process-sources - - install-link - - - @scm-manager/ui-types - - - - link-ui-components - process-sources - - install-link - - - @scm-manager/ui-components - - - - - diff --git a/scm-plugins/scm-hg-plugin/yarn.lock b/scm-plugins/scm-hg-plugin/yarn.lock index c3e8cc476f..70d4169198 100644 --- a/scm-plugins/scm-hg-plugin/yarn.lock +++ b/scm-plugins/scm-hg-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" +"@scm-manager/ui-bundler@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -660,6 +660,7 @@ browserify-css "^0.14.0" colors "^1.3.1" commander "^2.17.1" + connect-history-api-fallback "^1.5.0" eslint "^5.4.0" eslint-config-react-app "^2.1.0" eslint-plugin-flowtype "^2.50.0" diff --git a/scm-plugins/scm-legacy-plugin/pom.xml b/scm-plugins/scm-legacy-plugin/pom.xml index 31d8422fbf..52aff51dd2 100644 --- a/scm-plugins/scm-legacy-plugin/pom.xml +++ b/scm-plugins/scm-legacy-plugin/pom.xml @@ -23,4 +23,19 @@ - \ No newline at end of file + + + + + + sonia.scm.maven + smp-maven-plugin + true + + true + + + + + + diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index b05332e6ca..1798c0b2e5 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.17" + "@scm-manager/ui-bundler": "^0.0.19" } } diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 6d99ff713c..395def8c27 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -37,11 +37,19 @@ - - - + + + sonia.scm.maven + smp-maven-plugin + + true + + + + + org.apache.maven.plugins maven-jar-plugin @@ -55,34 +63,6 @@ - - com.github.sdorra - buildfrontend-maven-plugin - - - link-ui-types - process-sources - - install-link - - - @scm-manager/ui-types - - - - link-ui-components - process-sources - - install-link - - - @scm-manager/ui-components - - - - - - diff --git a/scm-plugins/scm-svn-plugin/yarn.lock b/scm-plugins/scm-svn-plugin/yarn.lock index c3e8cc476f..977d642f6f 100644 --- a/scm-plugins/scm-svn-plugin/yarn.lock +++ b/scm-plugins/scm-svn-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" +"@scm-manager/ui-bundler@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" diff --git a/scm-ui-components/packages/ui-components/package.json b/scm-ui-components/packages/ui-components/package.json index 096a8636b3..3d52d16173 100644 --- a/scm-ui-components/packages/ui-components/package.json +++ b/scm-ui-components/packages/ui-components/package.json @@ -12,7 +12,7 @@ "eslint-fix": "eslint src --fix" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.17", + "@scm-manager/ui-bundler": "^0.0.19", "create-index": "^2.3.0", "enzyme": "^3.5.0", "enzyme-adapter-react-16": "^1.3.1", diff --git a/scm-ui-components/packages/ui-components/yarn.lock b/scm-ui-components/packages/ui-components/yarn.lock index b4b0cc32a8..0200fdd1bf 100644 --- a/scm-ui-components/packages/ui-components/yarn.lock +++ b/scm-ui-components/packages/ui-components/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" +"@scm-manager/ui-bundler@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" diff --git a/scm-ui-components/packages/ui-types/package.json b/scm-ui-components/packages/ui-types/package.json index 0701870562..122b1ba504 100644 --- a/scm-ui-components/packages/ui-types/package.json +++ b/scm-ui-components/packages/ui-types/package.json @@ -14,7 +14,7 @@ "check": "flow check" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.17" + "@scm-manager/ui-bundler": "^0.0.19" }, "browserify": { "transform": [ diff --git a/scm-ui-components/packages/ui-types/yarn.lock b/scm-ui-components/packages/ui-types/yarn.lock index 58b579d6ae..4f7257e8bb 100644 --- a/scm-ui-components/packages/ui-types/yarn.lock +++ b/scm-ui-components/packages/ui-types/yarn.lock @@ -707,9 +707,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" +"@scm-manager/ui-bundler@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" diff --git a/scm-ui/package.json b/scm-ui/package.json index de42ff2a36..e21c2e4739 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -43,7 +43,7 @@ "pre-commit": "jest && flow && eslint src" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.17", + "@scm-manager/ui-bundler": "^0.0.19", "copyfiles": "^2.0.0", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 18b198622c..5e0fff1bd4 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -645,9 +645,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" +"@scm-manager/ui-bundler@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" From 40559dab8b04ce55027e3ed9241131ce91e49840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 29 Oct 2018 16:45:32 +0100 Subject: [PATCH 33/44] refactoring regarding design --- .../src/repos/sources/containers/Content.js | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 4a66eb3670..72964f1952 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -16,6 +16,7 @@ import DownloadViewer from "../components/content/DownloadViewer"; import FileSize from "../components/FileSize"; import injectSheet from "react-jss"; import classNames from "classnames"; +import RepositoryEntry from "../../components/list/RepositoryEntry"; type Props = { t: string => string, @@ -36,12 +37,16 @@ type State = { language: string, error: Error, hasError: boolean, - loaded: boolean + loaded: boolean, + collapsed: boolean }; const styles = { toCenterContent: { display: "block" + }, + pointer: { + cursor: "pointer" } }; @@ -54,7 +59,8 @@ class Content extends React.Component { language: "", error: new Error(), hasError: false, - loaded: false + loaded: false, + collapsed: false }; } @@ -81,8 +87,15 @@ class Content extends React.Component { .catch(err => {}); } + toggleCollapse = () => { + this.setState(prevState => ({ + collapsed: !prevState.collapsed + })); + }; + showHeader() { - const { file, revision } = this.props; + const { file, revision, classes } = this.props; + const collapsed = this.state.collapsed; const date = ; const description = file.description ? (

@@ -97,23 +110,36 @@ class Content extends React.Component {

) : null; const branch = "[" + revision + "]"; + const icon = collapsed ? "fa-angle-right" : "fa-angle-down"; return ( -
-
-
-

{file.name}

-
-
{branch}
-
+
-
{description}
-
{date}
+
+ +
+
+
{file.name}
+
+
{date} + Size
-
+ ); } + showMoreInformation() { + const collapsed = this.state.collapsed; + const { classes } = this.props; + if (!collapsed) { + return ( +
+ "Filename": ... "Path": ... "Branch" ... +
+ ); + } + return null; + } + showContent() { const { file } = this.props; const contentType = this.state.contentType; @@ -121,7 +147,7 @@ class Content extends React.Component { if (contentType.startsWith("image/")) { return ; } else if (language) { - return ; + return ; } else { return ; } @@ -142,16 +168,14 @@ class Content extends React.Component { const header = this.showHeader(); const content = this.showContent(); + const moreInformation = this.showMoreInformation(); const fileSize = file.directory ? "" : ; return (
- {header}