Render images from repository correctly

This adds a markdown renderer for images, so that images
that are referenced by their repository path are resolved
correctly. In this case, the content rest endpoint is
rendered as source url. For this, two new contexts
(RepositoryContext and RepositoryRevisionContext)
have been added, that make the repository and the
current revision available, so that the content url can
be resolved properly. These new contexts may be used
by plugins like the scm-readme-plugin.

Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>

Reviewed-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
Rene Pfeuffer
2022-12-19 10:12:01 +01:00
committed by SCM-Manager
parent 6ba792e5bc
commit f2f2f29791
22 changed files with 604 additions and 216 deletions

View File

@@ -27,7 +27,7 @@ import { Changeset, Repository } from "@scm-manager/ui-types";
import { ErrorPage, Loading } from "@scm-manager/ui-components";
import ChangesetDetails from "../components/changesets/ChangesetDetails";
import { FileControlFactory } from "@scm-manager/ui-components";
import { useChangeset } from "@scm-manager/ui-api";
import { RepositoryRevisionContextProvider, useChangeset } from "@scm-manager/ui-api";
import { useParams } from "react-router-dom";
type Props = {
@@ -53,11 +53,13 @@ const ChangesetView: FC<Props> = ({ repository, fileControlFactoryFactory }) =>
}
return (
<ChangesetDetails
changeset={changeset}
repository={repository}
fileControlFactory={fileControlFactoryFactory && fileControlFactoryFactory(changeset)}
/>
<RepositoryRevisionContextProvider revision={changeset.id}>
<ChangesetDetails
changeset={changeset}
repository={repository}
fileControlFactory={fileControlFactoryFactory && fileControlFactoryFactory(changeset)}
/>
</RepositoryRevisionContextProvider>
);
};

View File

