;

Документация по разработке сайта

Здесь буду записывать свои костыли, что бы потом мог хотя бы разобраться сам. Так же сайт развивается и некоторый код здесь может устареть. Но суть будет одна.

Api

Файл paths.ts

Здесь у меня хранятся пути до контента mdx файлов.

import path from "path"; //  используется для работы с путями файлов
export const POSTS_PATH_JS = path.join(process.cwd(), `/Content/javascript`);
export const POSTS_PATH_TS = path.join(process.cwd(), `/Content/typescript`);
export const POSTS_PATH_NEXT = path.join(process.cwd(), `/Content/nextjs`);
export const POSTS_PATH_REACT = path.join(process.cwd(), `/Content/react`);
export const POSTS_PATH_PARADIGMS = path.join(process.cwd(), `/Content/paradigms`);

process.cwd() - Этот метод возвращается текущий рабочий каталог. Таким образом мы полностью получаем путь к нашим файлам, вместе с рабочим каталогом
Например:
С методом process.cwd()

export const POSTS_PATH_REACT = path.join(process.cwd(), `/Content/react`);
console.log(POSTS_PATH_REACT); // D:\WEb\Disco-code\Content\react

Без метода

export const POSTS_PATH_REACT = path.join(`/Content/react`); 

console.log(POSTS_PATH_REACT); // \Content\react

Сам api.ts файл

Импорты в api.ts

import path from "path"; // используется для работы с путями файлов
import fs from "fs"; // используетс для работы с  файлами
import { sync } from "glob"; // с помощью этой функции мы получаем все наши mdx файлы по url
import matter from "gray-matter"; // об этом будет ниже.

Функция getSlugs

export const getSlugs = (url: string): string[] => {
    // в url передается путь до mdx файлов, именно те переменные, что мы разбирали выше из файла paths.ts

    const paths = sync(`${url}/*.mdx`); // здесь мы получаем весе наши пути до каждого файла
    // метод sync возвращает нам их в массиве

    return paths.map(path => { // перебираем этот массив
        const parts = path.split("/"); // разбиваем весь путь на составляющие
        // Таким образом мы получаем имя конкретно файла, без остального пути.
        // например мы разбили путь на составляющие у нас такой массив [ 'D:', 'WEb', 'Disco-code', 'Content', 'docs', 'sitedocument.mdx' ]
        // в этом массиве 6 элементов, наше название файла всегда последнее, и так как счет идет от 0, мы отнимаем parts.length - 1, и получаем индекс последнего элемента.
      
        const fileName = parts[parts.length - 1]; //  последний элемент  массива наш fileName
    
        const [slug, _ext] = fileName.split("."); // теперь разбиваем название файла например  sitedocument.mdx стало [sitedocument,mdx]
        // в Slug у нас имя файла, в _ext расширение - .mdx
        return slug; // возвращаем имя файла
    });
};

Функция getPostFromSlug

// в нее передаем url из paths.ts и при использовании getStaticPaths и getSlugs мы будем получать наш slug об этом уже на самих страницах.
export const getPostFromSlug = (slug: string,url: string): Post => {
        
    const postPath = path.join(url, `${slug}.mdx`); // из пути и имени создаем полный путь к файлу

    const source = fs.readFileSync(postPath); // тут мы читаем все наши файлы и получаем весь наш контент из mdx файла в буфере
    // если добавить опцию в виде стандарта для кодировки, то тогда получим нормальный текст, Для этого добавляем опцию fs.readFileSync(postPath,{encoding:'utf8'}) 
    
    const { content, data } = matter(source); // я использую метод matter, поэтому мне не нужно добавлять кодировку выше. 
    // Этот метод так же позволяет нам отделить контент от данных что записанны в начале mdx файла.
    // Например вот начало одной из страниц .mdx:
     /* Это все будет в переменной data.
        ---  
        title: 'Анимация с помощью setInterval'
        id: 24 
        prev: '/js/basicjs/setIntervalAndMore'
        next: '/js/basicjs/date'
        category: 'basicjs'
        --- 
        Все что ниже это наш основной контент, он будет в переменной content.

     */

    return {
        content, // возвращаем весь контент
        meta: {  // вовращаем объект с нашими мета скажем так тегами
            slug, // возвращаем так же имя файла, поможет в работе в будущем
            title: data.title ?? slug, // свойство title, если в data.title нет, то подставляем название файла
            id: data.id ?? "", // свойство с id
            prev: data.prev ?? 'none', // свойство с пред страницей
            next: data.next ?? 'none', // свойство со след страницей
            category: data.category ?? "none", // и категория
        },
    };

};

