Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate 2 #18

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
20 changes: 5 additions & 15 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
env: { browser: true, es2021: true, node: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
],
overrides: [
{
env: {
node: true,
},
env: { node: true },
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
parserOptions: { sourceType: "script" },
},
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
plugins: ["@typescript-eslint", "react"],
ignorePatterns: ["/dist/*"],
rules: {
"react/react-in-jsx-scope": "off", // React 17+ ではJSXトランスフォームが不要
"@typescript-eslint/no-explicit-any": "warn", // any型の使用を警告(エラーにしたい場合は`error`)
"no-unused-vars": "off",
},
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,8 @@
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.1.4"
},
"rules": {
"no-unused-vars": "off"
}
}
1 change: 1 addition & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default function App() {
try {
md = replaceExternalSyntax(md);
} catch (e: any) {
/* eslint @typescript-eslint/no-explicit-any: 0 */
md = e.toString();
}
MDToHTML(md.replaceAll(opts.prefix, "##").replaceAll(opts.suffix, ""))
Expand Down
313 changes: 313 additions & 0 deletions src/App1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
//マークダウンの編集
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 "./index.css";

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<positionInfo>(null); // ドラッグされた位置
const [inputValue, setInputValue] = useState("");
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false);
const [visualize, setVisualize] = useState(true); // テキストエリアを表示にするか非表示にするか
const [fileContent, setFileContent] = useState<string>("");
const [imageData, setImageData] = useState<string>("");

// 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
const d = ExtractDefinitions(markdown, opts.prefix, opts.suffix);
const newd = new Map<string, string>();
const promises: Promise<Map<string, string>>[] = [];
d.forEach((v, k) => {// eslint-disable-line

let md = replaceExternalSyntax(v);
md = md.replaceAll(opts.prefix, "##").replaceAll(opts.suffix, ""); // eslint-disable-line
//const p = MDToHTML(md).then((newv) => newd.set(k, newv));
//promises.push(p);
});
Promise.all(promises).then(() => setDict(newd));

// prepare HTML
let md;
try {
md = replaceExternalSyntax(markdown.replace(/!define[\s\S]*$/m, "")); // !define以下をすべて取り去る。
/* eslint @typescript-eslint/no-explicit-any: 0 */
} 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<string> };
}) => {
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 (
<>
<div className="save_container">
<div className="upload_save">
<UploadMarkdown onFileContentChange={setFileContent} />
<Button variant="text" onClick={saveFile}>
保存
</Button>
</div>
<div className="upload_save">
<UploadImage onImageChange={handleImageChange} />
</div>
</div>
{visualize == false && (
<>
<div className="upload_save">
<Button
variant="text"
onClick={() => {
setVisualize(true);
}}
>
編集画面の表示
</Button>
</div>
<div className="wrapper_false">
<ConvertMarkdown dictionary={dict} html={html} opts={opts} />
</div>
</>
)}
<div>{imageData}</div>
{visualize == true && (
<>
<div className="upload_save">
<Button
variant="text"
onClick={() => {
setVisualize(false);
}}
>
編集画面の非表示
</Button>
</div>
<div className="wrapper_true">
<div className="convert_markdown">
<ConvertMarkdown dictionary={dict} html={html} opts={opts} />
</div>
<textarea
value={markdown}
onChange={(event) => {
setMarkdown(event.target.value);
}}
placeholder="編集画面"
/>
</div>
</>
)}

{/* ドラッグして参照する部分 */}
{inputPosition && (
<>
<Textarea
value={inputValue}
onChange={handleInputChange}
style={{
position: "absolute",
top: `${inputPosition.top}px`,
left: `${inputPosition.left}px`,
}}
onFocus={handleTextAreaFocus}
onBlur={handleTextAreaBlur}
/>
<button
onClick={() =>
setMarkdown((markdown) => markdown + "\n" + inputValue + "\n")
}
style={{
position: "absolute",
top: `${inputPosition.top - 1}px`,
left: `${inputPosition.left + 250}px`,
}}
>
送信
</button>
</>
)}
</>
);
}

// this uses given dictionary as the source to extract definition from,
// and given html to render the main note.

function ConvertMarkdown({
dictionary,
html,
}: {
dictionary: Map<string, string>;
html: string;
opts: { prefix: string; suffix: string };
}) {
const parsing = html.split("\n");

dictionary = new Map(
[...dictionary.entries()].sort((a, b) => a[0].length - b[0].length),
); // Sort dictionary entries by word length to avoid overlapping replacements

// Replace words with tooltip-enabled spans
dictionary.forEach((_def: string, word: string) => {
let idx = 0;
for (const line of parsing) {
// Skip lines that are part of the definition to avoid replacing inside the definition itself
if (!line.includes(`<h2>${word}</h2>`)) {
parsing[idx] = line.replaceAll(
word,
`<span class="${word} underline">${word}</span>`,
);
}
idx++;
}
});

const parsedHtml = parsing.join("\n");

const options: HTMLReactParserOptions = {
replace(domNode) {
if (!(domNode instanceof Element)) {
return domNode;
}

const tagName = domNode.tagName;

// Handle images
if (tagName === "img") {
const src = domNode.attribs?.src;
const alt = domNode.attribs?.alt;
return (
<img src={src} alt={alt || "image"} style={{ maxWidth: "100%" }} />
);
}

const word: string | undefined = domNode.attribs?.class?.split(" ")[0];
const newClass: string = domNode.attribs?.class
?.split(" ")
.slice(0)
.join(" ");

// Handle words that should show tooltips
if (
domNode instanceof Element &&
domNode.attribs?.class &&
dictionary.has(word)
) {
return (
<Tippy
content={parse(dictionary.get(word) || "")}
className="markdown_tippy"
>
<span className={newClass}>{word}</span>
</Tippy>
);
}

return domNode; // Return the domNode unchanged if no special handling is needed
},
};

return <>{parse(parsedHtml, options)}</>; // パースされた HTML を返す
}
Loading
Loading