@@ -28,6 +28,7 @@ import { Repository, Branch } from "@scm-manager/ui-types";
import CodeActionBar from "../codeSection/components/CodeActionBar";
import { urls } from "@scm-manager/ui-components";
import Changesets from "./Changesets";
import { RepositoryRevisionContextProvider } from "@scm-manager/ui-api";
type Props = {
repository: Repository;
@@ -66,7 +67,7 @@ const ChangesetRoot: FC<Props> = ({ repository, baseUrl, branches, selectedBranc
};
return (
<>
<RepositoryRevisionContextProvider revision={selectedBranch}>
<CodeActionBar
branches={branches}
selectedBranch={!isBranchAvailable() ? selectedBranch : defaultBranch?.name}
@@ -76,7 +77,7 @@ const ChangesetRoot: FC<Props> = ({ repository, baseUrl, branches, selectedBranc
<Route path={`${url}/:page?`}>
<Changesets repository={repository} branch={branches?.filter(b => b.name === selectedBranch)[0]} url={url} />
</Route>
</>
</RepositoryRevisionContextProvider>
);
};

View File

@@ -60,7 +60,7 @@ import SourceExtensions from "../sources/containers/SourceExtensions";
import TagsOverview from "../tags/container/TagsOverview";
import CompareRoot from "../compare/CompareRoot";
import TagRoot from "../tags/container/TagRoot";
import { useIndexLinks, useNamespaceAndNameContext, useRepository } from "@scm-manager/ui-api";
import { RepositoryContextProvider, useIndexLinks, useNamespaceAndNameContext, useRepository } from "@scm-manager/ui-api";
import styled from "styled-components";
import { useShortcut } from "@scm-manager/ui-shortcuts";
@@ -265,149 +265,151 @@ const RepositoryRoot = () => {
return (
<StateMenuContextProvider>
<Page
title={titleComponent}
documentTitle={`${repository.namespace}/${repository.name}`}
afterTitle={
<MobileWrapped className="is-flex is-align-items-center">
<ExtensionPoint name="repository.afterTitle" props={{ repository }} />
<TagGroup className="has-text-weight-bold">
<RepositoryFlags repository={repository} tooltipLocation="bottom" />
</TagGroup>
</MobileWrapped>
}
>
{modal}
<CustomQueryFlexWrappedColumns>
<PrimaryContentColumn>
<Switch>
<Redirect exact from={urls.escapeUrlForRoute(match.url)} to={urls.escapeUrlForRoute(redirectedUrl)} />
<RepositoryContextProvider repository={repository}>
<Page
title={titleComponent}
documentTitle={`${repository.namespace}/${repository.name}`}
afterTitle={
<MobileWrapped className="is-flex is-align-items-center">
<ExtensionPoint name="repository.afterTitle" props={{ repository }} />
<TagGroup className="has-text-weight-bold">
<RepositoryFlags repository={repository} tooltipLocation="bottom" />
</TagGroup>
</MobileWrapped>
}
>
{modal}
<CustomQueryFlexWrappedColumns>
<PrimaryContentColumn>
<Switch>
<Redirect exact from={urls.escapeUrlForRoute(match.url)} to={urls.escapeUrlForRoute(redirectedUrl)} />
{/* redirect pre 2.0.0-rc2 links */}
<Redirect from={`${escapedUrl}/changeset/:id`} to={`${url}/code/changeset/:id`} />
<Redirect exact from={`${escapedUrl}/sources`} to={`${url}/code/sources`} />
<Redirect from={`${escapedUrl}/sources/:revision/:path*`} to={`${url}/code/sources/:revision/:path*`} />
<Redirect exact from={`${escapedUrl}/changesets`} to={`${url}/code/changesets`} />
<Redirect
from={`${escapedUrl}/branch/:branch/changesets`}
to={`${url}/code/branch/:branch/changesets/`}
/>
{/* redirect pre 2.0.0-rc2 links */}
<Redirect from={`${escapedUrl}/changeset/:id`} to={`${url}/code/changeset/:id`} />
<Redirect exact from={`${escapedUrl}/sources`} to={`${url}/code/sources`} />
<Redirect from={`${escapedUrl}/sources/:revision/:path*`} to={`${url}/code/sources/:revision/:path*`} />
<Redirect exact from={`${escapedUrl}/changesets`} to={`${url}/code/changesets`} />
<Redirect
from={`${escapedUrl}/branch/:branch/changesets`}
to={`${url}/code/branch/:branch/changesets/`}
/>
<Route path={`${escapedUrl}/info`} exact>
<RepositoryDetails repository={repository} />
</Route>
<Route path={`${escapedUrl}/settings/general`}>
<EditRepo repository={repository} />
</Route>
<Route path={`${escapedUrl}/settings/permissions`}>
<Permissions namespaceOrRepository={repository} />
</Route>
<Route exact path={`${escapedUrl}/code/changeset/:id`}>
<ChangesetView repository={repository} fileControlFactoryFactory={fileControlFactoryFactory} />
</Route>
<Route path={`${escapedUrl}/code/sourceext/:extension`} exact={true}>
<SourceExtensions repository={repository} />
</Route>
<Route path={`${escapedUrl}/code/sourceext/:extension/:revision/:path*`}>
<SourceExtensions repository={repository} baseUrl={`${url}/code/sources`} />
</Route>
<Route path={`${escapedUrl}/code`}>
<CodeOverview baseUrl={`${url}/code`} repository={repository} />
</Route>
<Route path={`${escapedUrl}/branch/:branch`}>
<BranchRoot repository={repository} />
</Route>
<Route path={`${escapedUrl}/branches`} exact={true}>
<BranchesOverview repository={repository} baseUrl={`${url}/branch`} />
</Route>
<Route path={`${escapedUrl}/branches/create`}>
<CreateBranch repository={repository} />
</Route>
<Route path={`${escapedUrl}/tag/:tag`}>
<TagRoot repository={repository} baseUrl={`${url}/tag`} />
</Route>
<Route path={`${escapedUrl}/tags`} exact={true}>
<TagsOverview repository={repository} baseUrl={`${url}/tag`} />
</Route>
<Route path={`${escapedUrl}/compare/:sourceType/:sourceName`}>
<CompareRoot repository={repository} baseUrl={`${url}/compare`} />
</Route>
<ExtensionPoint<extensionPoints.RepositoryRoute>
name="repository.route"
props={{
repository,
url: urls.escapeUrlForRoute(url),
indexLinks,
}}
renderAll={true}
/>
</Switch>
</PrimaryContentColumn>
<SecondaryNavigationColumn>
<SecondaryNavigation label={t("repositoryRoot.menu.navigationLabel")}>
<ExtensionPoint<extensionPoints.RepositoryNavigationTopLevel>
name="repository.navigation.topLevel"
props={extensionProps}
renderAll={true}
/>
<NavLink
to={`${url}/info`}
icon="fas fa-info-circle"
label={t("repositoryRoot.menu.informationNavLink")}
title={t("repositoryRoot.menu.informationNavLink")}
/>
<RepositoryNavLink
repository={repository}
linkName="branches"
to={`${url}/branches/`}
icon="fas fa-code-branch"
label={t("repositoryRoot.menu.branchesNavLink")}
activeWhenMatch={matchesBranches}
activeOnlyWhenExact={false}
title={t("repositoryRoot.menu.branchesNavLink")}
/>
<RepositoryNavLink
repository={repository}
linkName="tags"
to={`${url}/tags/`}
icon="fas fa-tags"
label={t("repositoryRoot.menu.tagsNavLink")}
activeWhenMatch={matchesTags}
activeOnlyWhenExact={false}
title={t("repositoryRoot.menu.tagsNavLink")}
/>
<RepositoryNavLink
repository={repository}
linkName={codeLinkname}
to={evaluateDestinationForCodeLink()}
icon="fas fa-code"
label={t("repositoryRoot.menu.sourcesNavLink")}
activeWhenMatch={matchesCode}
activeOnlyWhenExact={false}
title={t("repositoryRoot.menu.sourcesNavLink")}
/>
<ExtensionPoint<extensionPoints.RepositoryNavigation>
name="repository.navigation"
props={extensionProps}
renderAll={true}
/>
<SubNavigation
to={`${url}/settings/general`}
label={t("repositoryRoot.menu.settingsNavLink")}
title={t("repositoryRoot.menu.settingsNavLink")}
>
<EditRepoNavLink repository={repository} editUrl={`${url}/settings/general`} />
<PermissionsNavLink permissionUrl={`${url}/settings/permissions`} repository={repository} />
<ExtensionPoint<extensionPoints.RepositorySetting>
name="repository.setting"
<Route path={`${escapedUrl}/info`} exact>
<RepositoryDetails repository={repository} />
</Route>
<Route path={`${escapedUrl}/settings/general`}>
<EditRepo repository={repository} />
</Route>
<Route path={`${escapedUrl}/settings/permissions`}>
<Permissions namespaceOrRepository={repository} />
</Route>
<Route exact path={`${escapedUrl}/code/changeset/:id`}>
<ChangesetView repository={repository} fileControlFactoryFactory={fileControlFactoryFactory} />
</Route>
<Route path={`${escapedUrl}/code/sourceext/:extension`} exact={true}>
<SourceExtensions repository={repository} />
</Route>
<Route path={`${escapedUrl}/code/sourceext/:extension/:revision/:path*`}>
<SourceExtensions repository={repository} baseUrl={`${url}/code/sources`} />
</Route>
<Route path={`${escapedUrl}/code`}>
<CodeOverview baseUrl={`${url}/code`} repository={repository} />
</Route>
<Route path={`${escapedUrl}/branch/:branch`}>
<BranchRoot repository={repository} />
</Route>
<Route path={`${escapedUrl}/branches`} exact={true}>
<BranchesOverview repository={repository} baseUrl={`${url}/branch`} />
</Route>
<Route path={`${escapedUrl}/branches/create`}>
<CreateBranch repository={repository} />
</Route>
<Route path={`${escapedUrl}/tag/:tag`}>
<TagRoot repository={repository} baseUrl={`${url}/tag`} />
</Route>
<Route path={`${escapedUrl}/tags`} exact={true}>
<TagsOverview repository={repository} baseUrl={`${url}/tag`} />
</Route>
<Route path={`${escapedUrl}/compare/:sourceType/:sourceName`}>
<CompareRoot repository={repository} baseUrl={`${url}/compare`} />
</Route>
<ExtensionPoint<extensionPoints.RepositoryRoute>
name="repository.route"
props={{
repository,
url: urls.escapeUrlForRoute(url),
indexLinks,
}}
renderAll={true}
/>
</Switch>
</PrimaryContentColumn>
<SecondaryNavigationColumn>
<SecondaryNavigation label={t("repositoryRoot.menu.navigationLabel")}>
<ExtensionPoint<extensionPoints.RepositoryNavigationTopLevel>
name="repository.navigation.topLevel"
props={extensionProps}
renderAll={true}
/>
</SubNavigation>
</SecondaryNavigation>
</SecondaryNavigationColumn>
</CustomQueryFlexWrappedColumns>
</Page>
<NavLink
to={`${url}/info`}
icon="fas fa-info-circle"
label={t("repositoryRoot.menu.informationNavLink")}
title={t("repositoryRoot.menu.informationNavLink")}
/>
<RepositoryNavLink
repository={repository}
linkName="branches"
to={`${url}/branches/`}
icon="fas fa-code-branch"
label={t("repositoryRoot.menu.branchesNavLink")}
activeWhenMatch={matchesBranches}
activeOnlyWhenExact={false}
title={t("repositoryRoot.menu.branchesNavLink")}
/>
<RepositoryNavLink
repository={repository}
linkName="tags"
to={`${url}/tags/`}
icon="fas fa-tags"
label={t("repositoryRoot.menu.tagsNavLink")}
activeWhenMatch={matchesTags}
activeOnlyWhenExact={false}
title={t("repositoryRoot.menu.tagsNavLink")}
/>
<RepositoryNavLink
repository={repository}
linkName={codeLinkname}
to={evaluateDestinationForCodeLink()}
icon="fas fa-code"
label={t("repositoryRoot.menu.sourcesNavLink")}
activeWhenMatch={matchesCode}
activeOnlyWhenExact={false}
title={t("repositoryRoot.menu.sourcesNavLink")}
/>
<ExtensionPoint<extensionPoints.RepositoryNavigation>
name="repository.navigation"
props={extensionProps}
renderAll={true}
/>
<SubNavigation
to={`${url}/settings/general`}
label={t("repositoryRoot.menu.settingsNavLink")}
title={t("repositoryRoot.menu.settingsNavLink")}
>
<EditRepoNavLink repository={repository} editUrl={`${url}/settings/general`} />
<PermissionsNavLink permissionUrl={`${url}/settings/permissions`} repository={repository} />
<ExtensionPoint<extensionPoints.RepositorySetting>
name="repository.setting"
props={extensionProps}
renderAll={true}
/>
</SubNavigation>
</SecondaryNavigation>
</SecondaryNavigationColumn>
</CustomQueryFlexWrappedColumns>
</Page>
</RepositoryContextProvider>
</StateMenuContextProvider>
);
};

View File

@@ -23,7 +23,7 @@
*/
import React, { FC, useState } from "react";
import MarkdownViewer from "./MarkdownViewer";
import { File } from "@scm-manager/ui-types";
import { File, Repository } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, SyntaxHighlighter } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import { useFileContent } from "@scm-manager/ui-api";
@@ -34,9 +34,10 @@ import classNames from "classnames";
type Props = {
file: File;
basePath: string;
repository: Repository;
};
const SwitchableMarkdownViewer: FC<Props> = ({ file, basePath }) => {
const SwitchableMarkdownViewer: FC<Props> = ({ file, basePath, repository }) => {
const { isLoading, error, data: content } = useFileContent(file);
const { t } = useTranslation("repos");
const location = useLocation();
@@ -68,7 +69,13 @@ const SwitchableMarkdownViewer: FC<Props> = ({ file, basePath }) => {
</ul>
</div>
{renderMarkdown ? (
<MarkdownViewer content={content || ""} basePath={basePath} permalink={permalink} />
<MarkdownViewer
content={content || ""}
basePath={basePath}
permalink={permalink}
revision={file.revision}
repository={repository}
/>
) : (
<SyntaxHighlighter language="markdown" value={content || ""} permalink={permalink} />
)}

View File

@@ -24,7 +24,7 @@
import React, { FC, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useHistory, useLocation, useParams } from "react-router-dom";
import { useSources } from "@scm-manager/ui-api";
import { RepositoryRevisionContextProvider, useSources } from "@scm-manager/ui-api";
import { Branch, Repository } from "@scm-manager/ui-types";
import { Breadcrumb, ErrorNotification, Loading, Notification } from "@scm-manager/ui-components";
import FileTree from "../components/FileTree";
@@ -178,7 +178,7 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
};
return (
<>
<RepositoryRevisionContextProvider revision={revision}>
{hasBranchesWhenSupporting(repository) && (
<CodeActionBar
selectedBranch={selectedBranch}
@@ -193,7 +193,7 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
/>
)}
{renderPanelContent()}
</>
</RepositoryRevisionContextProvider>
);
};

View File

@@ -66,7 +66,7 @@ const SourcesView: FC<Props> = ({ file, repository, revision }) => {
if (contentType.startsWith("image/")) {
sources = <ImageViewer file={file} />;
} else if (contentType.includes("markdown") || (language && language.toLowerCase() === "markdown")) {
sources = <SwitchableMarkdownViewer file={file} basePath={basePath} />;
sources = <SwitchableMarkdownViewer file={file} basePath={basePath} repository={repository} />;
} else if (language) {
sources = <SourcecodeViewer file={file} language={language} />;
} else if (contentType.startsWith("text/")) {