Функция getAllPosts

Тут мы совмещаем предыдущие функции и получаем массив со всеми постами, вместе с контентом и тегами в начале каждого mdx файла.


export const getAllPosts = (url:string) => {
    // получаем массив с именами файлов, перебираем их и используем нашу функцию на каждом, в итоге получаем массив со всем контентом
    const posts = getSlugs(url).map(slug => getPostFromSlug(slug,url));

    return posts;
};

context

import { createContext, ReactNode } from "react";
type TODO_ANY = any; // ну тут потому что я тупой, да не знаю как типизировать
export interface IAppContext {
    AllThemePosts: TODO_ANY;

}
export const AppContext = createContext<IAppContext>({AllThemePosts:[]}); // создаем контекст массив.

// ну у нас будут наши посты в AllThemePosts, children - то чем будет оборачивать AppContextProvider
export const AppContextProvider = ({AllThemePosts,children}:IAppContext & {children: ReactNode}):JSX.Element => {
            // оборачиваем данные которые будем получать из layout, посути наши страницы
    return <AppContext.Provider value={{AllThemePosts}}>
                {children }
            </AppContext.Provider>;
};

layout

// ну тут обынчый layout
const Layout = ({ children, }: LayoutProps): JSX.Element => {
    
    const router = useRouter();
    const r = router.asPath.split('/').length;
     
    return (
        <>
            <div className={cn(wrapper, {
                [s.wrapperWithoutRightSide]: (r >= 3) // тут все изи, немного меняю страницу, если мы находимся где то в постах уже
                // wrapperWithoutRightSide - просто убирает путсую правую часть у сайта, что бы место под контент было больше, например не небольших экранах
            })}> 
                <Header className={header} />
                <Sidebar className={sidebar} />
                <div className={body}>
                    <div className={content}>
                    <ListMobileMenu />
                        {children}

                    </div>
                </div>
                <Footer className={footer} />
            </div>

        </>

    );
};

С layout в принципе и так все понятно.

Функция withLayout

Тут же внутри layout у меня есть функция withLayout, которой мы оборачиваем все страницы. Это HOC - компонент высшего порядка. На наших страницах(page) с помощью getStaticProps мы будем получать данные с помощью функций из api, полученные данные со страниц, как раз таки будут попадать в props функции withLayout. А сама страница будет компонентом в layout которая внутри layout будет внутри content на месте children. Суть такая, что мы на странице получили данные, эти данные попали в withLayout, отсюда они попали в контекст и в саму страницу(компонент) возвращаему вместе с layout. Мы могли бы передать пропсы в layout и просто перекидывать их в нужные компоненты, в сайдбар, из сайдабара в меню по js, там в другое меню и так далее. Но как мне кажется легче их получить через контекст без перекидываний.

export const withLayout = <T extends Record<string, unknown> & IAppContext>(Component: FunctionComponent<T>) => { // сам компонент 
    return function withLayoutComponent(props: T): JSX.Element { // тута будут наши данные из страниц обернутых withLayout
        return ( // ну вот мы и оборачиваем весь layout, а значит, что может получить наши посты почти везде.
            <AppContextProvider AllThemePosts={props.AllThemePosts}>
                <Layout >
                    <Component {...props} />
                </Layout>
            </AppContextProvider>
        );
    };
};

Как мы работаем с api на страницах

На примере js

Файл index.tsx - содержание всего js

