import { useCallback, useEffect, useRef, useState } from 'react' import CodeMirrorEditor from './CodeMirrorEditor' import Preview from './Preview' import { encodeDocument, decodeDocument } from './url' import sampleDoc from './sample.md?raw' import './App.css' function readHash(): string | null { const h = window.location.hash.replace(/^#/, '') if (!h) return null try { return decodeDocument(h) } catch { return null } } export default function App() { const [text, setText] = useState(() => readHash() ?? sampleDoc) const [urlLength, setUrlLength] = useState(() => window.location.href.length) const [copied, setCopied] = useState(false) // Debounced text -> URL. replaceState (not assignment) so typing doesn't // flood the browser history; lastWrittenRef lets the hashchange listener // tell our own writes apart from external navigation. const lastWrittenRef = useRef(null) useEffect(() => { const t = setTimeout(() => { const encoded = text ? encodeDocument(text) : '' lastWrittenRef.current = encoded const url = new URL(window.location.href) url.hash = encoded window.history.replaceState(null, '', url) setUrlLength(url.href.length) }, 300) return () => clearTimeout(t) }, [text]) // URL -> text, when the user pastes a different mdshare link into the // address bar or navigates back/forward. useEffect(() => { const onHashChange = () => { const h = window.location.hash.replace(/^#/, '') if (h === lastWrittenRef.current) return const decoded = readHash() if (decoded !== null) setText(decoded) } window.addEventListener('hashchange', onHashChange) return () => window.removeEventListener('hashchange', onHashChange) }, []) const copyLink = useCallback(async () => { // Encode the current text directly so a fast type-then-copy can't race // the debounced URL write. const url = new URL(window.location.href) url.hash = text ? encodeDocument(text) : '' lastWrittenRef.current = url.hash.replace(/^#/, '') window.history.replaceState(null, '', url) setUrlLength(url.href.length) await navigator.clipboard.writeText(url.href) setCopied(true) setTimeout(() => setCopied(false), 1500) }, [text]) const newDocument = useCallback(() => { if (text.trim() && text !== sampleDoc) { if (!window.confirm('Discard the current document? (The old link keeps working.)')) return } setText('') }, [text]) // Simple draggable splitter const [split, setSplit] = useState(0.5) const mainRef = useRef(null) const onSplitterDown = useCallback((e: React.PointerEvent) => { e.preventDefault() const main = mainRef.current if (!main) return const rect = main.getBoundingClientRect() const onMove = (ev: PointerEvent) => { const f = (ev.clientX - rect.left) / rect.width setSplit(Math.min(0.8, Math.max(0.2, f))) } const onUp = () => { window.removeEventListener('pointermove', onMove) window.removeEventListener('pointerup', onUp) } window.addEventListener('pointermove', onMove) window.addEventListener('pointerup', onUp) }, []) return (
mdshare markdown that lives in the URL URL: {urlLength.toLocaleString()} chars
) }