推しアイデア
サイトのどこでもメモをはっ付けられる
―
サイトのどこでもメモをはっ付けられる
記事が古くて使えない!そんな時はみんなに共有してあげよう
みんなにメモを共有 みんなのメモを検索
技術記事を読んで実践したとき、その記事が使えなかったことはありませか? Mymemoはそんな時にメモを共有してみんなに知らせるだけでなく、他のユーザーのメモを参照することにより、早期に参考にしていた技術記事に見切りをつけることができます 知識のアウトプットにもなることを目指して開発したプロダクトです。

| レイヤー | 技術 |
|---|---|
| フロントエンド | React 19.2.0 + Vite 7.3.1 |
| DnD | @dnd-kit/core 6.3.1, @dnd-kit/sortable 10.0.0 |
| バックエンド | Express 5.2.1 (Node.js, ESM) |
| DB | Supabase (PostgreSQL) |
| デプロイ | Render.com |
| 配布形式 | Chrome Extension (Manifest V3) |
実装済み機能:
・メモ保存機能 書いたメモはGoogle Cloud Storageで保存されているため、高速で読み込み可能
メモの保存はバックエンド(Express)経由で Supabase に書き込みます。画像データは GCS にアップロードし、そのURLをDBに格納します。
// Express (backend) app.post('/api/memos', async (req, res) => { const { userId, url, content, tags, color } = req.body; const { data, error } = await supabase .from('memos') .insert({ user_id: userId, page_url: url, content, tags, color }); if (error) return res.status(500).json({ error }); res.json(data); });
・メモ貼付機能 書いたメモをサイトのどこにでも張ることが可能。貼付したメモはサイズの変更や削除、非表示、内容の編集を直接できます。
Chrome の content script から React コンポーネントをページに注入し、@dnd-kit でドラッグ移動を実装しています。
// content_script.tsx(ページへの注入) import { useDraggable } from '@dnd-kit/core'; function StickyNote({ memo }: { memo: Memo }) { const { attributes, listeners, setNodeRef, transform } = useDraggable({ id: memo.id, }); const style = { transform: `translate(${transform?.x ?? memo.x}px, ${transform?.y ?? memo.y}px)`, backgroundColor: memo.color, }; return ( <div ref={setNodeRef} style={style} {...listeners} {...attributes} className="sticky-note"> <p>{memo.content}</p> </div> ); }
// manifest.json (Manifest V3) { "manifest_version": 3, "content_scripts": [{ "matches": ["<all_urls>"], "js": ["content_script.js"] }] }
・メモ共有機能 書いたメモをほかの人が見られるように共有することができます。
is_public フラグを true に更新するだけで公開状態に切り替わります。
// 共有ボタン押下時 async function shareMemo(memoId) { const { error } = await supabase .from('memos') .update({ is_public: true }) .eq('id', memoId); }
・タグ&メモ検索機能 メモにはタグを追加することが可能。検索機能ではタグや本文、またはその両方からメモの検索が可能。ほかの人が書いたメモも検索可能であり、メモからサイトへアクセスすることができます。
ローカル検索はメモリ上でフィルタリング、リモート検索は Supabase の ilike と配列演算子を使用します。
// ローカル全文検索 function searchLocal(memos: Memo[], query: string, tags: string[]) { return memos.filter(m => { const matchText = m.content.toLowerCase().includes(query.toLowerCase()); const matchTag = tags.length === 0 || tags.every(t => m.tags.includes(t)); return matchText && matchTag; }); } // リモート検索(公開メモ) async function searchRemote(query: string, tag: string) { const { data } = await supabase .from('memos') .select('*') .eq('is_public', true) .ilike('content', `%${query}%`) .contains('tags', tag ? [tag] : []); return data; }
・いいね機能 メモにはいいねをすることができ、有益な情報にいいねを付けて後から見返すことが可能です。
いいねはユニーク制約付きで管理し、重複いいねを防止しています。
// いいね追加 async function likeMemo(memoId: string, userId: string) { await supabase .from('likes') .upsert({ memo_id: memoId, user_id: userId }, { onConflict: 'memo_id,user_id' }); } // いいね解除 async function unlikeMemo(memoId: string, userId: string) { await supabase .from('likes') .delete() .match({ memo_id: memoId, user_id: userId }); }
お絵かき機能ではキャンバスを設置して好きなものを書くことができます
Canvas API でマウスの軌跡を描画し、toDataURL() で画像として保存します。
function DrawingCanvas() { const canvasRef = useRef<HTMLCanvasElement>(null); const drawing = useRef(false); const startDraw = (e: React.MouseEvent) => { drawing.current = true; const ctx = canvasRef.current!.getContext('2d')!; ctx.beginPath(); ctx.moveTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY); }; const draw = (e: React.MouseEvent) => { if (!drawing.current) return; const ctx = canvasRef.current!.getContext('2d')!; ctx.lineTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY); ctx.stroke(); }; const save = () => { const dataUrl = canvasRef.current!.toDataURL('image/png'); // dataUrl を GCS にアップロードして保存 }; return ( <canvas ref={canvasRef} onMouseDown={startDraw} onMouseMove={draw} onMouseUp={() => (drawing.current = false)} /> ); }
・マイページ機能 このページでは自身が保存したメモとそのURLを閲覧することができます
ログインユーザーのメモ一覧と統計(総数・公開数・いいね数)を取得して表示します。
// マイページデータ取得 async function fetchMyPage(userId: string) { const { data: memos } = await supabase .from('memos') .select('*, likes(count)') .eq('user_id', userId) .order('created_at', { ascending: false }); const stats = { total: memos?.length ?? 0, public: memos?.filter(m => m.is_public).length ?? 0, totalLikes: memos?.reduce((sum, m) => sum + (m.likes[0]?.count ?? 0), 0) ?? 0, }; return { memos, stats }; }
chromeの拡張機能で使えるためブラウザのタブを増やすことなくサイトの評価を見ることができる!
マイページにて過去自分が保存したメモと紐づけされたURLを確認できるため、開発中にメモを保存しておくことで後から有用だったサイトを共有しようとしたときにマイページを見るだけで当時参考にしていたサイトが分かる。
つまりchromeのブックマーク機能+メモでサイトを探すのが楽だしあとから見直したときに楽ができる
事前開発をしている段階でメインで開発を進めている人が風邪で4日ダウンしてしまった
バックエンドデプロイする用のRenderが一生動かなくて二日ぐらいバックの開発が止まっていた
(原因は参照するgithubのプロダクトが全く同じ名前のものが二つあり、参照を間違っていた)
そのため毎日22時に進捗報告を行っているが進捗が止まっている情報が出るだけの日があってつらかった




