Markdwonメモアプリ作成(TypeScript)(第四回)

タグの登録と表示

今回はマークダウンの編集ページを作成します。

前回の内容は以下を参照してください。

Markdwonメモアプリ作成(TypeScript)(第一回)
Markdwonメモアプリ作成(TypeScript)(第二回)
Markdwonメモアプリ作成(TypeScript)(第三回)

今回の手順は以下のとおりです。

FlaskのAPI改修

過去に作成した内容とほぼ同じです。登録と更新のAPIを追加しています。

    
@app.route('/markdown', methods=["POST"])
def post_markdown():
    url = request.json["url"]
    title = request.json["title"]
    body = request.json["body"]

    conn = db_connection()
    with conn.cursor() as cur:
        cur.execute(
            f"INSERT INTO markdown (url, title, body) values('{url}','{title}','{body}')")
        conn.commit()

    return RESULT_CODE_SUCESS


@app.route('/markdown<path:path>', methods=["PUT"])
def put_markdown(path):
    title = request.json["title"]
    body = request.json["body"]

    conn = db_connection()
    with conn.cursor() as cur:
        cur.execute(
            f"UPDATE markdown SET title = '{title}', body = '{body}' WHERE url = '{path}' ")
        conn.commit()

    return RESULT_CODE_SUCESS

    
  

post_markdown()とput_markdownの関数を追加します。

マークダウンページの更新