Есть главная страница js в котором находится содержание - index.tsx.

js_page

На этой странице мы используем getStaticProps вметсе с getAllPosts. Таким образом на этой странице мы получаем данные с нагих mdx страниц, то есть meta данные, которые указаны в самом верху каждой статьи.

import type { GetStaticProps, NextPage } from "next";
import React from "react";
import { withLayout } from "../../layout/Layout";
import { getAllPosts } from "../api/api";
import { POSTS_PATH_ADVANCED_JS, POSTS_PATH_ALGORITHMS_JS, POSTS_PATH_BASIC_JS, POSTS_PATH_PRACTICE_JS } from "../api/paths";
import Head from "next/head";
import MainMenuJs from "../../src/Components/Menu/MenuJs/MainMenuJs";

export const getStaticProps: GetStaticProps = async () => { // вот сам getStaticProps, функция будет срабатывать когда мы заходим на страницу
// то есть когда мы будем находиться в корневой главной странице, у нас в пропсах ничего не будет 
// как только мы зайдем на страницу js, мы получим вот это вот все внизу AllThemePosts
  const posts_Basic_Js = getAllPosts(POSTS_PATH_BASIC_JS).map((post) => post.meta);
  const posts_Advanced_Js = getAllPosts(POSTS_PATH_ADVANCED_JS).map((post) => post.meta);
  const posts_Algorithms_Js = getAllPosts(POSTS_PATH_ALGORITHMS_JS).map((post) => post.meta);
  const posts_Practice_Js = getAllPosts(POSTS_PATH_PRACTICE_JS).map((post) => post.meta);
  // тут я получаю кажду тему отдельно и помещаю все в AllThemePosts и далее возвращаю его из getStaticProps
  const AllThemePosts = {posts_Basic_Js,posts_Advanced_Js,posts_Algorithms_Js,posts_Practice_Js};
  return {
    props: {
      AllThemePosts, 
    },
  };
};

// таким образом, все, что находится в AllThemePosts, попадает в контекст
const Home: NextPage = (): JSX.Element => {

// тут все понятно
  return (
    <div className="page_content">
      <Head>
        <title>Содержание</title>
        <meta
          name="google-site-verification"
          content="ArMplWlyr69JYGz_vTfAjA8HzzYLdXm-p5gHjqgDihY"
        />

        <meta name="yandex-verification" content="a99ae512e4f1c330" />
        <meta
          name="description"
          content={"Уроки по javascript, задачи, алгоритмы.js статьи,"}
        />
        <meta property="og:title" content="Уроки по javascript" />
        <meta
          property="og:description"
          content={"Уроки и разбор разных тем по javascript"}
        />

        <meta property="og:type" content="article" />
        <meta property="og:url" content="https://discocode.ru/js" />
        <meta
          property="og:image"
          content="https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.seancdavis.com%2Fposts%2Frun-loop-n-times-javascript%2F&psig=AOvVaw3_1bJy-ASlokQV8SIxMjzi&ust=1665134220667000&source=images&cd=vfe&ved=0CAwQjRxqFwoTCKi09r-iy_oCFQAAAAAdAAAAABAI"
        />
        <meta property="og:site_name" content="DiscoCode" />
        <meta property="og:locale" content="ru_Ru" />

        <meta property="og:author" content="Дмитрий черномашенцев" />
        <meta property="og:section" content="JavaScript" />
        <meta property="og:tag" content="JavaScript, js" />
      </Head>
      <h2>Содержание</h2>

      <div className="page_body">

        <div className="page_menu">

          <MainMenuJs /> { // тут мы вызываем наше меню внутри которого с помощью контекста я получаю посты
          }
        </div>
      </div>
    </div>
  );
};

export default withLayout(Home); // тут как раз таки експоритруем наш компонент обернутый withLayout

Получается так, что пока мы не зайдем на какую либо страницу, наш контекст будет пустым, как только мы зашли например на js старницу, в наш контекст попадает весь объект AllThemePosts с нужными данными, для отображения содержания

Продолжение следует....