Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,067 changes: 550 additions & 517 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"react-markdown": "^9.0.1",
"react-pdf": "^9.1.0",
"react-remark": "^2.1.0",
"react-router-dom": "^7.2.0",
"rehype-katex": "^7.0.0",
"rehype-stringify": "^10.0.0",
"remark-gfm": "^4.0.0",
Expand All @@ -43,6 +44,6 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.1.4"
"vite": "^6.2.0"
}
}
150 changes: 136 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import { replaceExternalSyntax } from "./external-syntax";
import { ExtractPDF } from "./extractPDF";
import pdfFile from "/chibutsu_nyumon.pdf";
import Textarea from "@mui/joy/Textarea";
import { Button } from "@mui/material";
import "katex/dist/katex.min.css";
import "tippy.js/dist/tippy.css";
Expand All @@ -18,17 +17,32 @@
export default function App() {
const [markdown, setMarkdown] = useState("");
const [html, setHTML] = useState("");
const [dict, setDict] = useState(new Map());
const [dict, setDict] = useState(new Map<string, string>());
const opts = { prefix: "!define", suffix: "!enddef" };
const [inputPosition, setInputPosition] = useState<positionInfo>(null);
const [inputValue, setInputValue] = useState("");
const [fixedInputValue, setFixedInputValue] = useState("!define");
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false);
const [visualize, setVisualize] = useState(true);
const [fileContent, setFileContent] = useState<string>("");
const [imageData, setImageData] = useState<string>("");
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
const inputRef = useRef<HTMLTextAreaElement | null>(null);
const previewRef = useRef<HTMLDivElement | null>(null);

// 2/3追加・テンプレート部分

const [buttonName, setButtonName] = useState<string[]>([
"積分記号",
"偏微分",
]);
const [buttonContent, setButtonContent] = useState<string[]>([
"\\int_a^b dx",
"\\frac{\\partial}{\\partial x}",
]);
const [nameText, setNameText] = useState<string>("");
const [contentText, setContentText] = useState<string>("");

useEffect(() => {
setMarkdown(fileContent);
}, [fileContent]);
Expand All @@ -53,7 +67,7 @@
let md = replaceExternalSyntax(markdown.replace(/!define[\s\S]*$/m, ""));
try {
md = replaceExternalSyntax(md);
} catch (e: any) {

Check warning on line 70 in src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint (ESLint)

Unexpected any. Specify a different type
md = e.toString();
}
MDToHTML(md.replaceAll(opts.prefix, "##").replaceAll(opts.suffix, ""))
Expand Down Expand Up @@ -93,6 +107,12 @@
setInputValue(event.target.value);
};

const handleFixedInputChange = (event: {
target: { value: SetStateAction<string> };
}) => {
setFixedInputValue(event.target.value);
};

const handleScrollSync = (
sourceRef: React.RefObject<HTMLElement>,
targetRef: React.RefObject<HTMLElement>,
Expand All @@ -103,7 +123,15 @@
};

