1. GoogleDriveのMarkdownを手軽に誰でも閲覧させたい
先日、「GoogleDrive上のMarkdownファイルをそのまま閲覧できるビューアはないか?」
という相談がありました。
Driveのプレビューでは、Markdownが自動でGoogleドキュメントに変換されてしまい、しかも同じフォルダに別ファイルとして保存されてしまったりします。
相談された方はエンジニアではないため、拡張機能やVSCodeをわざわざインストールしてもらうのもどうかと思い、Markdownを簡単に閲覧・編集できるWebアプリを実装しました。
2. できたもの!
今回は共有しやすいということもあり、Stremlitで実装しました。
主な機能は以下の3つです。
- Markdownファイルを入れると、左側にソース、右側がプレビューが表示される
- 左側のソースを編集して
Ctrl + Enterでプレビューが更新される - 編集したソースをダウンロードできる
3. 実装
3.1. 全体コード
全体コードはこちらです
import streamlit as st
import os
# ページ設定
st.set_page_config(
page_title="マークダウンビュアー",
layout="wide"
)
# タイトル
st.title("マークダウンビュアー")
st.markdown("---")
# サイドバーでファイルアップロード
with st.sidebar:
st.header("ファイルを選択")
uploaded_file = st.file_uploader(
"マークダウンファイルをアップロード",
type=['md', 'markdown', 'txt'],
help="`.md`、`.markdown`、`.txt`ファイルに対応しています"
)
st.markdown("---")
st.markdown("### 使い方")
st.markdown("""
1. 左のファイルアップローダーからマークダウンファイルを選択
2. ファイルの内容が自動的に表示されます
3. マークダウン記法が適用されて表示されます
""")
# メインエリア
if uploaded_file is not None:
# ファイル情報を表示
col1, col2 = st.columns([3, 1])
with col1:
st.info(f"ファイル名: **{uploaded_file.name}**")
with col2:
file_size = len(uploaded_file.getvalue()) / 1024 # KB
st.info(f"サイズ: **{file_size:.2f} KB**")
# ファイルの内容を読み込み
try:
content = uploaded_file.read().decode('utf-8')
# セッション状態に保存(初回またはファイル変更時のみ)
if 'markdown_content' not in st.session_state or st.session_state.get('current_file') != uploaded_file.name:
st.session_state.markdown_content = content
st.session_state.current_file = uploaded_file.name
st.session_state.original_content = content
# 2列に分割して表示
col1, col2 = st.columns(2)
with col1:
st.markdown("### マークダウンソース")
# ダウンロードボタンを上部に配置
st.download_button(
label="ダウンロード",
data=st.session_state.markdown_content,
file_name=uploaded_file.name,
mime="text/markdown",
use_container_width=True
)
# コールバック関数
def update_content():
st.session_state.markdown_content = st.session_state.editor
# 編集可能なテキストエリア(画面いっぱいに拡張)
st.text_area(
label="",
value=st.session_state.markdown_content,
height=1200,
label_visibility="collapsed",
key="editor",
on_change=update_content
)
with col2:
st.markdown("### プレビュー")
st.markdown(st.session_state.markdown_content)
except UnicodeDecodeError:
st.error("ファイルの読み込みに失敗しました。")
except Exception as e:
st.error(f"エラーが発生しました: {str(e)}")
else:
# ファイルが選択されていない場合
st.info("左のサイドバーからマークダウンファイルをアップロードしてください")
3.2. 技術的なポイント
3.2.1. Markdown編集内容の保持
アップロードしたMarkdownファイルの内容をst.session_stateに保存することで、再レンダリングしても値が消えないようにしています。
初回または別ファイルを読み込んだときのみ、内容をstateに保存し、編集内容もstateに反映しています。
if 'markdown_content' not in st.session_state or \
st.session_state.get('current_file') != uploaded_file.name:
st.session_state.markdown_content = content
st.session_state.current_file = uploaded_file.name
st.session_state.original_content = content
コールバックで編集内容を即反映しています。
def update_content():
st.session_state.markdown_content = st.session_state.editor
3.2.2. 編集できるMarkdownエディタを実装
st.text_area(
label="",
value=st.session_state.markdown_content,
height=1200,
label_visibility="collapsed",
key="editor",
on_change=update_content
)
3.2.3. Ctrl + Enter で即時プレビュー更新ができる仕組み
Streamlitのtext_areaは、キー操作で明示的にイベントを発火する仕組みはありません。
しかしCtrl + Enterを押すと、Streamlitアプリ全体が再実行されるという仕様があります。
このアプリでは
- 再実行時に
st.session_state.editor(編集中の内容)が保たれる -
on_change=update_contentによりstateが更新される - 結果、右側のプレビューが即時反映される
という仕組みです。
つまり コールバック + Streamlit の再実行モデル によって、Ctrl + Enterでそのまま即時更新が実現されています。
3.2.4. 編集とプレビューを同時に表示
st.columns()を用いて、左にソース編集、右にプレビューを並べています。
col1, col2 = st.columns(2)
with col1:
st.markdown("### マークダウンソース")
...
with col2:
st.markdown("### プレビュー")
st.markdown(st.session_state.markdown_content)
3.2.5. Markdownの内容をそのままダウンロードできる
st.download_buttonを用いて、編集したMarkdownを保存できるようにしています。
st.download_button(
label="ダウンロード",
data=st.session_state.markdown_content,
file_name=uploaded_file.name,
mime="text/markdown",
use_container_width=True
)
4. まとめ
Markdownファイルの閲覧と編集ができる簡単なアプリを作製してみました。
今回は基本的な編集のみしかできませんでしたが、もっと色々な編集をできるものを作ってみたいと思いました。
