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

データ管理

前回はトップページを作成しました。

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

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

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

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

マークダウン個別取得API作成(back:Flask側作成)

Flaskのapp.pyを修正します。

    
import os

from flask import Flask, request
from flask_cors import CORS

import pymysql.cursors
import json


app = Flask(__name__)
CORS(app)


def db_connection():
    conn = pymysql.connect(
        host='database',
        port=3306,
        user=os.environ.get('MYSQL_USER'),
        password=os.environ.get('MYSQL_PASSWORD'),
        db=os.environ.get('MYSQL_DATABASE'),
        cursorclass=pymysql.cursors.DictCursor
    )

    return conn

@app.route('/markdown', methods=["GET"])
def get_markdown():
    conn = db_connection()
    with conn.cursor() as cur:
        cur.execute(f"SELECT * FROM markdown")
        results = cur.fetchall()

    return json.dumps(results, indent=2)


@app.route('/markdown/<path:path>', methods=["GET"])
def get_markdown_url(path):
    conn = db_connection()
    with conn.cursor() as cur:
        cur.execute(f"SELECT * FROM markdown WHERE url = '{path}'")
        results = cur.fetchall()

    if not results:
        return json.dumps({}, indent=2)
    return json.dumps(results[0], indent=2)
    
  

get_markdown_url()の関数を追加しました。

URLのパスを取得し、そのURLをキーとしてマークダウンのデータを取得します。

自動採番などで生成したidをキーにすることも検討しました。

マークダウンの場合は[xxx](xxx)このような書き方でリンクを生成することができます。

リンクはURL形式であり、今回作るアプリのように表示と編集が手軽にできる場合はURL形式をキーにデータを取得することが望ましいと判断しました。

ブログなど投稿を管理画面を使ってあらかじめリンク先が決まっている場合はIDをキーに使う方がよいかもしれません。

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

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

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

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 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);
        } else {
          setMarkdown(res.data);
        }
      })
  }, [])

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

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

Markdownのページは、以下のテンプレートを切り替えて出力します。

なお、今回は表示用のテンプレートのコーディングをメインで紹介します。

表示用テンプレート ViewTemplate
編集用テンプレート EditTemplate

編集用テンプレート作成

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

編集モードできりかえるときのダミーテンプレートとして事前に作成しておきます。

    
import { FC } from "react";

export const EditTemplate: FC = () => {
  return (
    <>
    <h1>編集用テンプレート</h1>
    </>
  )
}
    
  

編集用テンプレートの文言を表示します。

表示用テンプレート作成

表示用テンプレート(ViewTemplate.tsx)ファイルを作成します。

    
import { FC } from "react";

import remarkGfm from "remark-gfm";
import ReactMarkdown from "react-markdown";

import { Box, Drawer, Container, ThemeProvider } from "@mui/material";
import { MarkdownType } from "../../types/MarkdownType.type";

import { Theme } from "../uilibs/theme/Theme";

import { SubHeaderNavigation } from "../uilibs/organisms/SubHeaderNavigation";
import { HeaderNavigation } from "../uilibs/organisms/HeaderNavigation";

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

export const ViewTemplate: FC<MarkdownProps> = (props) => {
  const { markdown, setMarkdown, editMode, onClickSwitchMode } = props;

  const StyleTemplateBox = { display: "flex", flexDirection: "column", height: "100vh" };
  const StyleMainBox = { display: "flex", flexDirection: "row", height: "100vh", width: '100%' };

  return (
    <ThemeProvider theme={Theme}>
      <Box sx={StyleTemplateBox}>
        <HeaderNavigation />
        <SubHeaderNavigation markdown={markdown} setMarkdown={setMarkdown} editMode={editMode} onClickSwitchMode={onClickSwitchMode} />
        <Drawer></Drawer>
        <Box sx={StyleMainBox}>
          <Container maxWidth="xl">
            <ReactMarkdown remarkPlugins={[remarkGfm]}>{markdown.body}</ReactMarkdown>
          </Container>
        </Box>
      </Box>
    </ThemeProvider>
  );
};
    
  

前回作成したヘッダーナビゲーション部品を配置します。

サブヘッダーナビゲーションのコンポーネントを作成しました。

サイドメニュー用のDrawerコンポーネントを用意していますが、今回コーディングは省略します。

    
<ReactMarkdown remarkPlugins={[remarkGfm]}>{markdown.body}</ReactMarkdown>
    
  

react-markdownのコンポーネントを呼び出します。

マークダウンのデータ形式をHTML形式に変換するコンポーネントです。

プラグインとして「remarkGfm」を使っています。

このプライグインを使うことでテーブルや、取り消し線などが変換できます。

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

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

「organisms/」ディレクトリ配下に配置します。

    
project/
 ┣ front/
 ┃ ┣ Dockerfile
 ┃ ┗ src/
 ┃   ┗ markdown-app/
 ┃     ┗ src/
  ┃       ┣ components/
  ┃       ┃ ┗ uilibs/
  ┃       ┃   ┗ organisms/
  ┃       ┃     ┗ SubHeaderNavigation.tsx

・・・略・・・
    
  

コーディングの内容は以下の通りです。

    
import React, { FC } from "react";

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

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

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

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

  const { markdown, setMarkdown, editMode, 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 StyleToolBar = { backgroundColor: "#1976d2", display: "flex", justifyContent: "space-between" };
  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} />
        <SwitchModeButtonNavi editMode={editMode} onClickSwitchMode={onClickSwitchMode} />
      </Toolbar>
    </Box>
  );
}
    
  

