feat(repository): make it possible to see readme files on repository details page@@ -1,5 +1,5 @@
{
- "_generatedAtUnix": 1663688911068,
+ "_generatedAtUnix": 1663691571271,
"_hashAlgorithm": "sha1",
"_version": 2,
"islands": {
@@ -26,17 +26,11 @@
"pathSource": "./app/islands/RepositoryTreeView.tsx",
"pathBundle": "./public/.islands/RepositoryTreeView.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryTreeView.bundle.js.map"
- },
- "SideMenu": {
- "hash": "5d01374da1cbee58e081b9022aa56f0624209e27",
- "pathSource": "./app/islands/SideMenu.tsx",
- "pathBundle": "./public/.islands/SideMenu.bundle.js",
- "pathSourceMap": "./public/.islands/SideMenu.bundle.js.map"
}
},
"views": {
"HomeView": {
- "hash": "68b106be682a83650061c345bd5d7ac084fd9768",
+ "hash": "f1f68abb235c063ad79a1921fc4be2e8f8b2597a",
"pathSource": "./app/views/HomeView.tsx"
},
"InternalErrorView": {
@@ -44,31 +38,31 @@
"pathSource": "./app/views/InternalErrorView.tsx"
},
"DashboardView": {
- "hash": "94d966b2d5954a66b0be59fda234c04e55c41d60",
+ "hash": "9b287b43216a2a41bb961737801dfd950945b6af",
"pathSource": "./app/views/auth/DashboardView.tsx"
},
"LoginView": {
- "hash": "ca64e50d382088fd54ac2617a983d369c2fe8820",
+ "hash": "6d69b3db6e0c92ebcc9d7611a0275e06dcd18379",
"pathSource": "./app/views/auth/LoginView.tsx"
},
"RegisterView": {
- "hash": "3e2c7053b529624ed6cfa9ea8631b4480bd9775b",
+ "hash": "6c5d677d4ba6710ec60f043c03a0e08cd2384de3",
"pathSource": "./app/views/auth/RegisterView.tsx"
},
"RepositoryBrowserView": {
- "hash": "49cc11b5e655c6b7d7bf4f36a36226160613939b",
+ "hash": "e6493e8fedabe1f0835eee239ce726dad7f19af6",
"pathSource": "./app/views/repository/RepositoryBrowserView.tsx"
},
"RepositoryCreateView": {
- "hash": "cb01e6394094a287f6084a43d556277abbbc5b05",
+ "hash": "f141b710674ecd55db0fa429ab73901e30001a39",
"pathSource": "./app/views/repository/RepositoryCreateView.tsx"
},
"RepositoryDetailsView": {
- "hash": "c8490681d1af914b28aeaee4d454260cb3f1bfbb",
+ "hash": "1cd767882e2f20c35576fb1b77432c5ea01231de",
"pathSource": "./app/views/repository/RepositoryDetailsView.tsx"
},
"RepositoryExploreView": {
- "hash": "c4c1d08c876232054af3318739a6d1091510afb2",
+ "hash": "06685120a2cdbcb6671ac0436054a3b1345594ef",
"pathSource": "./app/views/repository/RepositoryExploreView.tsx"
}
}
@@ -1,33 +1,24 @@
-// 3rd-party
+// 1st-party
import { removeCommentsAndSpacing } from "@ethicdevs/react-monolith";
+// 3rd-party
import React, { FC } from "react";
import styled, { css } from "styled-components";
-
// app
import type { CommonViewProps, WithThemeSchemeProp } from "../types";
import { NamedColors } from "../utils/style";
-
-import InstantRouterIndicator from "../islands/InstantRouterIndicator";
-import SideMenu from "../islands/SideMenu";
import { PageHeader } from "./PageHeader";
+// app islands
+import InstantRouterIndicator from "../islands/InstantRouterIndicator";
interface LayoutProps extends CommonViewProps {
- showSideMenu?: boolean;
+ foo?: boolean;
}
const BRANDLINE_HEIGHT = 4;
const HEADER_HEIGHT = 72;
-const SIDE_MENU_WIDTH = 320;
export const Layout: FC<LayoutProps & WithThemeSchemeProp> = (commonProps) => {
- const {
- children,
- menuDefinition,
- currentPageSlug = undefined,
- currentSectionSlug = undefined,
- showSideMenu = menuDefinition != null,
- themeScheme,
- } = commonProps;
+ const { children, themeScheme } = commonProps;
const sharedProps = {
themeScheme,
@@ -65,32 +56,14 @@ export const Layout: FC<LayoutProps & WithThemeSchemeProp> = (commonProps) => {
<div data-islandid={`${InstantRouterIndicator.name}$$0`}>
<InstantRouterIndicator />
</div>
- {/*<StyledBrandLine {...sharedProps} />*/}
<StyledPageHeaderWrapper {...sharedProps}>
<PageHeader commonProps={commonProps} themeScheme={themeScheme} />
</StyledPageHeaderWrapper>
<StyledPageWrapper>
- {menuDefinition && showSideMenu && (
- <StyledSideMenuWrapper
- data-islandid={`${SideMenu.name}$$0`}
- {...sharedProps}
- >
- <SideMenu
- commonProps={commonProps}
- currentSectionSlug={currentSectionSlug}
- currentPageSlug={currentPageSlug}
- menuDefinition={menuDefinition}
- themeScheme={themeScheme}
- />
- </StyledSideMenuWrapper>
- )}
<StyledChildrenWrapper {...sharedProps}>
{children}
</StyledChildrenWrapper>
</StyledPageWrapper>
- {/*<StyledPageFooterWrapper>
- <h4>Footer</h4>
- </StyledPageFooterWrapper>*/}
</StyledLayoutWrapper>
</>
);
@@ -112,17 +85,6 @@ const StyledLayoutWrapper = styled.div<WithThemeSchemeProp>`
`};
`;
-/*const StyledBrandLine = styled.div<WithThemeSchemeProp>`
- display: flex;
- width: 100%;
- height: ${BRANDLINE_HEIGHT}px;
-
- ${({ themeScheme }) => css`
- background-color: ${NamedColors.BRAND_LINE[themeScheme]};
- background-image: linear-gradient(90deg, #1a8f97 0%, #23024d 100%);
- `};
-`;*/
-
const StyledPageHeaderWrapper = styled.div<WithThemeSchemeProp>`
display: flex;
flex-flow: row nowrap;
@@ -175,29 +137,6 @@ const StyledPageWrapper = styled.div`
min-height: calc(100% - ${BRANDLINE_HEIGHT + HEADER_HEIGHT}px);
`;
-const StyledSideMenuWrapper = styled.aside<WithThemeSchemeProp>`
- display: flex;
- flex-flow: column nowrap;
- justify-content: flex-start;
- align-items: flex-start;
-
- width: ${SIDE_MENU_WIDTH}px;
- min-height: calc(100% - ${BRANDLINE_HEIGHT + HEADER_HEIGHT}px);
- max-height: calc(100vh - ${BRANDLINE_HEIGHT + HEADER_HEIGHT}px);
-
- position: sticky;
- top: ${HEADER_HEIGHT}px;
-
- overflow-y: auto;
-
- padding: 8px 0 16px 0;
-
- ${({ themeScheme }) => css`
- background-color: ${NamedColors.SIDE_MENU[themeScheme]};
- border-right: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};
- `};
-`;
-
const StyledChildrenWrapper = styled.div`
display: flex;
flex-flow: column nowrap;
@@ -227,7 +227,7 @@ const StyledCodeTag = styled.code<WithThemeSchemeProp>`
${codeElBaseCss};
${({ themeScheme }) => css`
- background-color: ${NamedColors.SIDE_MENU[themeScheme]};
+ background-color: ${NamedColors.CARD[themeScheme]};
border: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};
`};
`;
@@ -242,7 +242,7 @@ const StyledCodeBlock = styled.pre<WithThemeSchemeProp>`
${codeElBaseCss};
${({ themeScheme }) => css`
- background-color: ${NamedColors.SIDE_MENU[themeScheme]};
+ background-color: ${NamedColors.CARD[themeScheme]};
border: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};
`};
@@ -1,14 +0,0 @@
-import styled, { css } from "styled-components";
-
-import type { WithThemeSchemeProp } from "../types";
-import { NamedColors } from "../utils/style";
-
-export const MenuDivider = styled.div<WithThemeSchemeProp>`
- width: 100%;
- height: 1px;
- margin: 8px 0;
-
- ${({ themeScheme }) => css`
- background-color: ${NamedColors.BORDER_DEFAULT[themeScheme]};
- `};
-`;
@@ -1,67 +0,0 @@
-// 3rd-party
-import React, { FC } from "react";
-import styled, { css } from "styled-components";
-// app
-import type { AppThemeScheme } from "../types";
-import { NamedColors } from "../utils/style";
-
-interface MenuItemProps {
- slug: string;
- active?: boolean;
- href?: string;
- onClick?: React.MouseEventHandler<HTMLAnchorElement>;
- section?: boolean;
- themeScheme: AppThemeScheme;
- title?: string;
-}
-
-export const MenuItem: FC<MenuItemProps> = ({ children, ...props }) => {
- return <StyledMenuItem {...props}>{children}</StyledMenuItem>;
-};
-
-const StyledMenuItem = styled.a<MenuItemProps>`
- display: flex;
- flex-flow: row nowrap;
- justify-content: flex-start;
- align-items: center;
-
- min-height: 40px;
- height: 40px;
- width: 100%;
-
- ${({ themeScheme, active = false, section = false }) => css`
- padding: ${section ? "0 20px" : "0 20px 0 32px"};
-
- color: ${active || section
- ? NamedColors.TEXT_DEFAULT[themeScheme]
- : NamedColors.TEXT_MUTED[themeScheme]};
- background-color: ${active === false
- ? "transparent"
- : NamedColors.CARD_OVERLAY[themeScheme]};
-
- font-size: ${section ? 20 : 18}px;
- line-height: ${section ? 24 : 20}px;
- `};
-
- font-style: normal;
- font-weight: 600;
- text-decoration: none;
-
- transition: background-color 140ms ease-in-out 0s, color 140ms ease-in-out 0s;
-
- ${({ themeScheme, active = false }) =>
- active &&
- css`
- background-color: ${NamedColors.SIDE_MENU_ITEM_HOVER[themeScheme]};
- color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
- border-top: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};
- border-bottom: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};
- `};
-
- &:hover {
- ${({ themeScheme }) => css`
- background-color: ${NamedColors.SIDE_MENU_ITEM_HOVER[themeScheme]};
- color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
- `};
- }
-`;
@@ -4,8 +4,6 @@ export { Grid } from "./Grid";
export * as Icons from "./icons";
export { Layout } from "./Layout";
export { MarkdownToJsx } from "./MarkdownToJsx";
-export { MenuItem } from "./MenuItem";
-export { MenuDivider } from "./MenuDivider";
export { PageHeader } from "./PageHeader";
export { PageWrapper } from "./PageWrapper";
export { TextEllipsis } from "./TextEllipsis.styled";
@@ -1,13 +1,11 @@
// beware to imports in this file, they must work in both server/client sides.
-import type { AppThemeScheme, SectionsPagesIndex } from "./types";
+import type { AppThemeScheme } from "./types";
type Const = {
APP_NAME: string;
DEFAULT_THEME_SCHEME: AppThemeScheme;
- SECTIONS_WITH_PAGES: SectionsPagesIndex;
- SECTIONS_TITLES: Record<string, string>;
- SECTIONS_SUMMARIES: Record<string, string>;
+ README_FILE_NAMES: string[];
SESSION_TTL_SECONDS: number;
SSR_CACHE_MAX_SIZE_BYTES: number;
};
@@ -15,45 +13,16 @@ type Const = {
export const Const: Const = {
APP_NAME: "GitFOSS",
DEFAULT_THEME_SCHEME: "dark",
+ README_FILE_NAMES: [
+ "READ_ME",
+ "READ_ME.md",
+ "README",
+ "README.md",
+ "ReadMe",
+ "ReadMe.md",
+ "readme",
+ "readme.md",
+ ],
SESSION_TTL_SECONDS: 24 * (60 * 60), // 1 day in seconds
SSR_CACHE_MAX_SIZE_BYTES: 50_000_000, // 50Mb in bytes
- // TODO(refactor): all of this will move into folder/index.yml
- SECTIONS_WITH_PAGES: {
- ["getting-started"]: [
- "the-monolith-architecture",
- "tooling-typescript",
- "generate-project-cli",
- ],
- ["server-configuration"]: ["server-file-explained", "options"],
- ["declarative-routing"]: ["routes-file-explained", "options"],
- ["controllers"]: ["what-is-a-controller"],
- ["views-and-islands"]: ["what-is-a-view", "what-is-an-island"],
- ["seo-and-a11y"]: ["meta-tags", "accessibility"],
- ["faq-and-known-issues"]: ["islands-cannot-share-react-context"],
- },
- SECTIONS_TITLES: {
- "getting-started": "Getting Started",
- "server-configuration": "Server Configuration",
- "declarative-routing": "Declarative Routing",
- controllers: "Controllers",
- "views-and-islands": "Views and Islands",
- "seo-and-a11y": "SEO & A11Y",
- "faq-and-known-issues": "FAQ & Known Issues",
- },
- SECTIONS_SUMMARIES: {
- "getting-started":
- "Discover the concepts behind React Monolith and how to use them so they benefit to your websites and web applications.",
- "server-configuration":
- "Learn about the server file and how you can use the options it provides to develop an application that fit your needs in a fast way.",
- "declarative-routing":
- "Navigation handling couldn’t be made easier thanks to a simple JSX declarative routes file that contains all your route/controller mappings.",
- controllers:
- "Discover how you can implement your business logic on the server-side and reply by streaming React views as a response to the client request.",
- "views-and-islands":
- "Learn more about the two types ReactView and ReactIsland and how they help you to create interactivity in area where you need it.",
- "seo-and-a11y":
- "Learn how you can leverage the ViewContext to generate meta-tags that improve your search engine ranking and accessibility for your users.",
- "faq-and-known-issues":
- "Frequently Asked Questions and Known Issues around the React Monolith Framework and its underlying fastify-stream-react-views plugin (which does most of the SSR/Islands work).",
- },
};
@@ -48,6 +48,7 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
},
parentOrg,
path,
+ readmeFileContent: null,
ref,
repo,
repoHead: await repoService.getRepositoryHead(repo, ref),
@@ -2,6 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
import { AppRoute, AppRoutesParams } from "../../routes";
+import { Const } from "../../const";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
@@ -33,6 +34,17 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
return reply.status(404).callNotFound();
}
+ const readmeFiles = await repoService.isFileInRepositoryPath(
+ repo,
+ "",
+ Const.README_FILE_NAMES
+ );
+
+ const readmeFileContent =
+ readmeFiles.length >= 1
+ ? await repoService.getRepositoryFileContent(repo, readmeFiles[0])
+ : null;
+
const reqHandler = reply.makeRequestHandler(request, reply);
try {
@@ -44,6 +56,7 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
},
parentOrg,
path,
+ readmeFileContent,
ref,
repo,
repoHead: await repoService.getRepositoryHead(repo, ref),
@@ -62,6 +75,7 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
},
parentOrg,
path,
+ readmeFileContent,
ref,
repo,
repoHead: null,
@@ -1,173 +0,0 @@
-// 3rd-party
-import type { ReactIsland } from "@ethicdevs/react-monolith";
-import React, { useCallback, useState } from "react";
-import styled, { css } from "styled-components";
-// app
-import type {
- CommonProps,
- SectionsWithPages,
- WithThemeSchemeProp,
-} from "../types";
-import { MenuDivider } from "../components/MenuDivider";
-import { MenuItem } from "../components/MenuItem";
-import { NamedColors } from "../utils/style";
-
-interface SideMenuProps extends CommonProps {
- foo?: boolean;
- currentPageSlug?: string;
- currentSectionSlug?: string;
- menuDefinition: SectionsWithPages;
-}
-
-const SideMenu: ReactIsland<SideMenuProps & WithThemeSchemeProp> = ({
- menuDefinition,
- themeScheme,
- currentPageSlug = undefined,
- currentSectionSlug = undefined,
-}) => {
- const [activeSectionSlug, setActiveSectionSlug] = useState<string | null>(
- currentSectionSlug || null
- );
- const [activePageSlug, setActivePageSlug] = useState<string | null>(
- currentPageSlug || null
- );
-
- const onMenuItemSectionClick: React.MouseEventHandler<HTMLAnchorElement> =
- useCallback(
- (ev) => {
- ev.persist();
- ev.preventDefault();
-
- const windowPathname = new URL(window.location.href).pathname;
- const windowSectionSlug = windowPathname.split("/")[2];
- const linkPathname = new URL(ev.currentTarget.href).pathname;
- const linkSectionSlug = linkPathname.split("/")[2];
- const linkPageSlug = linkPathname.split("/")[3];
-
- const changePage = () => {
- let timeoutId = setTimeout(() => {
- clearTimeout(timeoutId);
- window.location.replace(linkPathname);
- }, 10);
- };
-
- if (windowSectionSlug !== linkSectionSlug) {
- setActiveSectionSlug(linkSectionSlug);
- if (activePageSlug !== linkPageSlug) {
- setActivePageSlug(linkPageSlug);
- }
- changePage();
- } else if (
- windowSectionSlug === activeSectionSlug &&
- activeSectionSlug !== linkSectionSlug
- ) {
- setActiveSectionSlug(linkSectionSlug);
- if (activePageSlug !== linkPageSlug) {
- setActivePageSlug(linkPageSlug);
- }
- changePage();
- } else {
- // toggle state
- setActiveSectionSlug((prev) =>
- prev == null ? linkSectionSlug : null
- );
- }
- },
- [
- activeSectionSlug,
- activePageSlug,
- setActiveSectionSlug,
- setActivePageSlug,
- ]
- );
-
- return (
- <div style={{ width: "100%" }}>
- {menuDefinition != null &&
- Object.entries(menuDefinition).map(
- ([sectionSlug, section], idx, arr) => (
- <React.Fragment key={sectionSlug}>
- <StyledMenuSectionContainer
- themeScheme={themeScheme}
- active={sectionSlug === activeSectionSlug}
- >
- <>
- <MenuItem
- data-section
- slug={sectionSlug}
- section
- active={sectionSlug === activeSectionSlug}
- href={`/docs/${sectionSlug}/${
- Object.keys(section.pagesBySlug)[0]
- }`}
- onClick={onMenuItemSectionClick}
- themeScheme={themeScheme}
- title={section.title}
- >
- {section.title}
- </MenuItem>
- {activeSectionSlug === sectionSlug &&
- section.pagesBySlug != null &&
- Object.entries(section.pagesBySlug).map(
- ([pageSlug, page]) => (
- <MenuItem
- key={pageSlug}
- slug={pageSlug}
- active={
- sectionSlug === activeSectionSlug &&
- pageSlug === activePageSlug
- }
- href={`/docs/${sectionSlug}/${pageSlug}`}
- themeScheme={themeScheme}
- title={page.metas.title}
- >
- {page.metas.title}
- </MenuItem>
- )
- )}
- </>
- </StyledMenuSectionContainer>
- {idx < arr.length - 1 && (
- <MenuDivider themeScheme={themeScheme} />
- )}
- </React.Fragment>
- )
- )}
- </div>
- );
-};
-
-const hoverOrActiveCss = css<{ active?: boolean } & WithThemeSchemeProp>`
- ${({ themeScheme }) => css`
- background-color: ${NamedColors.SIDE_MENU_SECTION_HOVER[themeScheme]};
- `};
-
- & > [data-section] {
- ${({ themeScheme, active = false }) => css`
- border-bottom: 1px solid
- ${active
- ? NamedColors.BORDER_DEFAULT[themeScheme]
- : NamedColors.SIDE_MENU_ITEM_HOVER[themeScheme]};
- background-color: ${NamedColors.SIDE_MENU_ITEM_HOVER[
- themeScheme
- ]} !important;
- `};
- }
-`;
-
-const StyledMenuSectionContainer = styled.div<
- { active?: boolean } & WithThemeSchemeProp
->`
- width: 100%;
-
- transition: background-color 140ms ease-in-out 0s, color 140ms ease-in-out 0s;
-
- ${({ active = false }) => active && hoverOrActiveCss};
-
- &:hover {
- ${hoverOrActiveCss};
- }
-`;
-
-SideMenu.displayName = "SideMenu";
-export default SideMenu;
@@ -11,6 +11,7 @@ import { default as makeGetRepositoryFiles } from "./getRepositoryFiles";
import { default as makeGetRepositoryHead } from "./getRepositoryHead";
import { default as makeGetRepositoryHTTPCloneUrl } from "./getRepositoryHTTPCloneUrl";
import { default as makeGetRepositorySSHCloneUrl } from "./getRepositorySSHCloneUrl";
+import { default as makeIsFileInRepositoryPath } from "./isFileInRepositoryPath";
export const makeRepositoryService = makeService<
RepositoryServiceAPI,
@@ -24,4 +25,5 @@ export const makeRepositoryService = makeService<
getRepositoryHead: makeGetRepositoryHead,
getRepositoryHTTPCloneUrl: makeGetRepositoryHTTPCloneUrl,
getRepositorySSHCloneUrl: makeGetRepositorySSHCloneUrl,
+ isFileInRepositoryPath: makeIsFileInRepositoryPath,
});
@@ -0,0 +1,75 @@
+// std
+import { existsSync } from "node:fs";
+import { spawn } from "node:child_process";
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import type { Repository } from "@prisma/client";
+// app
+import { Env } from "../../env";
+// service
+import type { RepositoryServiceDeps } from "./types";
+
+const makeIsFileInRepositoryPath: ServiceMethodFactory<
+ RepositoryServiceDeps,
+ [Repository, string, string[], string | undefined],
+ Promise<string[]>
+> = ({ request }) => {
+ return async (repo, path, filesToMatch, ref = "HEAD") => {
+ if (path !== "" && path.endsWith("/") === false) {
+ throw new Error("Could not search for files in a non-tree blob.");
+ }
+
+ const parentOrg = await request.prisma.organization.findUnique({
+ where: {
+ id: repo.organizationId,
+ },
+ });
+
+ if (parentOrg == null) {
+ throw new Error(
+ `Could not find the parent organization for project "${repo.slug}".`
+ );
+ }
+
+ try {
+ const repoPath = `${Env.GIT_REPOSITORIES_ROOT}/${parentOrg.slug}/${repo.slug}.git`;
+ if (existsSync(repoPath) === false) {
+ throw new Error(
+ `Could not find a valid git repository at: ${repoPath}`
+ );
+ }
+
+ const gitLsTreeProcess = spawn(
+ "git",
+ ["ls-tree", "--name-only", `${ref}:${path}`],
+ {
+ cwd: repoPath,
+ }
+ );
+
+ const gitLsTreeResult = await new Promise<string>((resolve, reject) => {
+ let buffer = [] as string[];
+ gitLsTreeProcess.stdout.on("data", (data) => buffer.push(data));
+ gitLsTreeProcess.stderr.on("data", (data) => {
+ reject(new Error(Buffer.from(data).toString("utf-8")));
+ });
+ gitLsTreeProcess.stdout.on("close", () => {
+ resolve(buffer.join(""));
+ });
+ });
+
+ const files = gitLsTreeResult.split("\n");
+ return files.reduce((acc, currFile) => {
+ if (filesToMatch.includes(currFile)) {
+ acc = [...acc, currFile];
+ }
+ return acc;
+ }, [] as string[]);
+ } catch (_) {
+ return [];
+ }
+ };
+};
+
+export default makeIsFileInRepositoryPath;
@@ -50,6 +50,12 @@ export interface RepositoryServiceAPI extends ServiceApiContract {
): Promise<RepositoryHead>;
getRepositoryHTTPCloneUrl(repository: Repository): Promise<string>;
getRepositorySSHCloneUrl(repository: Repository): Promise<string>;
+ isFileInRepositoryPath(
+ repository: Repository,
+ path: string,
+ filesToMatch: string[],
+ ref?: string
+ ): Promise<string[]>;
}
export interface RepositoryServiceDeps {
@@ -18,62 +18,8 @@ export interface AppSessionData extends Prisma.JsonObject {
two_factor_complete: boolean;
}
-// @deprecated use #Section instead
-export interface IDocSectionEntrypoint {
- slug: string;
- firstPageSlug: string;
- coverImageUrl: string;
- title: string;
- summary: string;
-}
-
-export interface Section {
- slug: string;
- title: string;
- summary: string;
- pagesBySlug: {
- [pageSlug: string]: Page;
- };
-}
-
-export interface PageSummary {
- title: string;
- subtitle?: string;
- href: string;
-}
-
-export interface Page {
- slug: string;
- content: string;
- tableOfContent: Array<{
- title: string;
- targetAnchor: string;
- depth: number;
- }>;
- metas: {
- [x: string]: any;
- title?: string;
- summary?: string;
- showExamples?: string;
- examplesFile?: string;
- prevPage?: null | PageSummary;
- nextPage?: null | PageSummary;
- };
-}
-
-export type SectionsPagesIndex = {
- [sectionSlug: string]: string[];
-};
-
-export type SectionsWithPages = {
- [sectionSlug: string]: Section;
-};
-
export interface CommonViewProps {
authenticated: boolean;
- currentSectionSlug: string | undefined;
- currentPageSlug: string | undefined;
- menuDefinition: SectionsWithPages;
themeScheme: AppThemeScheme;
title?: string;
}
@@ -1,53 +0,0 @@
-// std
-import { cwd } from "process";
-import { join, resolve } from "path";
-import { readFile } from "fs/promises";
-// 3rd-part
-import matter from "gray-matter";
-import tocGenerator from "markdown-toc";
-// app
-import type { Page } from "../../types";
-import * as paths from "../../../paths";
-
-export async function getDocFileContent(
- sectionSlug: string,
- pageSlug: string,
- ext: string
-): Promise<Page> {
- try {
- const filePath = resolve(
- join(paths.ROOT_FOLDER, "docs", sectionSlug, `${pageSlug}.${ext}`)
- );
-
- const content = await readFile(filePath, { encoding: "utf-8" });
- const ymlMatter = matter(content, { language: "yaml" });
- const toc = tocGenerator(ymlMatter.content).json as {
- content: string;
- slug: string;
- lvl: number;
- }[];
- return {
- slug: pageSlug,
- content: ymlMatter.content as string,
- tableOfContent: toc.map((entry) => ({
- title: entry.content,
- targetAnchor: entry.slug,
- depth: entry.lvl,
- })),
- metas: ymlMatter.data,
- };
- } catch (err) {
- const workingDir = cwd();
- const error = err as Error;
- return {
- slug: pageSlug,
- content: `# ${
- error.name || "InternalServerError"
- }\n\n> ${error.message.replace(`${workingDir}/docs`, "")}`,
- tableOfContent: [],
- metas: {
- error,
- },
- };
- }
-}
@@ -2,7 +2,6 @@
export { authenticatedOrLogin } from "./authenticatedOrLogin";
export { authenticatedOrRedirect } from "./authenticatedOrRedirect";
-export { getDocFileContent } from "./getDocFileContent";
export { getEnv } from "./getEnv";
export { guestOrRedirect } from "./guestOrRedirect";
export { localAppDomainPreHandler } from "./localAppDomainPreHandler";
@@ -8,11 +8,6 @@ import { Const } from "../../const";
export const makeRequestHandler = {
getter: () => {
return (request: FastifyRequest, reply: FastifyReply) => {
- const { sectionSlug, pageSlug } = request.params as {
- sectionSlug?: string;
- pageSlug?: string;
- };
-
return <T extends Record<string, unknown>>(
viewName: string,
props?: T & CommonViewProps,
@@ -23,8 +18,6 @@ export const makeRequestHandler = {
commonProps: {
authenticated: request.session.data.authenticated,
title: props?.title,
- currentSectionSlug: sectionSlug,
- currentPageSlug: pageSlug,
themeScheme:
(request.cookies?.["theme_scheme"]?.split(".")?.[0] ||
Const.DEFAULT_THEME_SCHEME) === "light"
@@ -29,22 +29,6 @@ const NamedColors = {
light: Colors.WHITE_01,
dark: Colors.GRAY_DARK_05,
},
- SIDE_MENU: {
- light: Colors.WHITE_01,
- dark: Colors.BLACK_01,
- },
- SIDE_MENU_SECTION_HOVER: {
- light: Colors.GRAY_LIGHT_08,
- dark: Colors.GRAY_DARK_04,
- },
- SIDE_MENU_ITEM_HOVER: {
- light: Colors.GRAY_LIGHT_05,
- dark: Colors.GRAY_DARK_03,
- },
- SIDE_EXAMPLES: {
- light: Colors.GRAY_LIGHT_03,
- dark: Colors.GRAY_DARK_04,
- },
TEXT_DEFAULT: {
light: Colors.BLACK_01,
dark: Colors.WHITE_01,
@@ -14,7 +14,7 @@ export interface HomeViewProps extends CommonProps {
const HomeView: ReactView<HomeViewProps> = (props) => {
const { commonProps } = props;
return (
- <Layout {...commonProps} showSideMenu={false}>
+ <Layout {...commonProps}>
<PageWrapper>
<StyledButtonsRow>
<ButtonAnchor href={"/auth/register"}>Get Started</ButtonAnchor>
@@ -14,7 +14,7 @@ export interface DashboardViewProps extends CommonProps {
const DashboardView: ReactView<DashboardViewProps> = ({ commonProps }) => {
return (
- <Layout {...commonProps} showSideMenu={false}>
+ <Layout {...commonProps}>
<PageWrapper>
<h1>Hey, welcome!</h1>
</PageWrapper>
@@ -18,7 +18,7 @@ const LoginView: ReactView<LoginViewProps> = ({
initialValues = undefined,
}) => {
return (
- <Layout {...commonProps} showSideMenu={false}>
+ <Layout {...commonProps}>
<PageWrapper>
{errorMessage && (
<div className={"error_message"}>
@@ -19,7 +19,7 @@ const RegisterView: ReactView<RegisterViewProps> = ({
initialValues = undefined,
}) => {
return (
- <Layout {...commonProps} showSideMenu={false}>
+ <Layout {...commonProps}>
<PageWrapper>
{errorMessage && (
<div className={"error_message"}>
@@ -26,7 +26,7 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
repo,
}) => {
return (
- <Layout {...commonProps} showSideMenu={false}>
+ <Layout {...commonProps}>
<PageWrapper>
<h1>
{parentOrg.displayName || parentOrg.slug}
@@ -30,7 +30,7 @@ const RepositoryCreateView: ReactView<RepositoryCreateViewProps> = ({
initialValues = undefined,
}) => {
return (
- <Layout {...commonProps} showSideMenu={false}>
+ <Layout {...commonProps}>
<PageWrapper>
{errorMessage && (
<div className={"error_message"}>
@@ -5,7 +5,12 @@ import React from "react";
// generated via script[prisma:generate]
import type { Organization, Repository, User } from "@prisma/client";
// app
-import type { CommonProps, RepositoryHead, RepositoryFile } from "../../types";
+import type {
+ CommonProps,
+ RepositoryHead,
+ RepositoryFile,
+ RepositoryFileContent,
+} from "../../types";
import { Layout, PageWrapper } from "../../components";
// app islands
import RepositoryInitialSetup from "../../islands/RepositoryInitialSetup";
@@ -19,6 +24,7 @@ export interface RepositoryDetailsViewProps extends CommonProps {
};
parentOrg: Organization;
path: string;
+ readmeFileContent: null | RepositoryFileContent;
ref: string;
repo: Repository;
repoHead: null | RepositoryHead;
@@ -31,13 +37,14 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
cloneUrl,
parentOrg,
path,
+ readmeFileContent,
ref,
repo,
repoHead,
repoFiles,
}) => {
return (
- <Layout {...commonProps} showSideMenu={false}>
+ <Layout {...commonProps}>
<PageWrapper>
<h1>
{parentOrg.displayName || parentOrg.slug}
@@ -98,6 +105,14 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
/>
</div>
)}
+ {readmeFileContent != null && (
+ <div>
+ <h3>Read Me</h3>
+ <code>
+ <pre>{readmeFileContent.content}</pre>
+ </code>
+ </div>
+ )}
</PageWrapper>
</Layout>
);
@@ -17,7 +17,7 @@ const RepositoryExploreView: ReactView<RepositoryExploreViewProps> = ({
repositories,
}) => {
return (
- <Layout {...commonProps} showSideMenu={false}>
+ <Layout {...commonProps}>
<PageWrapper>
{repositories.map((repo) => (
<div key={repo.id}>