article editor
This commit is contained in:
1792
package-lock.json
generated
1792
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,16 +12,23 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@monaco-editor/react": "^4.7.0",
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"@reduxjs/toolkit": "^2.9.2",
|
"@reduxjs/toolkit": "^2.9.2",
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/react-redux": "^7.1.33",
|
"@types/react-redux": "^7.1.33",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^11.9.0",
|
"framer-motion": "^11.9.0",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"monaco-editor": "^0.54.0",
|
"monaco-editor": "^0.54.0",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.9.4",
|
"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-cn": "^1.0.2",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"tailwindcss": "^3.4.12"
|
"tailwindcss": "^3.4.12"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Route, Routes } from "react-router-dom";
|
|||||||
import Home from "./pages/Home";
|
import Home from "./pages/Home";
|
||||||
import Mission from "./pages/Mission";
|
import Mission from "./pages/Mission";
|
||||||
import UploadMissionForm from "./views/mission/UploadMissionForm";
|
import UploadMissionForm from "./views/mission/UploadMissionForm";
|
||||||
|
import MarkdownEditor from "./views/articleeditor/Editor";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -16,7 +17,7 @@ function App() {
|
|||||||
<Route path="/home/*" element={<Home />} />
|
<Route path="/home/*" element={<Home />} />
|
||||||
<Route path="/mission/:missionId" element={<Mission />} />
|
<Route path="/mission/:missionId" element={<Mission />} />
|
||||||
<Route path="/upload" element={<UploadMissionForm/>}/>
|
<Route path="/upload" element={<UploadMissionForm/>}/>
|
||||||
<Route path="*" element={<Home />} />
|
<Route path="*" element={<MarkdownEditor onChange={(value: string) => {}}/>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
0
src/pages/ArticleEditor.tsx
Normal file
0
src/pages/ArticleEditor.tsx
Normal file
300
src/views/articleeditor/Editor.tsx
Normal file
300
src/views/articleeditor/Editor.tsx
Normal 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-логотип:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 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;
|
||||||
@@ -11,5 +11,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [require('@tailwindcss/typography')],
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user