マークダウンのURLとタイトルをサブヘッダーナビゲーションで表示します。

編集モードか表示モードか判定をしてTextFiledとTypographを切り替えるコンポーネントを作成しました。

同様に編集モードか表示モードか判定をしえ編集、表示のアイコンを変えるコンポーネントも作成しています。

テキスト表示切り替え部品作成

テキスト表示切り替え部品(SwitchTextFieldTypography.tsx)ファイルを更新します。

表示モードの時はTypographyのコンポーネントを使用します。

編集モードは、TextFieldのコンポーネントを使用します。

「molecules/」ディレクトリ配下に(SwitchTextFieldTypography.tsx)を配置します。

    
project/
 ┣ front/
 ┃ ┣ Dockerfile
 ┃ ┗ src/
 ┃   ┗ markdown-app/
 ┃     ┗ src/
  ┃       ┣ components/
  ┃       ┃ ┗ uilibs/
  ┃       ┃   ┗ molecules/
  ┃       ┃     ┗ SwitchTextFieldTypography.tsx
・・・略・・・
    
  

コーディングの内容は以下の通りです。

    
import React, { FC } from "react";

import { CustomTypography } from "../atoms/CustomTypography";
import { CustomTextField } from "../atoms/CustomTextField";

interface Props {
  editMode: boolean;
  label: string;
  value: string;
  text: string;
  onChangeValue: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
};

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

  const { editMode, label, value, text, onChangeValue } = props;

  return (
    <>
      {editMode ? <CustomTextField label={label} value={value} onChangeValue={onChangeValue} /> : <CustomTypography text={text} />}
    </>
  );
};
    
  

編集モードか表示モードを判定をしてTextFiledとTypographを切り替えます。

TextFieldとCustomTypographyはそれぞれ部品化しています。

カスタムテキストフィールド部品作成

カスタムテキストフィールド部品(CustomTextField.tsx)ファイルを作成します。

「atoms/」ディレクトリ配下に(CustomTextField.tsx)を配置します。

    
project/
 ┣ front/
 ┃ ┣ Dockerfile
 ┃ ┗ src/
 ┃   ┗ markdown-app/
 ┃     ┗ src/
  ┃       ┣ components/
  ┃       ┃ ┗ uilibs/
  ┃       ┃   ┗ atoms/
  ┃       ┃     ┗ CustomTextField.tsx
・・・略・・・
    
  
    
import { FC } from "react";

import { TextField } from "@mui/material";

interface Props {
  label: String;
  value: String;
  onChangeValue: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
};

export const CustomTextField: FC<Props> = (props) => {
  const { label, value, onChangeValue } = props;
  return (
    <TextField label={label} value={value} onChange={onChangeValue} />
  );
};
    
  

TextFieldの部品化です。

onChangeの関数をpropsで渡していますが、ここの記述方法がわからずコーディングに時間がかかってしまいました。

TextFieldのonChangeイベントのeventの型は、「React.ChangeEvent<HTMLTextAreaElement>」です。

Eventの型がわかりませんでした。

Typescriptは数値、文字はわかりやすいですが、event系の型やコンポーネントの型がわかりずらいですね。

カスタムタイポグラフィ部品の作成

カスタムタイポグラフィ(CustomTextField.tsx)ファイルを更新します。

「atoms/」ディレクトリ配下に(CustomTextField.tsx)を配置します。

    
project/
 ┣ front/
 ┃ ┣ Dockerfile
 ┃ ┗ src/
 ┃   ┗ markdown-app/
 ┃     ┗ src/
  ┃       ┣ components/
  ┃       ┃ ┗ uilibs/
  ┃       ┃   ┗ atoms/
  ┃       ┃     ┗ CustomTextField.tsx
・・・略・・・
    
  
    
import { FC } from "react";

import { Typography } from "@mui/material";

interface Props {
  text: string;
};

export const CustomTypography: FC<Props> = (props) => {
  const { text } = props;
  return (
    <Typography>{text}</Typography>
  );
};
    
  

propsで受け取った文字列を出力するコンポーネントです。

表示と編集の切り替えアイコン部品作成

表示と編集の切り替えアイコン部品(SwitchModeButtonNavi.tsx)ファイルを作成します。

「molecules/」ディレクトリ配下に(SwitchModeButtonNavi.tsx)を配置します。

    
project/
 ┣ front/
 ┃ ┣ Dockerfile
 ┃ ┗ src/
 ┃   ┗ markdown-app/
 ┃     ┗ src/
  ┃       ┣ components/
  ┃       ┃ ┗ uilibs/
  ┃       ┃   ┗ molecules/
  ┃       ┃     ┗ SwitchModeButtonNavi.tsx
・・・略・・・
    
  
    
import React, { FC } from "react";

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

import EditIcon from '@mui/icons-material/Edit';
import PageviewIcon from '@mui/icons-material/Pageview';

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

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

  const { editMode, onClickSwitchMode } = props;

  return (
    <BottomNavigation showLabels sx={{ backgroundColor: "#1976d2" }}>
      {editMode ? <BottomNavigationAction label="表示" icon={<PageviewIcon />} onClick={onClickSwitchMode} /> : <BottomNavigationAction label="編集" icon={<EditIcon />} onClick={onClickSwitchMode} />}
    </BottomNavigation>
  );
};
    
  

propsでeditModeを受け取って編集と表示を切り替えます。

画面確認

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

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

    
docker-compose up
    
  

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

http://localhost:3000

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

トップページ

markdown-homepage

markdownページの表示モードです。

markdown-homepage

まとめ

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

コメント

0 件のコメント:

コメントを投稿

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