From 71967f891e14657515be7446689d912d10d86f78 Mon Sep 17 00:00:00 2001 From: chandresh Date: Tue, 10 Feb 2026 01:46:31 +0530 Subject: [PATCH] Decent functionality --- docs/blog-layout-spec.md | 30 ++ frontend/package.json | 1 + frontend/src/components/Editor.jsx | 10 +- frontend/src/components/MediaLibraryModal.jsx | 2 +- frontend/src/components/MobilePreview.jsx | 14 +- .../src/components/TipTapContentRenderer.jsx | 65 +++- frontend/src/components/Toolbar.jsx | 331 ++++++++++++++++-- frontend/src/extensions/YouTube.js | 127 +++++++ frontend/src/index.css | 96 ++++- frontend/src/pages/BlogPost.jsx | 21 +- frontend/src/pages/Dashboard.jsx | 48 +-- frontend/src/pages/Editor.jsx | 65 ++-- frontend/src/pages/Login.jsx | 6 +- 13 files changed, 683 insertions(+), 133 deletions(-) create mode 100644 docs/blog-layout-spec.md create mode 100644 frontend/src/extensions/YouTube.js diff --git a/docs/blog-layout-spec.md b/docs/blog-layout-spec.md new file mode 100644 index 0000000..4178874 --- /dev/null +++ b/docs/blog-layout-spec.md @@ -0,0 +1,30 @@ +# Blog post layout spec + +This document defines the shared layout and spacing for the blog post detail screen so that the **blog-editor mobile preview** and the **Android app** (BlogDetailScreen) match exactly. + +## Values (single source of truth) + +| Element | Web (Tailwind) | Android (Compose) | Notes | +|--------|----------------|-------------------|--------| +| Screen horizontal padding | `px-4` (16px) | `16.dp` | Gutter for title, date, divider, body | +| Header block vertical padding | `py-6` (24px) | `24.dp` | Top and bottom of header block | +| Gap between title and date | `mb-3` (12px) | `12.dp` | | +| Divider | Full width, `px-4` inset | `padding(horizontal = 16.dp)` | | +| Gap between divider and content | `pt-6` (24px) | `24.dp` | | +| Content block vertical padding | 0 top, 0 bottom | None | Only horizontal padding on content | +| Paragraph / block spacing | `mb-3` (12px) | `12.dp` | Between paragraphs and after blocks | +| Image block vertical margin | `my-2` (8px) | `8.dp` | Top and bottom of image | +| Image caption gap | `mt-1` (4px) | `4.dp` | Above caption text | +| Bottom padding (scroll) | `h-8` (32px) | `32.dp` | | +| TopAppBar | `px-4 py-3` | Material TopAppBar | Back + "Blog Post" | + +## Image caption + +- Only show caption when `title` is present and **not** the literal string `"null"` (case-insensitive). Both Web and Android must hide caption when title is null, blank, or `"null"` to avoid showing "null" on screen. + +## Where this is used + +- **blog-editor frontend**: [MobilePreview.jsx](../frontend/src/components/MobilePreview.jsx), [TipTapContentRenderer.jsx](../frontend/src/components/TipTapContentRenderer.jsx) +- **Android app**: [BlogDetailScreen.kt](../../android-app/app/src/main/java/com/livingai/android/ui/screens/BlogDetailScreen.kt), [TipTapContentRenderer.kt](../../android-app/app/src/main/java/com/livingai/android/ui/components/TipTapContentRenderer.kt) + +When changing layout or spacing, update both codebases and this spec. diff --git a/frontend/package.json b/frontend/package.json index f1ae042..4b3af30 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@tiptap/extension-bubble-menu": "^3.19.0", "@tiptap/extension-color": "^2.1.13", "@tiptap/extension-image": "^2.1.13", + "@tiptap/extension-link": "^2.1.13", "@tiptap/extension-text-style": "^2.1.13", "@tiptap/extension-underline": "^2.1.13", "@tiptap/react": "^2.1.13", diff --git a/frontend/src/components/Editor.jsx b/frontend/src/components/Editor.jsx index 1600241..d618692 100644 --- a/frontend/src/components/Editor.jsx +++ b/frontend/src/components/Editor.jsx @@ -4,8 +4,10 @@ import StarterKit from '@tiptap/starter-kit' import TextStyle from '@tiptap/extension-text-style' import Color from '@tiptap/extension-color' import Underline from '@tiptap/extension-underline' +import Link from '@tiptap/extension-link' import { FontSize } from '../extensions/FontSize' import { ImageResize } from '../extensions/ImageResize' +import { YouTube } from '../extensions/YouTube' import Toolbar from './Toolbar' import ImageBubbleMenu from './ImageBubbleMenu' import MediaLibraryModal from './MediaLibraryModal' @@ -131,9 +133,11 @@ export default function Editor({ content, onChange, onImageUpload, postId, sessi extensions: [ StarterKit, ImageResize, + YouTube, TextStyle, Color, Underline, + Link.configure({ openOnClick: false }), FontSize, ], content: content || '', @@ -195,7 +199,7 @@ export default function Editor({ content, onChange, onImageUpload, postId, sessi } return ( -
+
- +
+ +
{ diff --git a/frontend/src/components/MediaLibraryModal.jsx b/frontend/src/components/MediaLibraryModal.jsx index 15ec330..33d22df 100644 --- a/frontend/src/components/MediaLibraryModal.jsx +++ b/frontend/src/components/MediaLibraryModal.jsx @@ -85,7 +85,7 @@ export default function MediaLibraryModal({ onClick={onClose} >
e.stopPropagation()} role="document" > diff --git a/frontend/src/components/MobilePreview.jsx b/frontend/src/components/MobilePreview.jsx index 9422162..413309d 100644 --- a/frontend/src/components/MobilePreview.jsx +++ b/frontend/src/components/MobilePreview.jsx @@ -48,14 +48,14 @@ export default function MobilePreview({ title, content, createdAt }) {

Blog Post

- {/* Scrollable Content Area */} + {/* Scrollable Content Area - layout matches Android BlogDetailScreen (see blog layout spec) */}
- {/* Header Section with Title and Date */} -
+ {/* Header: 16px horizontal, 24px vertical; 12px gap between title and date */} +

{title || 'Untitled Post'}

-

+

{formattedDate}

@@ -65,8 +65,8 @@ export default function MobilePreview({ title, content, createdAt }) {
- {/* Content Section */} -
+ {/* Content: 16px horizontal only, 24px gap after divider */} +
{content ? ( ) : ( @@ -76,7 +76,7 @@ export default function MobilePreview({ title, content, createdAt }) { )}
- {/* Bottom padding for better scrolling */} + {/* Bottom padding for scroll (32px) */}
diff --git a/frontend/src/components/TipTapContentRenderer.jsx b/frontend/src/components/TipTapContentRenderer.jsx index 6c950a2..151edce 100644 --- a/frontend/src/components/TipTapContentRenderer.jsx +++ b/frontend/src/components/TipTapContentRenderer.jsx @@ -1,4 +1,5 @@ import React from 'react' +import { getYouTubeEmbedUrl } from '../extensions/YouTube' /** * Renders TipTap JSON content as HTML matching Android styling @@ -61,6 +62,11 @@ function RenderNode({ node }) { ) + case 'youtube': + return ( + + ) + case 'blockquote': return ( @@ -124,12 +130,12 @@ function HeadingNode({ level, content, marks }) { if (!textContent.trim()) return null const headingClasses = { - 1: 'text-3xl font-bold mb-4 mt-6', - 2: 'text-2xl font-bold mb-4 mt-6', - 3: 'text-xl font-bold mb-3 mt-5', - 4: 'text-lg font-bold mb-3 mt-4', - 5: 'text-base font-bold mb-2 mt-3', - 6: 'text-sm font-bold mb-2 mt-3' + 1: 'text-3xl font-bold mb-3 mt-3', + 2: 'text-2xl font-bold mb-3 mt-3', + 3: 'text-xl font-bold mb-3 mt-3', + 4: 'text-lg font-bold mb-3 mt-3', + 5: 'text-base font-bold mb-3 mt-3', + 6: 'text-sm font-bold mb-3 mt-3' } const Tag = `h${level}` @@ -183,26 +189,44 @@ function ListItemNode({ content, marks }) { ) } +function YoutubeNode({ attrs }) { + const videoId = attrs?.videoId + if (!videoId) return null + const embedUrl = getYouTubeEmbedUrl(videoId) + return ( +
+