article editor

This commit is contained in:
Виталий Лавшонок
2025-11-04 12:44:16 +03:00
parent 193234b9e5
commit 1ef655803a
6 changed files with 2100 additions and 5 deletions

1792
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,16 +12,23 @@
"dependencies": {
"@monaco-editor/react": "^4.7.0",
"@reduxjs/toolkit": "^2.9.2",
"@tailwindcss/typography": "^0.5.19",
"@types/react-redux": "^7.1.33",
"axios": "^1.12.2",
"clsx": "^2.1.1",
"framer-motion": "^11.9.0",
"highlight.js": "^11.11.1",
"monaco-editor": "^0.54.0",
"postcss": "^8.4.47",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^10.1.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.4",
"rehype-highlight": "^7.0.2",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.1",
"tailwind-cn": "^1.0.2",
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.4.12"

View File

@@ -7,6 +7,7 @@ import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Mission from "./pages/Mission";
import UploadMissionForm from "./views/mission/UploadMissionForm";
import MarkdownEditor from "./views/articleeditor/Editor";
function App() {
return (
@@ -16,7 +17,7 @@ function App() {
<Route path="/home/*" element={<Home />} />
<Route path="/mission/:missionId" element={<Mission />} />
<Route path="/upload" element={<UploadMissionForm/>}/>
<Route path="*" element={<Home />} />
<Route path="*" element={<MarkdownEditor onChange={(value: string) => {}}/>} />
</Routes>
</div>

View File

View File

@@ -0,0 +1,300 @@
import { FC, useEffect, useState } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import rehypeHighlight from "rehype-highlight";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
import "highlight.js/styles/github-dark.css";
import Header from "../mission/statement/Header";
interface MarkdownEditorProps {
defaultValue?: string;
onChange: (value: string) => void;
}
const MarkdownEditor: FC<MarkdownEditorProps> = ({
defaultValue,
onChange,
}) => {
const [markdown, setMarkdown] = useState<string>(defaultValue ? defaultValue :
`# 🌙 Добро пожаловать в Markdown-редактор
Добро пожаловать в **Markdown-редактор**!
Здесь ты можешь писать в формате Markdown и видеть результат **в реальном времени** 👇
---
## 🧱 1. Форматирование текста
Вот примеры базового форматирования:
- **Жирный текст**
- *Курсивный текст*
- ***Жирный курсив***
- ~~Зачёркнутый~~
> 💬 _Цитаты_ можно использовать для выделения текста, заметок или описаний.
---
## 🧩 2. Списки
### 🔹 Маркированный список
- Один
- Два
- Вложенный уровень
- Ещё глубже
- Три
### 🔸 Нумерованный список
1. Первый
2. Второй
3. Третий
1. Вложенный
2. Ещё один
---
## ✅ 3. Чеклисты (GFM)
- [x] Поддержка Markdown
- [x] Подсветка кода
- [x] Таблицы
- [x] Эмодзи 😎
- [ ] Экспорт в PDF (в будущем)
---
## 💻 4. Код и подсветка
Пример **TypeScript**:
\`\`\`tsx
type User = {
name: string;
role: "Разработчик" | "Помощник";
};
function greet(user: User) {
return \`Привет, \${user.name}! 👋 Роль: \${user.role}\`;
}
console.log(greet({ name: "Ты", role: "Разработчик" }));
\`\`\`
Пример **JavaScript**:
\`\`\`js
const sum = (a, b) => a + b;
console.log(sum(2, 3)); // 5
\`\`\`
Пример **Python**:
\`\`\`python
def greet(name):
return f"Привет, {name}! 👋"
print(greet("Мир"))
\`\`\`
---
## 📊 5. Таблицы (GFM)
| Имя | Роль | Активен | Эмодзи |
|-------------|----------------|----------|--------|
| ChatGPT | Помощник 🤖 | ✅ | 🤓 |
| Ты | Разработчик 💻 | ✅ | 🚀 |
| TailwindCSS | Стилизация 🎨 | 🟢 | 💅 |
> Таблицы поддерживают **жирный текст**, _курсив_ и даже \`инлайн-код\` внутри ячеек.
---
## 🔗 6. Ссылки
- [Документация Markdown](https://www.markdownguide.org/)
- [React Markdown на GitHub](https://github.com/remarkjs/react-markdown)
- Автоматическая ссылка: https://github.com
---
## 🖼️ 7. Изображения
### Markdown-логотип:
![Markdown Logo](https://upload.wikimedia.org/wikipedia/commons/4/48/Markdown-mark.svg)
---
## 🧠 8. Цитаты и вложенность
> 💭 Это обычная цитата.
>
> > А это — **вложенная цитата**.
> >
> > > Можно вкладывать сколько угодно уровней!
---
## ⚙️ 9. Горизонтальные линии
---
***
---
## 🧮 10. Таблица внутри цитаты
> Вот таблица прямо внутри блока цитаты:
>
> | Язык | Назначение |
> |-------|-------------|
> | JS | Web-разработка |
> | TS | Строгая типизация |
> | PY | Скрипты и AI |
---
## 🧩 11. Встроенный HTML
<details>
<summary>📂 Раскрывающийся блок</summary>
Этот текст виден только после раскрытия!
<ul>
<li>HTML списки работают</li>
<li>И даже <b>жирный текст</b></li>
</ul>
</details>
---
## 🎨 12. Вложенные списки с кодом
- Этапы:
1. Создай проект
2. Добавь зависимости:
\`\`\`bash
npm install react-markdown remark-gfm rehype-highlight highlight.js
\`\`\`
3. Импортируй стили:
\`\`\`tsx
import "highlight.js/styles/github-dark.css";
\`\`\`
4. Готово!
---
## 🚀 13. Финал
Поздравляю! 🎉
Ты только что увидел все ключевые возможности **Markdown + GFM** в действии.
> ✨ Используй этот текст как шаблон для тестирования рендерера.
> 💡 Совет: попробуй поменять тему \`highlight.js\` (например \`monokai.css\` или \`atom-one-dark.css\`).
---
**🖤 Конец демонстрации. Спасибо, что используешь Markdown-редактор!**
`);
useEffect(() => {
onChange(markdown);
}, [markdown])
return (
<div className="h-screen grid grid-rows-[60px,1fr]">
<div className="">
<Header missionId={1} />
</div>
<div className="grid grid-cols-2 h-full min-h-0">
<div className="overflow-y-auto min-h-0 overflow-hidden ">
<div className="p-4 border-r border-gray-700 flex flex-col h-full ">
<h2 className="text-lg font-semibold mb-3 text-gray-100">👀 Предпросмотр</h2>
<div className="flex-1 bg-[#161b22] rounded-lg shadow-lg p-6 h-[calc(100%-40px)]">
<div className="prose prose-invert max-w-none h-full overflow-auto pr-4 medium-scrollbar">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[
rehypeRaw,
rehypeSanitize,
rehypeHighlight
]}
>
{markdown}
</ReactMarkdown>
</div>
</div>
</div>
</div>
<div className="overflow-y-auto min-h-0 overflow-hidden ">
<div className="p-4 border-r border-gray-700 flex flex-col h-full ">
<h2 className="text-lg font-semibold mb-3 text-gray-100">📝 Редактор</h2>
<textarea
value={markdown}
onChange={(e) => setMarkdown(e.target.value)}
className="flex-1 w-full bg-[#0d1117] text-gray-200 border border-gray-700
rounded-lg p-5 font-mono text-sm resize-none focus:outline-none focus:ring-2
medium-scrollbar
"
placeholder="Пиши в формате Markdown..."
/>
</div>
</div>
</div>
</div>
);
}
/*
<div className="grid grid-cols-2 h-screen bg-[#0d1117] text-gray-200 relative">
<div className="p-4 border-r border-gray-700 bg-[#161b22] flex flex-col h-screen ">
<h2 className="text-lg font-semibold mb-3 text-gray-100">📝 Редактор</h2>
<textarea
value={markdown}
onChange={(e) => setMarkdown(e.target.value)}
className="flex-1 w-full bg-[#0d1117] text-gray-200 border border-gray-700
rounded-lg p-3 font-mono text-sm resize-none focus:outline-none focus:ring-2
medium-scrollbar
"
placeholder="Пиши в формате Markdown..."
/>
</div>
<div className="p-4 bg-[#0d1117] flex flex-col h-full relative">
<h2 className="text-lg font-semibold mb-[12px] text-gray-100 h-[28px]">👀 Предпросмотр</h2>
<div className="flex-1 bg-[#161b22] rounded-lg shadow-lg p-6 h-[calc(100%-40px)]">
<div className="prose prose-invert max-w-none h-full overflow-auto pr-4 medium-scrollbar">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[
rehypeRaw,
rehypeSanitize,
rehypeHighlight
]}
>
{markdown}
</ReactMarkdown>
</div>
</div>
</div>
</div>
*/
export default MarkdownEditor;

View File

@@ -11,5 +11,6 @@ export default {
},
},
},
plugins: [],
plugins: [require('@tailwindcss/typography')],
};