feat(blog): Implement blog module with post management, image upload workflow, and localization
All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 18s
All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 18s
- Added S3 image upload workflow documentation. - Created custom hooks for managing blog posts, post details, and tags. - Developed BlogListPage and BlogPostPage components for displaying posts. - Integrated blog components and hooks into the main application. - Updated localization files to include blog-related strings. - Removed mock blog data and replaced it with dynamic data fetching.
This commit is contained in:
129
src/blog/editor/BlogEditor.tsx
Normal file
129
src/blog/editor/BlogEditor.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Blog Editor Component
|
||||
* Extends the base RichTextEditor with blog-specific functionality
|
||||
*/
|
||||
|
||||
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
|
||||
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
|
||||
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
|
||||
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'
|
||||
import { HeadingNode, QuoteNode } from '@lexical/rich-text'
|
||||
import { CodeNode, CodeHighlightNode } from '@lexical/code'
|
||||
import { ListItemNode, ListNode } from '@lexical/list'
|
||||
import { ListPlugin } from '@lexical/react/LexicalListPlugin'
|
||||
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin'
|
||||
import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode'
|
||||
import { TableCellNode, TableNode, TableRowNode } from '@lexical/table'
|
||||
import { TablePlugin } from '@lexical/react/LexicalTablePlugin'
|
||||
import { LinkNode, AutoLinkNode } from '@lexical/link'
|
||||
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin'
|
||||
import { AutoLinkPlugin } from '@lexical/react/LexicalAutoLinkPlugin'
|
||||
import { HashtagNode } from '@lexical/hashtag'
|
||||
|
||||
import { ImageNode } from '../../editor/nodes/ImageNode'
|
||||
import { MentionNode } from '../../editor/nodes/MentionNode'
|
||||
import ToolbarPlugin from '../../editor/plugins/ToolbarPlugin'
|
||||
import MarkdownPlugin from '../../editor/plugins/MarkdownShortcutPlugin'
|
||||
import DragDropPastePlugin from '../../editor/plugins/DragDropPastePlugin'
|
||||
import HashtagPlugin from '../../editor/plugins/HashtagPlugin'
|
||||
import MentionsPlugin from '../../editor/plugins/MentionsPlugin'
|
||||
import editorTheme from '../../editor/themes/EditorTheme'
|
||||
import { BlogImagePlugin } from './BlogImagePlugin'
|
||||
import '../../editor/styles/editor.css'
|
||||
|
||||
const URL_MATCHER =
|
||||
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/
|
||||
|
||||
const EMAIL_MATCHER =
|
||||
/(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/
|
||||
|
||||
const MATCHERS = [
|
||||
(text: string) => {
|
||||
const match = URL_MATCHER.exec(text)
|
||||
if (match === null) {
|
||||
return null
|
||||
}
|
||||
const fullMatch = match[0]
|
||||
return {
|
||||
index: match.index,
|
||||
length: fullMatch.length,
|
||||
text: fullMatch,
|
||||
url: fullMatch.startsWith('http') ? fullMatch : `https://${fullMatch}`,
|
||||
}
|
||||
},
|
||||
(text: string) => {
|
||||
const match = EMAIL_MATCHER.exec(text)
|
||||
if (match === null) {
|
||||
return null
|
||||
}
|
||||
const fullMatch = match[0]
|
||||
return {
|
||||
index: match.index,
|
||||
length: fullMatch.length,
|
||||
text: fullMatch,
|
||||
url: `mailto:${fullMatch}`,
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
interface BlogEditorProps {
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
export default function BlogEditor({ placeholder }: BlogEditorProps) {
|
||||
const editorConfig = {
|
||||
namespace: 'BlogEditor',
|
||||
theme: editorTheme,
|
||||
onError(error: Error) {
|
||||
console.error(error)
|
||||
},
|
||||
nodes: [
|
||||
HeadingNode,
|
||||
QuoteNode,
|
||||
CodeNode,
|
||||
CodeHighlightNode,
|
||||
ListNode,
|
||||
ListItemNode,
|
||||
HorizontalRuleNode,
|
||||
TableNode,
|
||||
TableRowNode,
|
||||
TableCellNode,
|
||||
LinkNode,
|
||||
AutoLinkNode,
|
||||
ImageNode,
|
||||
HashtagNode,
|
||||
MentionNode,
|
||||
],
|
||||
}
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={editorConfig}>
|
||||
<div className="editor-container">
|
||||
<ToolbarPlugin />
|
||||
<div className="editor-inner">
|
||||
<RichTextPlugin
|
||||
contentEditable={<ContentEditable className="editor-input" />}
|
||||
placeholder={
|
||||
<div className="editor-placeholder">
|
||||
{placeholder || 'Start writing your blog post...'}
|
||||
</div>
|
||||
}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<HistoryPlugin />
|
||||
<ListPlugin />
|
||||
<CheckListPlugin />
|
||||
<LinkPlugin />
|
||||
<AutoLinkPlugin matchers={MATCHERS} />
|
||||
<TablePlugin />
|
||||
<BlogImagePlugin />
|
||||
<DragDropPastePlugin />
|
||||
<HashtagPlugin />
|
||||
<MentionsPlugin />
|
||||
<MarkdownPlugin />
|
||||
</div>
|
||||
</div>
|
||||
</LexicalComposer>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user