From efdedf1a34f8e2ad6db8f1da864878663d37bad0 Mon Sep 17 00:00:00 2001 From: v0 Date: Thu, 19 Jun 2025 21:46:26 +0000 Subject: [PATCH] feat: add complete Docker setup for markdown editor Create Docker configuration files and setup commands #VERCEL_SKIP Co-authored-by: Anders Lehmann Pier <3219386+AndersPier@users.noreply.github.com> --- .dockerignore | 51 + .gitignore | 27 + Dockerfile | 69 ++ Makefile | 54 + app/api/health/route.ts | 12 + app/globals.css | 94 ++ app/layout.tsx | 20 + app/page.tsx | 59 + components.json | 21 + components/markdown-editor.tsx | 98 ++ components/markdown-input.tsx | 65 ++ components/markdown-preview.tsx | 129 +++ components/markdown-toolbar.tsx | 102 ++ components/theme-provider.tsx | 11 + components/ui/accordion.tsx | 58 + components/ui/alert-dialog.tsx | 141 +++ components/ui/alert.tsx | 59 + components/ui/aspect-ratio.tsx | 7 + components/ui/avatar.tsx | 50 + components/ui/badge.tsx | 36 + components/ui/breadcrumb.tsx | 115 ++ components/ui/button.tsx | 56 + components/ui/calendar.tsx | 66 ++ components/ui/card.tsx | 79 ++ components/ui/carousel.tsx | 262 +++++ components/ui/chart.tsx | 365 ++++++ components/ui/checkbox.tsx | 30 + components/ui/collapsible.tsx | 11 + components/ui/command.tsx | 153 +++ components/ui/context-menu.tsx | 200 ++++ components/ui/dialog.tsx | 122 ++ components/ui/drawer.tsx | 118 ++ components/ui/dropdown-menu.tsx | 200 ++++ components/ui/form.tsx | 178 +++ components/ui/hover-card.tsx | 29 + components/ui/input-otp.tsx | 71 ++ components/ui/input.tsx | 22 + components/ui/label.tsx | 26 + components/ui/menubar.tsx | 236 ++++ components/ui/navigation-menu.tsx | 128 +++ components/ui/pagination.tsx | 117 ++ components/ui/popover.tsx | 31 + components/ui/progress.tsx | 28 + components/ui/radio-group.tsx | 44 + components/ui/resizable.tsx | 45 + components/ui/scroll-area.tsx | 48 + components/ui/select.tsx | 160 +++ components/ui/separator.tsx | 31 + components/ui/sheet.tsx | 140 +++ components/ui/sidebar.tsx | 763 +++++++++++++ components/ui/skeleton.tsx | 15 + components/ui/slider.tsx | 28 + components/ui/sonner.tsx | 31 + components/ui/switch.tsx | 29 + components/ui/table.tsx | 117 ++ components/ui/tabs.tsx | 55 + components/ui/textarea.tsx | 22 + components/ui/toast.tsx | 129 +++ components/ui/toaster.tsx | 35 + components/ui/toggle-group.tsx | 61 + components/ui/toggle.tsx | 45 + components/ui/tooltip.tsx | 30 + components/ui/use-mobile.tsx | 19 + components/ui/use-toast.ts | 194 ++++ docker-compose.yml | 48 + hooks/use-mobile.tsx | 19 + hooks/use-toast.ts | 194 ++++ lib/utils.ts | 6 + next.config.mjs | 43 + nginx.conf | 96 ++ package.json | 32 + pnpm-lock.yaml | 1722 +++++++++++++++++++++++++++++ postcss.config.mjs | 8 + public/placeholder-logo.png | Bin 0 -> 568 bytes public/placeholder-logo.svg | 1 + public/placeholder-user.jpg | Bin 0 -> 1635 bytes public/placeholder.jpg | Bin 0 -> 1064 bytes public/placeholder.svg | 1 + styles/globals.css | 94 ++ tailwind.config.ts | 96 ++ tsconfig.json | 27 + 81 files changed, 8234 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 app/api/health/route.ts create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 components.json create mode 100644 components/markdown-editor.tsx create mode 100644 components/markdown-input.tsx create mode 100644 components/markdown-preview.tsx create mode 100644 components/markdown-toolbar.tsx create mode 100644 components/theme-provider.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/alert-dialog.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/aspect-ratio.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/breadcrumb.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/carousel.tsx create mode 100644 components/ui/chart.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/collapsible.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/context-menu.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/drawer.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/hover-card.tsx create mode 100644 components/ui/input-otp.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/menubar.tsx create mode 100644 components/ui/navigation-menu.tsx create mode 100644 components/ui/pagination.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/progress.tsx create mode 100644 components/ui/radio-group.tsx create mode 100644 components/ui/resizable.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/slider.tsx create mode 100644 components/ui/sonner.tsx create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 components/ui/toggle-group.tsx create mode 100644 components/ui/toggle.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 components/ui/use-mobile.tsx create mode 100644 components/ui/use-toast.ts create mode 100644 docker-compose.yml create mode 100644 hooks/use-mobile.tsx create mode 100644 hooks/use-toast.ts create mode 100644 lib/utils.ts create mode 100644 next.config.mjs create mode 100644 nginx.conf create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.mjs create mode 100644 public/placeholder-logo.png create mode 100644 public/placeholder-logo.svg create mode 100644 public/placeholder-user.jpg create mode 100644 public/placeholder.jpg create mode 100644 public/placeholder.svg create mode 100644 styles/globals.css create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..94305bb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Next.js +.next/ +out/ + +# Production +build + +# Misc +.DS_Store +*.tsbuildinfo + +# Debug +*.log + +# Local env files +.env*.local +.env + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo +next-env.d.ts + +# IDE +.vscode +.idea + +# Git +.git +.gitignore +README.md + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Testing +coverage +.nyc_output + +# Linting +.eslintcache diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f650315 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..aab6617 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,69 @@ +# Use the official Node.js 18 Alpine image as base +FROM node:18-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN \ + if [ -f yarn.lock ]; then yarn run build; \ + elif [ -f package-lock.json ]; then npm run build; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 +# set hostname to localhost +ENV HOSTNAME "0.0.0.0" + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD ["node", "server.js"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3ef133c --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +# Makefile for Markdown Editor Docker operations + +.PHONY: help build run stop clean logs shell health + +# Default target +help: + @echo "Available commands:" + @echo " build - Build the Docker image" + @echo " run - Run the application with docker-compose" + @echo " run-prod - Run with nginx reverse proxy" + @echo " stop - Stop all containers" + @echo " clean - Remove containers and images" + @echo " logs - Show application logs" + @echo " shell - Open shell in the app container" + @echo " health - Check application health" + +# Build the Docker image +build: + docker-compose build --no-cache + +# Run the application (development) +run: + docker-compose up -d markdown-editor + @echo "Application is running at http://localhost:3000" + +# Run with production setup (nginx + app) +run-prod: + docker-compose --profile production up -d + @echo "Application is running at http://localhost (port 80)" + +# Stop all containers +stop: + docker-compose down + +# Clean up containers and images +clean: + docker-compose down --rmi all --volumes --remove-orphans + docker system prune -f + +# Show logs +logs: + docker-compose logs -f markdown-editor + +# Open shell in the app container +shell: + docker-compose exec markdown-editor sh + +# Check application health +health: + @echo "Checking application health..." + @curl -f http://localhost:3000/api/health || echo "Health check failed" + +# Quick development cycle +dev: stop build run logs diff --git a/app/api/health/route.ts b/app/api/health/route.ts new file mode 100644 index 0000000..d223eeb --- /dev/null +++ b/app/api/health/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from "next/server" + +export async function GET() { + return NextResponse.json( + { + status: "healthy", + timestamp: new Date().toISOString(), + service: "markdown-editor", + }, + { status: 200 }, + ) +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..ac68442 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,94 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..17b2ce8 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from 'next' +import './globals.css' + +export const metadata: Metadata = { + title: 'v0 App', + description: 'Created with v0', + generator: 'v0.dev', +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + {children} + + ) +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..7e3b74d --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,59 @@ +"use client" +import { MarkdownEditor } from "@/components/markdown-editor" + +const defaultMarkdown = `# 🚀 Welcome to Markdown Editor + +This is a **modern** markdown editor with *real-time* preview and a **colorful** interface! + +## ✨ Features + +- 🎨 Beautiful split-screen editing +- ⚡ Live preview with instant updates +- 🛠️ Rich formatting toolbar +- 🌈 Modern, colorful UI + +### 💻 Code Example + +\`\`\`javascript +function createAwesome() { + console.log("Building something amazing! 🎉"); + return "success"; +} +\`\`\` + +### 📝 Lists & More + +1. **First priority** - Get things done +2. **Second priority** - Make it beautiful +3. **Third priority** - Share with the world + +- 🎯 Bullet points with style +- 🚀 Another awesome point + - 💡 Nested brilliance + - ⭐ Even more nested goodness + +### 🔗 Links and Media + +[🌟 Visit our GitHub](https://github.com) for more amazing projects! + +> 💬 This is a beautiful blockquote with some **bold** text and *italic* styling. + +--- + +### 📊 Data Table + +| Feature | Status | Priority | +|---------|--------|----------| +| Editor | ✅ Complete | High | +| Preview | ✅ Complete | High | +| Export | 🚧 In Progress | Medium | + +Happy writing! ✨🎉` + +export default function Home() { + return ( +
+ +
+ ) +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..d9ef0ae --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/components/markdown-editor.tsx b/components/markdown-editor.tsx new file mode 100644 index 0000000..59db61d --- /dev/null +++ b/components/markdown-editor.tsx @@ -0,0 +1,98 @@ +"use client" + +import { useState, useCallback } from "react" +import { MarkdownToolbar } from "./markdown-toolbar" +import { MarkdownInput } from "./markdown-input" +import { MarkdownPreview } from "./markdown-preview" +import { FileText, Eye, Edit3, Sparkles } from "lucide-react" + +interface MarkdownEditorProps { + initialContent?: string +} + +export function MarkdownEditor({ initialContent = "" }: MarkdownEditorProps) { + const [content, setContent] = useState(initialContent) + const [textareaRef, setTextareaRef] = useState(null) + + const insertText = useCallback( + (before: string, after = "", placeholder = "") => { + if (!textareaRef) return + + const start = textareaRef.selectionStart + const end = textareaRef.selectionEnd + const selectedText = content.substring(start, end) + const textToInsert = selectedText || placeholder + const newText = content.substring(0, start) + before + textToInsert + after + content.substring(end) + + setContent(newText) + + // Set cursor position after insertion + setTimeout(() => { + const newCursorPos = start + before.length + textToInsert.length + textareaRef.setSelectionRange(newCursorPos, newCursorPos) + textareaRef.focus() + }, 0) + }, + [content, textareaRef], + ) + + return ( +
+ {/* Header */} +
+
+
+
+ + +
+
+

+ Markdown Editor +

+

Write • Preview • Create

+
+
+
+
+ + {/* Toolbar */} +
+ +
+ + {/* Editor */} +
+ {/* Input Panel */} +
+
+
+ +
+ Editor +
+
+ Live +
+
+ +
+ + {/* Preview Panel */} +
+
+
+ +
+ Preview +
+
+ Real-time +
+
+ +
+
+
+ ) +} diff --git a/components/markdown-input.tsx b/components/markdown-input.tsx new file mode 100644 index 0000000..3d7f0c2 --- /dev/null +++ b/components/markdown-input.tsx @@ -0,0 +1,65 @@ +"use client" + +import type React from "react" +import { useEffect, useRef } from "react" + +interface MarkdownInputProps { + content: string + onChange: (content: string) => void + onTextareaRef: (ref: HTMLTextAreaElement | null) => void +} + +export function MarkdownInput({ content, onChange, onTextareaRef }: MarkdownInputProps) { + const textareaRef = useRef(null) + + useEffect(() => { + onTextareaRef(textareaRef.current) + }, [onTextareaRef]) + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Tab") { + e.preventDefault() + const start = e.currentTarget.selectionStart + const end = e.currentTarget.selectionEnd + const newContent = content.substring(0, start) + " " + content.substring(end) + onChange(newContent) + + setTimeout(() => { + if (textareaRef.current) { + textareaRef.current.setSelectionRange(start + 2, start + 2) + } + }, 0) + } + } + + return ( +
+