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

View File

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

View File

@ -1,23 +1,22 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios'; 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 { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea'; import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber'; import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch'; 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 { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
import MoreInfo from '@/components/MoreInfo'; import MoreInfo from '@/components/MoreInfo';
import dynamic from 'next/dynamic'; import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { import 'primereact/resources/primereact.min.css';
ssr: false, import MarkdownEditor from '@/components/markdown/MarkdownEditor';
});
const EditPublishedCombinedResourceForm = ({ event }) => { const EditPublishedCombinedResourceForm = ({ event }) => {
const router = useRouter(); const router = useRouter();
@ -220,9 +219,7 @@ const EditPublishedCombinedResourceForm = ({ event }) => {
<div className="p-inputgroup flex-1 flex-col mt-4"> <div className="p-inputgroup flex-1 flex-col mt-4">
<span>Video Embed</span> <span>Video Embed</span>
<div data-color-mode="dark"> <MarkdownEditor value={videoEmbed} onChange={handleVideoEmbedChange} height={250} />
<MDEditor value={videoEmbed} onChange={handleVideoEmbedChange} height={250} />
</div>
<small className="text-gray-400 mt-2"> <small className="text-gray-400 mt-2">
You can customize your video embed using markdown or HTML. For example, paste iframe 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. 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"> <div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span> <span>Content</span>
<div data-color-mode="dark"> <MarkdownEditor value={content} onChange={handleContentChange} height={350} />
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
</div> </div>
<div className="mt-8 flex-col w-full"> <div className="mt-8 flex-col w-full">

View File

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

View File

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

View File

@ -1,21 +1,21 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios'; 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 { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea'; import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber'; import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch'; 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 { Tooltip } from 'primereact/tooltip';
import dynamic from 'next/dynamic'; import 'primereact/resources/primereact.min.css';
import MarkdownEditor from '@/components/markdown/MarkdownEditor';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
const EditPublishedDocumentForm = ({ event }) => { const EditPublishedDocumentForm = ({ event }) => {
const router = useRouter(); const router = useRouter();
@ -198,9 +198,7 @@ const EditPublishedDocumentForm = ({ event }) => {
</div> </div>
<div className="p-inputgroup flex-1 flex-col mt-4"> <div className="p-inputgroup flex-1 flex-col mt-4">
<span>Content</span> <span>Content</span>
<div data-color-mode="dark"> <MarkdownEditor value={content} onChange={handleContentChange} height={350} />
<MDEditor value={content} onChange={handleContentChange} height={350} />
</div>
</div> </div>
<div className="mt-8 flex-col w-full"> <div className="mt-8 flex-col w-full">
<span className="pl-1 flex items-center"> <span className="pl-1 flex items-center">

View File

@ -1,23 +1,22 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios'; 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 { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea'; import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber'; import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch'; 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 { useEncryptContent } from '@/hooks/encryption/useEncryptContent';
import MoreInfo from '@/components/MoreInfo'; import MoreInfo from '@/components/MoreInfo';
import dynamic from 'next/dynamic'; import 'primeicons/primeicons.css';
import { Tooltip } from 'primereact/tooltip';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { import 'primereact/resources/primereact.min.css';
ssr: false, import MarkdownEditor from '@/components/markdown/MarkdownEditor';
});
const EditPublishedVideoForm = ({ event }) => { const EditPublishedVideoForm = ({ event }) => {
const router = useRouter(); const router = useRouter();
@ -190,9 +189,7 @@ const EditPublishedVideoForm = ({ event }) => {
</div> </div>
<div className="p-inputgroup flex-1 flex-col mt-4"> <div className="p-inputgroup flex-1 flex-col mt-4">
<span>Video Embed</span> <span>Video Embed</span>
<div data-color-mode="dark"> <MarkdownEditor value={videoEmbed} onChange={handleVideoEmbedChange} height={250} />
<MDEditor value={videoEmbed} onChange={handleVideoEmbedChange} height={250} />
</div>
<small className="text-gray-400 mt-2"> <small className="text-gray-400 mt-2">
You can customize your video embed using markdown or HTML. For example, paste iframe 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. 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;