GitFOSS
feat(repository): make it possible to see readme files on repository details page
+ 151
- 523
@@ -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"
     }
   }

app/components/Layout.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;

app/components/MarkdownToJsx.tsx
@@ -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]};
   `};
 

file deleted
app/components/MenuDivider.tsx
@@ -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]};
-  `};
-`;

file deleted
app/components/MenuItem.tsx
@@ -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]};
-    `};
-  }
-`;

app/components/index.ts
@@ -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).",
-  },
 };

app/controllers/repository/getRepositoryBrowserView.ts
@@ -48,6 +48,7 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
       },
       parentOrg,
       path,
+      readmeFileContent: null,
       ref,
       repo,
       repoHead: await repoService.getRepositoryHead(repo, ref),

app/controllers/repository/getRepositoryDetailsView.ts
@@ -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,

file deleted
app/islands/SideMenu.tsx
@@ -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;

app/services/repository/index.ts
@@ -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,
 });

new file
app/services/repository/isFileInRepositoryPath.ts
@@ -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;

app/services/repository/types.ts
@@ -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;
 }

file deleted
app/utils/server/getDocFileContent.ts
@@ -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,
-      },
-    };
-  }
-}

app/utils/server/index.ts
@@ -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";

app/utils/server/makeRequestHandler.ts
@@ -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"

app/utils/style/NamedColors.ts
@@ -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,

app/views/HomeView.tsx
@@ -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>

app/views/auth/DashboardView.tsx
@@ -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>

app/views/auth/LoginView.tsx
@@ -18,7 +18,7 @@ const LoginView: ReactView<LoginViewProps> = ({
   initialValues = undefined,
 }) => {
   return (
-    <Layout {...commonProps} showSideMenu={false}>
+    <Layout {...commonProps}>
       <PageWrapper>
         {errorMessage && (
           <div className={"error_message"}>

app/views/auth/RegisterView.tsx
@@ -19,7 +19,7 @@ const RegisterView: ReactView<RegisterViewProps> = ({
   initialValues = undefined,
 }) => {
   return (
-    <Layout {...commonProps} showSideMenu={false}>
+    <Layout {...commonProps}>
       <PageWrapper>
         {errorMessage && (
           <div className={"error_message"}>

app/views/repository/RepositoryBrowserView.tsx
@@ -26,7 +26,7 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
   repo,
 }) => {
   return (
-    <Layout {...commonProps} showSideMenu={false}>
+    <Layout {...commonProps}>
       <PageWrapper>
         <h1>
           {parentOrg.displayName || parentOrg.slug}

app/views/repository/RepositoryCreateView.tsx
@@ -30,7 +30,7 @@ const RepositoryCreateView: ReactView<RepositoryCreateViewProps> = ({
   initialValues = undefined,
 }) => {
   return (
-    <Layout {...commonProps} showSideMenu={false}>
+    <Layout {...commonProps}>
       <PageWrapper>
         {errorMessage && (
           <div className={"error_message"}>

app/views/repository/RepositoryDetailsView.tsx
@@ -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>
   );

app/views/repository/RepositoryExploreView.tsx
@@ -17,7 +17,7 @@ const RepositoryExploreView: ReactView<RepositoryExploreViewProps> = ({
   repositories,
 }) => {
   return (
-    <Layout {...commonProps} showSideMenu={false}>
+    <Layout {...commonProps}>
       <PageWrapper>
         {repositories.map((repo) => (
           <div key={repo.id}>