const insertDollarSignsAtCursor = (command: string) => {
if (textAreaRef.current) {
if (inputRef.current) {
const input = inputRef.current;
console.log(input.selectionStart, input.selectionEnd);
const start = input.selectionStart ?? 0;
const end = input.selectionEnd ?? 0;
const newText = `${inputValue.slice(0, start)}$$ \n ${command} \n $$${inputValue.slice(end)}`;
setInputValue(newText);
} else if (textAreaRef.current) {
// textAreaRef.currentがnullでないことを確認
const textarea = textAreaRef.current;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
Expand Down Expand Up @@ -157,16 +185,80 @@
編集画面の非表示
</Button>
</div>
<button onClick={() => insertDollarSignsAtCursor("\\int_a^b dx")}>
積分記号
</button>

{/* 2/3追加 テンプレート部 */}
{buttonName.map((name, index) => (
<>
<button
onClick={() => insertDollarSignsAtCursor(buttonContent[index])}
>
{name}
</button>
<button
onClick={() => {
setButtonName(buttonName.filter((_, i) => i !== index));
setButtonContent(buttonContent.filter((_, i) => i !== index));
}}
>
削除
</button>
</>
))}
<button
onClick={() =>
insertDollarSignsAtCursor("\\frac{\\partial}{\\partial x}")
}
onClick={() => {
setButtonName([...buttonName, nameText]);
setButtonContent([...buttonContent, contentText]);
}}
>
偏微分
+ 追加
</button>
<input
onChange={(event) => {
setNameText(event.target.value);
}}
></input>
<input
onChange={(event) => {
setContentText(event.target.value);
}}
></input>
<div>
<textarea
value={fixedInputValue}
ref={inputRef}
onChange={handleFixedInputChange}
/>
<button
onClick={() => {
setMarkdown(
(markdown) => markdown + "\n" + fixedInputValue + "\n",
);
setFixedInputValue("!define");
}}
>
送信
</button>
</div>
<WordDictionary
dictionary={dict}
html={html}
opts={opts}
// Removeボタンを押した時の処理
onRemove={(key) => {
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(
`!define\\s+${escapedKey}[\\s\\S]*?(?=!define|$)`,
"g",
);
setMarkdown(markdown.replace(regex, ""));
setDict((prevDict) => {
const newDict = new Map(prevDict);
newDict.delete(key);
return newDict;
});
}}
/>
{/* ここまで */}
<div className="wrapper_true">
<div
className="convert_markdown"
Expand All @@ -191,8 +283,9 @@
<ExtractPDF pdfName={pdfFile} opts={opts} />
{inputPosition && (
<>
<Textarea
<textarea
value={inputValue}
ref={inputRef}
onChange={handleInputChange}
style={{
position: "absolute",
Expand All @@ -203,9 +296,10 @@
onBlur={() => setIsTextAreaFocused(false)}
/>
<button
onClick={() =>
setMarkdown((markdown) => markdown + "\n" + inputValue + "\n")
}
onClick={() => {
console.log(inputValue);
setMarkdown((markdown) => markdown + "\n" + inputValue + "\n");
}}
style={{
position: "absolute",
top: `${inputPosition.top - 1}px`,
Expand Down Expand Up @@ -277,3 +371,31 @@

return <>{parse(parsedHtml, options)}</>;
}

function WordDictionary({
dictionary,
onRemove,
}: {
dictionary: Map<string, string>;
html: string;
opts: { prefix: string; suffix: string };
onRemove: (key: string, value: string) => void;
}) {
// キーの長さ順にソートした Map を配列として取得
const sortedEntries = [...dictionary.entries()].sort(
(a, b) => a[0].length - b[0].length,
);

return (
<ul>
{sortedEntries.map(([key, value], index) => (
<li key={index}>
<Tippy content={parse(value)} className="markdown_tippy">
<span>{key}</span>
</Tippy>
<button onClick={() => onRemove(key, value)}>Remove</button>
</li>
))}
</ul>
);
}
22 changes: 22 additions & 0 deletions src/AppRoutes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BrowserRouter, Route, Routes } from "react-router-dom";

import App from "./App.tsx";

import Home from "./Home.tsx";
import UserGuide from "./UserGuide/UserGuide.tsx";
import UserGuide_2 from "./UserGuide/UserGuide_2.tsx";

const AppRouter = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/App" element={<App />} />
<Route path="/UserGuide/1" element={<UserGuide />} />
<Route path="/UserGuide/2" element={<UserGuide_2 />} />
</Routes>
</BrowserRouter>
);
};

export default AppRouter;
17 changes: 17 additions & 0 deletions src/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function Home() {
return (
<div>
<h1>Mathdownへようこそ</h1>
<ul>
<li>
<a href="/App">アプリを開く</a>
</li>
<li>
<a href="/UserGuide/1">使い方</a>
</li>
</ul>
</div>
);
}

export default Home;
12 changes: 12 additions & 0 deletions src/UserGuide/UserGuide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function UserGuide() {
return (
<div>
<h1>使い方</h1>
<p>
<a href="/UserGuide/1">{"<"}</a>1/2<a href="/UserGuide/2">{">"}</a>
</p>
</div>
);
}

export default UserGuide;
12 changes: 12 additions & 0 deletions src/UserGuide/UserGuide_2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function UserGuide_2() {
return (
<div>
<h1>使い方</h1>
<p>
<a href="/UserGuide/1">{"<"}</a>2/2<a href="/UserGuide/1">{">"}</a>
</p>
</div>
);
}

export default UserGuide_2;
4 changes: 2 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import AppRoutes from "./AppRoutes.tsx";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
<AppRoutes />
</React.StrictMode>,
);
Loading