diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..25cf49a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# Use Node.js as the base image +FROM node:18 + +# Set the working directory +WORKDIR /app + +# Copy package.json and package-lock.json (if available) +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Create a directory for markdown files +RUN mkdir -p /markdown + +# Expose the port your app runs on +EXPOSE 3000 + +# Command to run the application +CMD ["npm", "start"] \ No newline at end of file diff --git a/manage_container.sh b/manage_container.sh new file mode 100755 index 0000000..5b49741 --- /dev/null +++ b/manage_container.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Configuration +IMAGE_NAME="markdown-blog" +CONTAINER_NAME="markdown-blog-container" +PORT=3000 +MARKDOWN_DIR="/path/to/your/markdown" # Update this to your local markdown directory + +# Function to build the Docker image +build_image() { + echo "Building Docker image..." + docker build -t $IMAGE_NAME . +} + +# Function to start the container +start_container() { + echo "Starting container..." + docker run -d --name $CONTAINER_NAME -p $PORT:3000 -v $MARKDOWN_DIR:/markdown $IMAGE_NAME +} + +# Function to stop the container +stop_container() { + echo "Stopping container..." + docker stop $CONTAINER_NAME +} + +# Function to restart the container +restart_container() { + echo "Restarting container..." + docker restart $CONTAINER_NAME +} + +# Function to view logs +view_logs() { + echo "Viewing logs..." + docker logs $CONTAINER_NAME +} + +# Main script logic +case "$1" in + build) + build_image + ;; + start) + start_container + ;; + stop) + stop_container + ;; + restart) + restart_container + ;; + logs) + view_logs + ;; + *) + echo "Usage: $0 {build|start|stop|restart|logs}" + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/posts/created-using-admingui.md b/posts/created-using-admingui.md new file mode 100644 index 0000000..d0f1c04 --- /dev/null +++ b/posts/created-using-admingui.md @@ -0,0 +1,8 @@ +--- +title: created using adminGUI +date: '2025-06-16' +tags: + - admin +summary: yeaa +--- +**this was created using the admin page** diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx new file mode 100644 index 0000000..811c1b5 --- /dev/null +++ b/src/app/admin/page.tsx @@ -0,0 +1,437 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; + +interface Post { + slug: string; + title: string; + date: string; + tags: string[]; + summary: string; + content: string; +} + +interface Folder { + type: 'folder'; + name: string; + path: string; + children: (Post | Folder)[]; +} + +interface Post { + type: 'post'; + slug: string; + title: string; + date: string; + tags: string[]; + summary: string; + content: string; +} + +type Node = Post | Folder; + +export default function AdminPage() { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [nodes, setNodes] = useState([]); + const [currentPath, setCurrentPath] = useState([]); + const [newPost, setNewPost] = useState({ + title: '', + date: new Date().toISOString().split('T')[0], + tags: '', + summary: '', + content: '', + }); + const [newFolderName, setNewFolderName] = useState(''); + const [isDragging, setIsDragging] = useState(false); + const router = useRouter(); + + useEffect(() => { + // Check if already authenticated + const auth = localStorage.getItem('adminAuth'); + if (auth === 'true') { + setIsAuthenticated(true); + loadContent(); + } + }, []); + + const loadContent = async () => { + try { + const response = await fetch('/api/posts'); + const data = await response.json(); + setNodes(data); + } catch (error) { + console.error('Error loading content:', error); + } + }; + + const handleLogin = (e: React.FormEvent) => { + e.preventDefault(); + if (username === 'admin' && password === 'admin') { + setIsAuthenticated(true); + localStorage.setItem('adminAuth', 'true'); + loadContent(); + } else { + alert('Invalid credentials'); + } + }; + + const handleLogout = () => { + setIsAuthenticated(false); + localStorage.removeItem('adminAuth'); + }; + + const handleCreatePost = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const response = await fetch('/api/admin/posts', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...newPost, + tags: newPost.tags.split(',').map(tag => tag.trim()), + path: currentPath.join('/'), + }), + }); + + if (response.ok) { + setNewPost({ + title: '', + date: new Date().toISOString().split('T')[0], + tags: '', + summary: '', + content: '', + }); + loadContent(); + } else { + alert('Error creating post'); + } + } catch (error) { + console.error('Error creating post:', error); + alert('Error creating post'); + } + }; + + const handleCreateFolder = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newFolderName.trim()) { + alert('Please enter a folder name'); + return; + } + + try { + const response = await fetch('/api/admin/folders', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: newFolderName, + path: currentPath.join('/'), + }), + }); + + if (response.ok) { + setNewFolderName(''); + loadContent(); + } else { + alert('Error creating folder'); + } + } catch (error) { + console.error('Error creating folder:', error); + alert('Error creating folder'); + } + }; + + // Get current directory contents + const getCurrentNodes = (): Node[] => { + let currentNodes: Node[] = nodes; + for (const segment of currentPath) { + const folder = currentNodes.find( + (n) => n.type === 'folder' && n.name === segment + ) as Folder | undefined; + if (folder) { + currentNodes = folder.children; + } else { + break; + } + } + return currentNodes; + }; + + const currentNodes = getCurrentNodes(); + + // Breadcrumbs + const breadcrumbs = [ + { name: 'Root', path: [] }, + ...currentPath.map((name, idx) => ({ + name, + path: currentPath.slice(0, idx + 1), + })), + ]; + + const handleDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(true); + }, []); + + const handleDragLeave = useCallback((e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + }, []); + + const handleDrop = useCallback(async (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + + const files = Array.from(e.dataTransfer.files); + const markdownFiles = files.filter(file => file.name.endsWith('.md')); + + if (markdownFiles.length === 0) { + alert('Please drop only Markdown files'); + return; + } + + for (const file of markdownFiles) { + try { + const content = await file.text(); + const formData = new FormData(); + formData.append('file', file); + formData.append('path', currentPath.join('/')); + + const response = await fetch('/api/admin/upload', { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + throw new Error(`Failed to upload ${file.name}`); + } + } catch (error) { + console.error(`Error uploading ${file.name}:`, error); + alert(`Error uploading ${file.name}`); + } + } + + loadContent(); + }, [currentPath]); + + if (!isAuthenticated) { + return ( +
+
+

Admin Login

+
+
+ + setUsername(e.target.value)} + className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" + required + /> +
+
+ + setPassword(e.target.value)} + className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" + required + /> +
+ +
+
+
+ ); + } + + return ( +
+
+
+

Admin Dashboard

+ +
+ + {/* Breadcrumb Navigation */} + + + {/* Create Folder Form */} +
+

Create New Folder

+
+ setNewFolderName(e.target.value)} + placeholder="Folder name" + className="flex-1 rounded-md border border-gray-300 px-3 py-2" + required + /> + +
+
+ + {/* Drag and Drop Zone */} +
+
+

Drag and drop Markdown files here

+

Files will be uploaded to: {currentPath.join('/') || 'root'}

+
+
+ + {/* Create Post Form */} +
+

Create New Post

+
+
+ + setNewPost({ ...newPost, title: e.target.value })} + className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" + required + /> +
+
+ + setNewPost({ ...newPost, date: e.target.value })} + className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" + required + /> +
+
+ + setNewPost({ ...newPost, tags: e.target.value })} + className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" + placeholder="tag1, tag2, tag3" + /> +
+
+ +