マークダウンページ(Markdown.tsx)のファイルを更新します。

    
import { FC, useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import axios from "axios";

import { MarkdownType } from "../../types/MarkdownType.type";

import { EditTemplate } from "../templates/EditTemplate";
import { ViewTemplate } from "../templates/ViewTemplate";

export const Markdown: FC = () => {

  const [editMode, setEditMode] = useState<boolean>(false);
  const [markdown, setMarkdown] = useState<MarkdownType>({ url: '', title: '', body: '' });
  const [insertMode, setInsertMode] = useState<boolean>(false);

  const location = useLocation();

  useEffect(() => {
    axios.get<MarkdownType>("http://localhost:5000/markdown" + location.pathname)
      .then(res => {
        if (!Object.keys(res.data).length) {
          setMarkdown({ url: decodeURI(location.pathname), title: '', body: '' });
          setEditMode(true);
          setInsertMode(true);
        } else {
          setMarkdown(res.data);
        }
      })
  }, [])

  const onClickSwitchMode = () => { setEditMode(!editMode) }

  return (
    <>
      {editMode ?
        <EditTemplate markdown={markdown} setMarkdown={setMarkdown} editMode={editMode} insertMode={insertMode} setInsertMode={setInsertMode} onClickSwitchMode={onClickSwitchMode} /> :
        <ViewTemplate markdown={markdown} setMarkdown={setMarkdown} editMode={editMode} insertMode={insertMode} setInsertMode={setInsertMode} onClickSwitchMode={onClickSwitchMode} />
      }
    </>
  );
};
    
  

EditTemplateのコンポーネントの記述を修正します。

ViewTemplateと同じようにパラメータを渡します。

新規登録用にinsertModeを追加します。

編集テンプレートの更新

編集テンプレート(EditTemplate.tsx)ファイルを更新します。

    
import { ThemeProvider } from "@emotion/react";
import { Container } from "@mui/material";
import { Box } from "@mui/system";
import { FC, useEffect } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { MarkdownType } from "../../types/MarkdownType.type";
import { MarkdownEditTextFiled } from "../uilibs/atoms/MarkdownEditTextFiled";
import { HeaderNavigation } from "../uilibs/organisms/HeaderNavigation";
import { SubHeaderNavigation } from "../uilibs/organisms/SubHeaderNavigation";
import { Theme } from "../uilibs/theme/Theme";


interface Props {
  markdown: MarkdownType;
  setMarkdown: React.Dispatch<React.SetStateAction<MarkdownType>>;
  editMode: boolean;
  insertMode: boolean;
  setInsertMode: React.Dispatch<React.SetStateAction<boolean>>;
  onClickSwitchMode: (event: React.MouseEvent<HTMLButtonElement>) => void;
};

export const EditTemplate: FC<Props> = (props) => {

  const { markdown, setMarkdown, editMode, insertMode, setInsertMode, onClickSwitchMode } = props;

  const StyleTemplateBox = { display: "flex", flexDirection: "column", height: "100vh" };
  const StyleMainBox = { display: "flex", flexDirection: "row", height: "100vh", width: '100%' };
  const StyleLeftBox = { height: "100vh", width: '50%', backgroundColor: "#121212" };
  const StyleRightBox = { height: "100vh", width: '50%' };

  const onChangeValue = (event: React.ChangeEvent<HTMLTextAreaElement>) => { setMarkdown({ url: markdown.url, title: markdown.title, body: event.target.value }); };

  useEffect(() => {
  }, [markdown.body]);

  return (
    <ThemeProvider theme={Theme}>
      <Box sx={StyleTemplateBox}>
        <HeaderNavigation />
        <SubHeaderNavigation markdown={markdown} setMarkdown={setMarkdown} editMode={editMode} insertMode={insertMode} setInsertMode={setInsertMode}  onClickSwitchMode={onClickSwitchMode}/>
        <Box sx={StyleMainBox}>
          <Box sx={StyleLeftBox}>
            <Container >
              <MarkdownEditTextFiled markdown={markdown} onChangeValue={onChangeValue} />
            </Container >
          </Box>
          <Box sx={StyleRightBox}>
            <Container className="markdown-view" >
              <ReactMarkdown remarkPlugins={[remarkGfm]}>{markdown.body}</ReactMarkdown>
            </Container >
          </Box>
        </Box>
      </Box>
    </ThemeProvider>
  );
};
    
  

サブヘッダーナビゲーション部品の作成

サブヘッダーナビゲーション部品(SubHeaderNavigation.tsx)ファイルを更新します。

    
import React, { FC } from "react";

import axios from "axios";

import {
  Box,
  Toolbar,
} from "@mui/material";

import { MarkdownType } from "../../../types/MarkdownType.type";
import { SwitchTextFieldTypography } from "../molecules/SwitchTextFieldTypography";
import { SwitchModeButtonNavi } from "../molecules/SwitchModeNavi";
import { SwitchInsertUpdateNavi } from "../molecules/SwitchInsertUpdateNavi";

interface Props {
  markdown: MarkdownType;
  setMarkdown: React.Dispatch>React.SetStateAction>MarkdownType<<;
  editMode: boolean;
  insertMode: boolean;
  setInsertMode: React.Dispatch>React.SetStateAction>boolean<<;
  onClickSwitchMode: (event: React.MouseEvent>HTMLButtonElement<) =< void;
};

export const SubHeaderNavigation: FC>Props< = (props) =< {

  const { markdown, setMarkdown, editMode, insertMode, setInsertMode, onClickSwitchMode } = props;

  const onChangeURL = (event: React.ChangeEvent>HTMLTextAreaElement<) =< { setMarkdown({ url: event.target.value, title: markdown.title, body: markdown.body }) };
  const onChangeTitle = (event: React.ChangeEvent>HTMLTextAreaElement<) =< { setMarkdown({ url: markdown.url, title: event.target.value, body: markdown.body }) };

  const onClickSaveMarkdown = () =< {
    axios.post(`http://localhost:5000/markdown`, markdown)
      .then(res =< {
        console.log(res.data);
        setInsertMode(false);
      });
  };

  const onClickUpdateMarkdown = () =< {
    axios.put(`http://localhost:5000/markdown` + markdown.url, markdown)
      .then(res =< {
        console.log(res.data);
      });
  };

  const StyleToolBar = { backgroundColor: "#1976d2", display: "flex", justifyContent: "space-between" };
  const StyleRightBox = { display: "flex", flexDirection: "row" };
  return (
    >Box<
      >Toolbar sx={StyleToolBar}<
        >SwitchTextFieldTypography editMode={editMode} label='url' value={markdown.url} onChangeValue={onChangeURL} text={markdown.url} /<
        >SwitchTextFieldTypography editMode={editMode} label='title' value={markdown.title} onChangeValue={onChangeTitle} text={markdown.title} /<
        >Box sx={StyleRightBox}<
          >SwitchInsertUpdateNavi editMode={editMode} insertMode={insertMode} onClickSaveMarkdown={onClickSaveMarkdown} onClickUpdateMarkdown={onClickUpdateMarkdown} /<
          >SwitchModeButtonNavi editMode={editMode} onClickSwitchMode={onClickSwitchMode} /<
        >/Box<
      >/Toolbar<
    >/Box<
  );
};
    
  

登録、更新用のAPIを呼び出しています。

登録と更新の切り替えアイコン部品作成

登録と更新の切り替えアイコン部品(SwitchInsertUpdateNavi.tsx)ファイルを更新します。

    
import React, { FC } from "react";

import { BottomNavigation, BottomNavigationAction } from "@mui/material";

import SaveIcon from '@mui/icons-material/Save';

interface Props {
  editMode: boolean;
  insertMode: boolean;
  onClickSaveMarkdown: (event: React.MouseEvent<HTMLButtonElement>) => void;
  onClickUpdateMarkdown: (event: React.MouseEvent<HTMLButtonElement>) => void;
};

export const SwitchInsertUpdateNavi: FC<Props> = (props) => {

  const { editMode, insertMode, onClickSaveMarkdown, onClickUpdateMarkdown } = props;

  return (
    <BottomNavigation showLabels sx={{ backgroundColor: "#1976d2" }}>
      {editMode ?
        insertMode ?
          <BottomNavigationAction label="新規登録" icon={<SaveIcon />} onClick={onClickSaveMarkdown} /> : <BottomNavigationAction label="更新" icon={<SaveIcon />} onClick={onClickUpdateMarkdown} /> :
        <></>
      }
    </BottomNavigation>
  );
};
    
  

登録と編集を切り替えるモードを追加しています。

画面確認

docker-compose upを使って各コンテナを起動します。

docker-compose.ymlファイルがある場所で以下のコマンドを実行します。

    
docker-compose up
    
  

起動したら以下のURLをブラウザに入力します。

http://localhost:3000

以下ブラウザの表示です。

markdownページの編集モードです。

markdown-homepage

まとめ

Markdwonメモアプリ作成(TypeScript)でマークダウンの編集ページを作成しました。

コメント

0 件のコメント:

コメントを投稿

コメントをお待ちしています。