|
|
@@ -1,73 +1,13 @@
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
import { useNavigate, useParams, Link } from 'react-router-dom';
|
|
|
-import MarkdownIt from 'markdown-it';
|
|
|
-import { full as emoji } from 'markdown-it-emoji';
|
|
|
-import container from "markdown-it-container";
|
|
|
-import abbr from "markdown-it-abbr";
|
|
|
-import deflist from "markdown-it-deflist";
|
|
|
-import footnote from "markdown-it-footnote";
|
|
|
-import mark from "markdown-it-mark";
|
|
|
-import sub from "markdown-it-sub";
|
|
|
-import sup from "markdown-it-sup";
|
|
|
-import ins from "markdown-it-ins";
|
|
|
-import spoiler from "markdown-it-spoiler";
|
|
|
-import DOMPurify from 'dompurify';
|
|
|
-
|
|
|
+import MDEditor from '@uiw/react-md-editor';
|
|
|
+import '@uiw/react-md-editor/markdown-editor.css';
|
|
|
+import '@uiw/react-markdown-preview/markdown.css';
|
|
|
const API_BASE = 'http://localhost:3001/api';
|
|
|
|
|
|
-// Markdown renderer setup (same as original App.jsx)
|
|
|
-const scrollableTablesPlugin = (md) => {
|
|
|
- const defaultRenderOpen = md.renderer.rules.table_open || function (tokens, idx, options, env, self) {
|
|
|
- return self.renderToken(tokens, idx, options);
|
|
|
- };
|
|
|
-
|
|
|
- const defaultRenderClose = md.renderer.rules.table_close || function (tokens, idx, options, env, self) {
|
|
|
- return self.renderToken(tokens, idx, options);
|
|
|
- };
|
|
|
-
|
|
|
- md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
|
|
|
- return '<div class="overflow-x-auto">' + defaultRenderOpen(tokens, idx, options, env, self);
|
|
|
- };
|
|
|
-
|
|
|
- md.renderer.rules.table_close = function (tokens, idx, options, env, self) {
|
|
|
- return defaultRenderClose(tokens, idx, options, env, self) + '</div>';
|
|
|
- };
|
|
|
-};
|
|
|
-
|
|
|
-const md = new MarkdownIt({
|
|
|
- html: true,
|
|
|
- linkify: true,
|
|
|
- typographer: true,
|
|
|
-});
|
|
|
-
|
|
|
-md.use(scrollableTablesPlugin)
|
|
|
- .use(emoji)
|
|
|
- .use(abbr)
|
|
|
- .use(sub)
|
|
|
- .use(sup)
|
|
|
- .use(ins)
|
|
|
- .use(mark)
|
|
|
- .use(deflist)
|
|
|
- .use(footnote)
|
|
|
- .use(spoiler)
|
|
|
- .use(container, "info")
|
|
|
- .use(container, "spoiler", {
|
|
|
- render(tokens, idx) {
|
|
|
- const token = tokens[idx];
|
|
|
- if (token.nesting === 1) {
|
|
|
- const m = token.info.trim().match(/^spoiler\s+(.*)$/);
|
|
|
- const title = m ? m[1] : "Spoiler";
|
|
|
- return `<details class="spoiler"><summary>${title}</summary>\n`;
|
|
|
- } else {
|
|
|
- return "</details>\n";
|
|
|
- }
|
|
|
- },
|
|
|
- })
|
|
|
- .use(container, "warning");
|
|
|
-
|
|
|
-md.renderer.rules.footnote_block_open = () => {
|
|
|
- return '<section class="footnotes"><ol class="list-decimal pl-6 mt-4">';
|
|
|
-};
|
|
|
+// Note: MDEditor handles its own markdown processing for the editor interface
|
|
|
+// The final blog rendering uses the MarkdownIt instance in App.jsx
|
|
|
+// This separation prevents footnote duplication issues
|
|
|
|
|
|
function PostEditor() {
|
|
|
const navigate = useNavigate();
|
|
|
@@ -84,7 +24,6 @@ function PostEditor() {
|
|
|
const [loading, setLoading] = useState(isEditing);
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
const [error, setError] = useState(null);
|
|
|
- const [previewMode, setPreviewMode] = useState(false);
|
|
|
|
|
|
useEffect(() => {
|
|
|
if (isEditing) {
|
|
|
@@ -173,12 +112,6 @@ function PostEditor() {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- const renderPreview = () => {
|
|
|
- if (!formData.content) return '<p class="text-gray-500">Write some content to see the preview...</p>';
|
|
|
-
|
|
|
- const htmlContent = md.render(formData.content);
|
|
|
- return DOMPurify.sanitize(htmlContent);
|
|
|
- };
|
|
|
|
|
|
if (loading) {
|
|
|
return (
|
|
|
@@ -287,51 +220,40 @@ function PostEditor() {
|
|
|
|
|
|
{/* Content Editor */}
|
|
|
<div className="bg-white shadow rounded-lg">
|
|
|
- <div className="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
|
|
+ <div className="px-6 py-4 border-b border-gray-200">
|
|
|
<h2 className="text-lg font-semibold text-gray-900">Content</h2>
|
|
|
- <div className="flex space-x-2">
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- onClick={() => setPreviewMode(false)}
|
|
|
- className={`px-3 py-1 text-sm rounded ${!previewMode ? 'bg-blue-100 text-blue-700' : 'text-gray-500 hover:text-gray-700'}`}
|
|
|
- >
|
|
|
- Edit
|
|
|
- </button>
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- onClick={() => setPreviewMode(true)}
|
|
|
- className={`px-3 py-1 text-sm rounded ${previewMode ? 'bg-blue-100 text-blue-700' : 'text-gray-500 hover:text-gray-700'}`}
|
|
|
- >
|
|
|
- Preview
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ <p className="text-sm text-gray-600 mt-1">
|
|
|
+ Use the WYSIWYG editor below. Click the tabs to switch between Edit and Preview modes.
|
|
|
+ </p>
|
|
|
</div>
|
|
|
|
|
|
<div className="px-6 py-4">
|
|
|
- {!previewMode ? (
|
|
|
- <div>
|
|
|
- <textarea
|
|
|
- id="content"
|
|
|
- required
|
|
|
- rows={20}
|
|
|
- value={formData.content}
|
|
|
- onChange={(e) => handleInputChange('content', e.target.value)}
|
|
|
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
|
|
|
- placeholder="Write your post content in Markdown..."
|
|
|
- />
|
|
|
- <div className="mt-2 text-xs text-gray-500">
|
|
|
- <p><strong>Supported Markdown features:</strong></p>
|
|
|
- <p>Basic syntax, tables, footnotes, emoji, custom containers (:::info, :::warning, :::spoiler), and more.</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- ) : (
|
|
|
- <div className="border border-gray-300 rounded-lg p-4 min-h-[500px]">
|
|
|
- <div
|
|
|
- className="markdown-content prose max-w-none"
|
|
|
- dangerouslySetInnerHTML={{ __html: renderPreview() }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- )}
|
|
|
+ <div className="border border-gray-300 rounded-lg overflow-hidden">
|
|
|
+ <MDEditor
|
|
|
+ value={formData.content}
|
|
|
+ onChange={(value) => handleInputChange('content', value || '')}
|
|
|
+ height={500}
|
|
|
+ preview="edit"
|
|
|
+ hideToolbar={false}
|
|
|
+ data-color-mode="light"
|
|
|
+ visibleDragBar={false}
|
|
|
+ textareaProps={{
|
|
|
+ placeholder: 'Write your post content in Markdown...',
|
|
|
+ style: { fontSize: '14px', fontFamily: 'ui-monospace, monospace' },
|
|
|
+ required: true
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="mt-3 text-xs text-gray-500 bg-blue-50 p-3 rounded-lg">
|
|
|
+ <p><strong>WYSIWYG Editor Features:</strong></p>
|
|
|
+ <ul className="list-disc list-inside mt-1 space-y-1">
|
|
|
+ <li>Toggle between <strong>Edit</strong>, <strong>Preview</strong>, and <strong>Live</strong> modes using the tabs</li>
|
|
|
+ <li>Use toolbar buttons for quick formatting (bold, italic, headers, lists, etc.)</li>
|
|
|
+ <li>Standard markdown features: tables, footnotes, emoji (:emoji:), HTML support</li>
|
|
|
+ <li>Collapsible content: Use <code><details><summary></code> HTML tags</li>
|
|
|
+ <li>Drag the divider to resize edit/preview panes in Live mode</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|