implement generic markdown input form for content

This commit is contained in:
austinkelsay 2025-05-11 15:43:53 -05:00
parent 027bf28e2f
commit 7a805f0988
No known key found for this signature in database
GPG Key ID: 5A763922E5BA08EE
9 changed files with 193 additions and 91 deletions

View File

@ -1,19 +1,17 @@
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch';
import GenericButton from '@/components/buttons/GenericButton';
import { useToast } from '@/hooks/useToast';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import dynamic from 'next/dynamic';
import { Tooltip } from 'primereact/tooltip';
import { useToast } from '@/hooks/useToast';
import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
import 'primereact/resources/primereact.min.css';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const CDN_ENDPOINT = process.env.NEXT_PUBLIC_CDN_ENDPOINT;
@ -199,9 +197,7 @@ const CombinedResourceForm = () => {
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span>
<div data-color-mode="dark">
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
<MarkdownEditor value={content} onChange={handleContentChange} height={350} />
</div>
<div className="mt-8 flex-col w-full">

View File

@ -1,19 +1,17 @@
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch';
import GenericButton from '@/components/buttons/GenericButton';
import { useToast } from '@/hooks/useToast';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import dynamic from 'next/dynamic';
import { Tooltip } from 'primereact/tooltip';
import { useToast } from '@/hooks/useToast';
import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
import 'primereact/resources/primereact.min.css';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const CDN_ENDPOINT = process.env.NEXT_PUBLIC_CDN_ENDPOINT;
@ -242,9 +240,7 @@ const EditDraftCombinedResourceForm = ({ draft }) => {
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span>
<div data-color-mode="dark">
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
<MarkdownEditor value={content} onChange={handleContentChange} height={350} />
</div>
<div className="mt-8 flex-col w-full">

View File

@ -1,23 +1,22 @@
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react';
import { useNDKContext } from '@/context/NDKContext';
import GenericButton from '@/components/buttons/GenericButton';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { validateEvent } from '@/utils/nostr';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch';
import GenericButton from '@/components/buttons/GenericButton';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { useToast } from '@/hooks/useToast';
import { useNDKContext } from '@/context/NDKContext';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { validateEvent } from '@/utils/nostr';
import { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
import MoreInfo from '@/components/MoreInfo';
import dynamic from 'next/dynamic';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), {
ssr: false,
});
import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
import 'primereact/resources/primereact.min.css';
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const EditPublishedCombinedResourceForm = ({ event }) => {
const router = useRouter();
@ -220,9 +219,7 @@ const EditPublishedCombinedResourceForm = ({ event }) => {
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Video Embed</span>
<div data-color-mode="dark">
<MDEditor value={videoEmbed} onChange={handleVideoEmbedChange} height={250} />
</div>
<MarkdownEditor value={videoEmbed} onChange={handleVideoEmbedChange} height={250} />
<small className="text-gray-400 mt-2">
You can customize your video embed using markdown or HTML. For example, paste iframe
embeds from YouTube or Vimeo, or use video tags for direct video files.
@ -239,9 +236,7 @@ const EditPublishedCombinedResourceForm = ({ event }) => {
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span>
<div data-color-mode="dark">
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
<MarkdownEditor value={content} onChange={handleContentChange} height={350} />
</div>
<div className="mt-8 flex-col w-full">

View File

@ -1,21 +1,20 @@
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch';
import { Calendar } from 'primereact/calendar';
import GenericButton from '@/components/buttons/GenericButton';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { useToast } from '@/hooks/useToast';
import { useNDKContext } from '@/context/NDKContext';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import dynamic from 'next/dynamic';
import { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), {
ssr: false,
});
import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
import 'primereact/resources/primereact.min.css';
import { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const EmbeddedDocumentForm = ({ draft = null, isPublished = false, onSave, isPaid }) => {
const [title, setTitle] = useState(draft?.title || '');
@ -183,9 +182,7 @@ const EmbeddedDocumentForm = ({ draft = null, isPublished = false, onSave, isPai
</div>
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span>
<div data-color-mode="dark">
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
<MarkdownEditor value={content} onChange={handleContentChange} height={350} />
</div>
<div className="mt-8 flex-col w-full">
<span className="pl-1 flex items-center">
@ -219,7 +216,6 @@ const EmbeddedDocumentForm = ({ draft = null, isPublished = false, onSave, isPai
<div className="w-full flex flex-row items-end justify-end py-2">
<GenericButton icon="pi pi-plus" onClick={addAdditionalLink} />
</div>
<Tooltip target=".pi-info-circle" />
</div>
<div className="mt-8 flex-col w-full">
{topics.map((topic, index) => (

View File

@ -8,14 +8,10 @@ import GenericButton from '@/components/buttons/GenericButton';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { useToast } from '@/hooks/useToast';
import dynamic from 'next/dynamic';
import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
import 'primeicons/primeicons.css';
import 'primereact/resources/primereact.min.css';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), {
ssr: false,
});
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const DocumentForm = () => {
const [title, setTitle] = useState('');
@ -149,9 +145,11 @@ const DocumentForm = () => {
</div>
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span>
<div data-color-mode="dark">
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
<MarkdownEditor
value={content}
onChange={handleContentChange}
height={350}
/>
</div>
<div className="mt-8 flex-col w-full">
<span className="pl-1 flex items-center">

View File

@ -8,10 +8,10 @@ import GenericButton from '@/components/buttons/GenericButton';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { useToast } from '@/hooks/useToast';
import dynamic from 'next/dynamic';
import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
import 'primereact/resources/primereact.min.css';
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const EditDraftDocumentForm = ({ draft }) => {
const [title, setTitle] = useState(draft?.title || '');
@ -143,9 +143,7 @@ const EditDraftDocumentForm = ({ draft }) => {
</div>
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span>
<div data-color-mode="dark">
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
<MarkdownEditor value={content} onChange={handleContentChange} height={350} />
</div>
<div className="mt-8 flex-col w-full">
<span className="pl-1 flex items-center">

View File

@ -1,21 +1,21 @@
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react';
import { useNDKContext } from '@/context/NDKContext';
import { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
import GenericButton from '@/components/buttons/GenericButton';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { validateEvent } from '@/utils/nostr';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch';
import GenericButton from '@/components/buttons/GenericButton';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { useToast } from '@/hooks/useToast';
import { useNDKContext } from '@/context/NDKContext';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { validateEvent } from '@/utils/nostr';
import { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
import dynamic from 'next/dynamic';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
import 'primereact/resources/primereact.min.css';
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const EditPublishedDocumentForm = ({ event }) => {
const router = useRouter();
@ -198,9 +198,7 @@ const EditPublishedDocumentForm = ({ event }) => {
</div>
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span>
<div data-color-mode="dark">
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
<MarkdownEditor value={content} onChange={handleContentChange} height={350} />
</div>
<div className="mt-8 flex-col w-full">
<span className="pl-1 flex items-center">

View File

@ -1,23 +1,22 @@
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';
import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react';
import { useNDKContext } from '@/context/NDKContext';
import GenericButton from '@/components/buttons/GenericButton';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { validateEvent } from '@/utils/nostr';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch';
import GenericButton from '@/components/buttons/GenericButton';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { useToast } from '@/hooks/useToast';
import { useNDKContext } from '@/context/NDKContext';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { validateEvent } from '@/utils/nostr';
import { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
import MoreInfo from '@/components/MoreInfo';
import dynamic from 'next/dynamic';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), {
ssr: false,
});
import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
import 'primereact/resources/primereact.min.css';
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const EditPublishedVideoForm = ({ event }) => {
const router = useRouter();
@ -190,9 +189,7 @@ const EditPublishedVideoForm = ({ event }) => {
</div>
<div className="p-inputgroup flex-1 flex-col mt-4">
<span>Video Embed</span>
<div data-color-mode="dark">
<MDEditor value={videoEmbed} onChange={handleVideoEmbedChange} height={250} />
</div>
<MarkdownEditor value={videoEmbed} onChange={handleVideoEmbedChange} height={250} />
<small className="text-gray-400 mt-2">
You can customize your video embed using markdown or HTML. For example, paste iframe
embeds from YouTube or Vimeo, or use video tags for direct video files.

View File

@ -0,0 +1,128 @@
import React from 'react';
import dynamic from 'next/dynamic';
import '@uiw/react-md-editor/markdown-editor.css';
import '@uiw/react-markdown-preview/markdown.css';
import 'github-markdown-css/github-markdown-dark.css';
// Custom theme for MDEditor
const mdEditorDarkTheme = {
markdown: '#fff',
markdownH1: '#fff',
markdownH2: '#fff',
markdownH3: '#fff',
markdownH4: '#fff',
markdownH5: '#fff',
markdownH6: '#fff',
markdownParagraph: '#fff',
markdownLink: '#58a6ff',
markdownCode: '#fff',
markdownList: '#fff',
markdownBlockquote: '#fff',
markdownTable: '#fff',
};
// Dynamically import MDEditor with custom theming
const MDEditor = dynamic(() => import('@uiw/react-md-editor').then(mod => {
// Override the module's default theme
if (mod.default) {
mod.default.Markdown = {
...mod.default.Markdown,
...mdEditorDarkTheme
};
}
return mod;
}), {
ssr: false,
});
/**
* A reusable markdown editor component with proper dark mode styling
*
* @param {Object} props
* @param {string} props.value - The markdown content
* @param {Function} props.onChange - Callback function when content changes
* @param {number} props.height - Height of the editor (default: 300)
* @param {string} props.placeholder - Placeholder text for the editor
* @param {string} props.preview - Preview mode ('edit', 'preview', 'live') (default: 'edit')
* @param {string} props.className - Additional class names
* @returns {JSX.Element}
*/
const MarkdownEditor = ({
value,
onChange,
height = 300,
placeholder = "Write your content here...",
preview = "edit",
className = "",
...props
}) => {
return (
<div data-color-mode="dark" className={`w-full ${className}`} style={{ colorScheme: 'dark' }}>
<MDEditor
value={value}
onChange={onChange}
height={height}
preview={preview}
className="md-editor-dark"
textareaProps={{
placeholder,
style: { color: "white" }
}}
{...props}
/>
<style jsx global>{`
/* Force all text to white in editor */
.w-md-editor * {
color: white !important;
}
/* Reset preview text color */
.w-md-editor-preview * {
color: #c9d1d9 !important;
}
/* Editor backgrounds */
.md-editor-dark {
background-color: #0d1117 !important;
}
.w-md-editor-text-input {
caret-color: white !important;
-webkit-text-fill-color: white !important;
color: white !important;
}
.w-md-editor-toolbar {
background-color: #161b22 !important;
border-bottom: 1px solid #30363d !important;
}
/* Preview styling */
.w-md-editor-preview {
background-color: #0d1117 !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
/* Make code blocks maintain their styling */
.w-md-editor-preview pre {
background-color: #1e1e1e !important;
color: #d4d4d4 !important;
padding: 1em !important;
border-radius: 5px !important;
}
.w-md-editor-preview code {
font-family: 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace !important;
color: #d4d4d4 !important;
}
/* Force anything with text-rendering to be white */
[style*="text-rendering"] {
color: white !important;
}
`}</style>
</div>
);
};
export default MarkdownEditor;