From c50ea4510dd58a1a37cb576fbe648962c06811e4 Mon Sep 17 00:00:00 2001 From: masaki-fukumura Date: Tue, 18 Feb 2025 21:06:49 +0900 Subject: [PATCH 1/2] Separate main functions --- package-lock.json | 75 +++++++++- package.json | 1 + src/App1.tsx | 317 +++++++++++++++++++++++++++++++++++++++++ src/App2.tsx | 310 ++++++++++++++++++++++++++++++++++++++++ src/App3.tsx | 286 +++++++++++++++++++++++++++++++++++++ src/AppRouters.tsx | 21 +++ src/extractPDF_old.tsx | 202 ++++++++++++++++++++++++++ src/main.tsx | 9 +- 8 files changed, 1213 insertions(+), 8 deletions(-) create mode 100644 src/App1.tsx create mode 100644 src/App2.tsx create mode 100644 src/App3.tsx create mode 100644 src/AppRouters.tsx create mode 100644 src/extractPDF_old.tsx diff --git a/package-lock.json b/package-lock.json index 7cb4c60..a936ad7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "react-markdown": "^9.0.1", "react-pdf": "^9.1.0", "react-remark": "^2.1.0", + "react-router-dom": "^7.1.5", "rehype-katex": "^7.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", @@ -2010,6 +2011,11 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -2892,6 +2898,14 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -5393,9 +5407,9 @@ } }, "node_modules/katex": { - "version": "0.16.10", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz", - "integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==", + "version": "0.16.21", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", + "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -7530,6 +7544,44 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/react-router": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.5.tgz", + "integrity": "sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.5.tgz", + "integrity": "sha512-/4f9+up0Qv92D3bB8iN5P1s3oHAepSGa9h5k6tpTFlixTTskJZwKGhJ6vRJ277tLD1zuaZTt95hyGWV1Z37csQ==", + "dependencies": { + "react-router": "7.1.5" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -7964,6 +8016,11 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "optional": true }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -8407,6 +8464,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8762,11 +8824,10 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/package.json b/package.json index 76b6c29..8de4cd8 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "react-markdown": "^9.0.1", "react-pdf": "^9.1.0", "react-remark": "^2.1.0", + "react-router-dom": "^7.1.5", "rehype-katex": "^7.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", diff --git a/src/App1.tsx b/src/App1.tsx new file mode 100644 index 0000000..b19597e --- /dev/null +++ b/src/App1.tsx @@ -0,0 +1,317 @@ +//マークダウンの編集 +import { useEffect, useState, SetStateAction } from "react"; +import parse, { Element, HTMLReactParserOptions } from "html-react-parser"; +// import Markdown from "react-markdown"; +// import rehypeKatex from "rehype-katex"; +// import remarkMath from "remark-math"; +import Tippy from "@tippyjs/react"; +// import markdownLink from "/hoge.md?url"; +import { ExtractDefinitions } from "./MDToDefinitions"; +import { MDToHTML } from "./MDToHTML"; +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"; +import UploadMarkdown from "./uploadMarkdown"; +import UploadImage from "./uploadImage"; + +import styled from "@emotion/styled"; + +type positionInfo = null | { top: number; left: number }; + +export default function App1() { + const [markdown, setMarkdown] = useState(""); + const [html, setHTML] = useState(""); + const [dict, setDict] = useState(new Map()); + const opts = { + prefix: "!define", + suffix: "!enddef", + }; + + // ドラッグして直接参照できる機能の部分 + const [inputPosition, setInputPosition] = useState(null); // ドラッグされた位置 + const [inputValue, setInputValue] = useState(""); + const [isTextAreaFocused, setIsTextAreaFocused] = useState(false); + const [visualize, setVisualize] = useState(true); // テキストエリアを表示にするか非表示にするか + const [fileContent, setFileContent] = useState(""); + const [imageData, setImageData] = useState(""); + + // get markdown + // useEffect(() => { + // fetch(markdownLink) + // .then((res) => res.text()) + // .then((t) => setMarkdown(t)) + // .catch((err) => console.error("Error fetching Hoge.md:", err)); + // }, []); + + useEffect(() => { + setMarkdown(fileContent); + }, [fileContent]); + + useEffect(() => { + localStorage.setItem("item", markdown); + }, [markdown]); // markdownの内容が変わるたびにlocalStorageに保存。 + + // use markdown (separation is necessary because it's async) + useEffect(() => void insideUseEffect(), [markdown]); + async function insideUseEffect() { + // prepare dictionary + let d = ExtractDefinitions(markdown, opts.prefix, opts.suffix); + const newd = new Map(); + const promises: Promise>[] = []; + d.forEach((v, k) => { + let md = replaceExternalSyntax(v); + md = md.replaceAll(opts.prefix, "##").replaceAll(opts.suffix, ""); + //const p = MDToHTML(md).then((newv) => newd.set(k, newv)); + //promises.push(p); + }); + Promise.all(promises).then(() => setDict(newd)); + + // prepare HTML + var md; + try { + md = replaceExternalSyntax(markdown.replace(/!define[\s\S]*$/m, "")); // !define以下をすべて取り去る。 + } catch (e: any) { + md = e.toString(); + } + MDToHTML(md.replaceAll(opts.prefix, "##").replaceAll(opts.suffix, "")) + .then((h) => setHTML(h)) + .catch(() => console.log("MDToHTML failed")); + } + + // ドラッグして直接参照できる機能の部分 + useEffect(() => { + const handleSelectionChange = () => { + const selection = document.getSelection(); + if (selection && selection.rangeCount > 0 && !isTextAreaFocused) { + // textareaがFocusされていないときのみ、selectionを発令する。 + const range = selection.getRangeAt(0); // Range { commonAncestorContainer: #text, startContainer: #text, startOffset: 8, endContainer: #text, endOffset: 23, collapsed: true } + // 左から8文字目から23文字目であることを指している。 + const rect = range.getBoundingClientRect(); // DOMRect { x: 209.56666564941406, y: 167.25, width: 130.38333129882812, height: 29, top: 167.25, right: 339.9499969482422, bottom: 196.25, left: 209.56666564941406 } + // 位置情報の取得 + + // setSelectedText(selection.toString()); // ...unused + + if (selection.toString()) { + // console.log(selection.toString()) 選択した範囲の文字列。 + setInputPosition({ + top: rect.bottom + window.scrollY, + left: rect.left + window.scrollX, + }); + setInputValue("!define " + selection.toString()); + } else { + setInputPosition(null); + } + } + }; + document.addEventListener("selectionchange", handleSelectionChange); + return () => { + document.removeEventListener("selectionchange", handleSelectionChange); + }; + }, [isTextAreaFocused]); + + const handleInputChange = (event: { + target: { value: SetStateAction }; + }) => { + setInputValue(event.target.value); + // console.log(inputValue) 入力された内容がここに入る。 + }; + + const handleImageChange = (content: string) => { + setImageData(content); + }; + + const handleTextAreaFocus = () => { + setIsTextAreaFocused(true); + }; + + const handleTextAreaBlur = () => { + setIsTextAreaFocused(false); + }; + + // テキストファイルを保存する + const saveFile = () => { + const blob = new Blob([markdown], { + type: ".md, text/markdown", + }); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + link.download = localStorage.getItem("filename") ?? "hoge.md"; // localStorage上に保存したファイル名を使う。 + link.click(); + }; + + return ( + <> +
+
+ + +
+
+ +
+
+ {visualize == false && ( + <> +
+ +
+
+ +
+ + )} +
{imageData}
+ {visualize == true && ( + <> +
+ +
+
+
+ +
+