Skip to content

acmsnu/SoW-Blogging-App

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Simple Blogging App πŸ“

Welcome to your frontend blogging application! This is a clean, modern, and responsive blog platform built with vanilla HTML, CSS, and JavaScript. Perfect for learning frontend development, content management, and building a personal blog without backend complexity!

What You'll Learn

  • Frontend application architecture and organization
  • Dynamic content rendering with vanilla JavaScript
  • Local storage for data persistence
  • Content management and CRUD operations
  • Rich text editing and markdown support
  • Search and filtering functionality
  • Responsive design for content-heavy applications
  • Client-side routing (Single Page Application concepts)

How It Works πŸ”§

The Simple Blogging App provides a complete blogging experience:

  1. Article Creation: Rich text editor for writing and formatting posts
  2. Content Management: Create, read, update, and delete blog posts
  3. Category System: Organize posts by categories and tags
  4. Search Functionality: Find posts by title, content, or tags
  5. Reading Experience: Clean, readable layout for blog posts
  6. Local Storage: All data persisted in browser's local storage
  7. Responsive Design: Optimized for reading on all devices

Prerequisites

Before you start, make sure you have these tools:

Getting Started

1. Clone or Download

Get your copy of this project:

git clone https://github.com/[your-username]/simple-blogging-app.git
cd simple-blogging-app

Or download the ZIP file and extract it to your desired location.

2. Open in Your Editor

Open the project folder in your preferred code editor:

code .

3. Start Development

You can open the project in several ways:

Option 1: Direct Browser Opening

  • Simply double-click index.html to open in your default browser

Option 2: Live Server (Recommended)

  • If using VS Code with Live Server extension
  • Right-click on index.html and select "Open with Live Server"
  • Your app will open at http://localhost:5500 with auto-reload

Option 3: Python Simple Server

python -m http.server 8000

Then visit http://localhost:8000

Project Structure

blogging/
β”œβ”€β”€ index.html              # Main HTML structure and app shell
β”œβ”€β”€ style.css              # Styling for blog interface and content
β”œβ”€β”€ script.js              # Blog functionality and data management
└── Readme.md             # This documentation

File Overview

  • index.html: Single-page app structure with blog interface
  • style.css: Modern CSS for blog layout, typography, and responsive design
  • script.js: Complete blog functionality including post management and UI interactions

Your Mission 🎯

Create a fully functional blogging platform that runs entirely in the browser:

Core Features (Start Here!)

  • Create new blog posts with title, content, and metadata
  • Display all posts in a clean, organized list
  • Read individual posts in a distraction-free layout
  • Edit existing posts with inline editing
  • Delete posts with confirmation dialogs
  • Basic search functionality by title and content

Intermediate Features

  • Category and tag system for organizing posts
  • Advanced search with filters (category, date, tags)
  • Rich text editor with formatting options
  • Draft system for saving work-in-progress
  • Post statistics (word count, reading time)
  • Export/import functionality for backup
  • Dark/light theme toggle

Advanced Features (Challenge!)

  • Markdown support with live preview
  • Image upload and management (using FileReader API)
  • Comment system with local storage
  • Social sharing buttons
  • RSS feed generation
  • Full-text search with highlighting
  • Progressive Web App (PWA) features
  • Offline functionality with service workers

Application Architecture πŸ—οΈ

Data Structure

// Blog post structure
const blogPost = {
  id: "unique-post-id",
  title: "Post Title",
  content: "Post content in HTML or markdown",
  summary: "Brief description of the post",
  author: "Author name",
  categories: ["Web Development", "JavaScript"],
  tags: ["tutorial", "frontend", "beginner"],
  createdDate: "2025-06-02T10:30:00Z",
  modifiedDate: "2025-06-02T11:45:00Z",
  status: "published", // draft, published, archived
  readingTime: 5, // estimated reading time in minutes
  featured: false,
};

Local Storage Management

class BlogStorage {
  constructor() {
    this.storageKey = "simpleBlogPosts";
    this.posts = this.loadPosts();
  }

  loadPosts() {
    const stored = localStorage.getItem(this.storageKey);
    return stored ? JSON.parse(stored) : [];
  }

  savePosts() {
    localStorage.setItem(this.storageKey, JSON.stringify(this.posts));
  }

  createPost(postData) {
    const newPost = {
      id: this.generateId(),
      ...postData,
      createdDate: new Date().toISOString(),
      modifiedDate: new Date().toISOString(),
    };
    this.posts.unshift(newPost);
    this.savePosts();
    return newPost;
  }
}

Search and Filter System

class BlogSearch {
  constructor(posts) {
    this.posts = posts;
  }

  search(query, filters = {}) {
    return this.posts.filter((post) => {
      const matchesQuery = this.matchesSearchQuery(post, query);
      const matchesCategory = this.matchesCategory(post, filters.category);
      const matchesTags = this.matchesTags(post, filters.tags);
      const matchesDateRange = this.matchesDateRange(post, filters.dateRange);

      return matchesQuery && matchesCategory && matchesTags && matchesDateRange;
    });
  }

  matchesSearchQuery(post, query) {
    if (!query) return true;
    const searchText =
      `${post.title} ${post.content} ${post.summary}`.toLowerCase();
    return searchText.includes(query.toLowerCase());
  }
}

Design Guidelines 🎨

Typography for Reading

:root {
  /* Reading-optimized typography */
  --font-reading: "Georgia", "Times New Roman", serif;
  --font-interface: "system-ui", "-apple-system", sans-serif;
  --font-code: "Monaco", "Menlo", monospace;

  /* Reading-friendly sizes */
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 1.875rem;
}

/* Blog post content styling */
.blog-content {
  font-family: var(--font-reading);
  font-size: var(--text-lg);
  line-height: 1.7;
  max-width: 65ch; /* Optimal reading width */
  margin: 0 auto;
}

Color Scheme for Blogs

:root {
  --primary-color: #2563eb;
  --secondary-color: #64748b;
  --accent-color: #059669;
  --background-light: #ffffff;
  --background-gray: #f8fafc;
  --text-primary: #1e293b;
  --text-secondary: #64748b;
  --text-muted: #94a3b8;
  --border-light: #e2e8f0;
  --success-color: #10b981;
  --warning-color: #f59e0b;
  --error-color: #ef4444;
}

Layout for Content

/* Blog layout grid */
.blog-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1rem;
}

@media (min-width: 768px) {
  .blog-layout {
    grid-template-columns: 2fr 1fr;
  }
}

/* Post card styling */
.post-card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  padding: 1.5rem;
  margin-bottom: 1.5rem;
  transition: box-shadow 0.2s ease;
}

.post-card:hover {
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

Content Management Features πŸ“š

Rich Text Editor Implementation

class SimpleEditor {
  constructor(element) {
    this.element = element;
    this.setupToolbar();
    this.bindEvents();
  }

  setupToolbar() {
    const toolbar = document.createElement("div");
    toolbar.className = "editor-toolbar";
    toolbar.innerHTML = `
      <button data-command="bold" title="Bold">B</button>
      <button data-command="italic" title="Italic">I</button>
      <button data-command="underline" title="Underline">U</button>
      <button data-command="createLink" title="Link">πŸ”—</button>
      <button data-command="insertOrderedList" title="Numbered List">1.</button>
      <button data-command="insertUnorderedList" title="Bullet List">β€’</button>
    `;
    this.element.parentNode.insertBefore(toolbar, this.element);
  }

  executeCommand(command, value = null) {
    document.execCommand(command, false, value);
    this.element.focus();
  }
}

Auto-Save Functionality

class AutoSave {
  constructor(blogStorage, interval = 30000) {
    // 30 seconds
    this.storage = blogStorage;
    this.interval = interval;
    this.timeoutId = null;
  }

  scheduleAutoSave(postId, getPostData) {
    clearTimeout(this.timeoutId);
    this.timeoutId = setTimeout(() => {
      const postData = getPostData();
      this.storage.saveDraft(postId, postData);
      this.showAutoSaveIndicator();
    }, this.interval);
  }

  showAutoSaveIndicator() {
    const indicator = document.getElementById("autosave-indicator");
    indicator.textContent = `Draft saved at ${new Date().toLocaleTimeString()}`;
    indicator.style.opacity = "1";
    setTimeout(() => (indicator.style.opacity = "0"), 2000);
  }
}

Performance Optimization ⚑

Efficient DOM Updates

// Virtual scrolling for large post lists
class VirtualScrolling {
  constructor(container, itemHeight, renderItem) {
    this.container = container;
    this.itemHeight = itemHeight;
    this.renderItem = renderItem;
    this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 2;
  }

  render(items, scrollTop) {
    const startIndex = Math.floor(scrollTop / this.itemHeight);
    const endIndex = Math.min(startIndex + this.visibleItems, items.length);

    this.container.innerHTML = "";
    for (let i = startIndex; i < endIndex; i++) {
      const element = this.renderItem(items[i], i);
      element.style.position = "absolute";
      element.style.top = `${i * this.itemHeight}px`;
      this.container.appendChild(element);
    }
  }
}

Image Optimization

// Lazy loading for post images
function setupLazyLoading() {
  const imageObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        img.classList.remove("lazy");
        imageObserver.unobserve(img);
      }
    });
  });

  document.querySelectorAll("img[data-src]").forEach((img) => {
    imageObserver.observe(img);
  });
}

Search Implementation πŸ”

Full-Text Search

class BlogSearch {
  constructor() {
    this.searchIndex = new Map();
    this.buildSearchIndex();
  }

  buildSearchIndex() {
    // Create inverted index for faster searching
    this.posts.forEach((post) => {
      const words = this.extractWords(post.title + " " + post.content);
      words.forEach((word) => {
        if (!this.searchIndex.has(word)) {
          this.searchIndex.set(word, new Set());
        }
        this.searchIndex.get(word).add(post.id);
      });
    });
  }

  extractWords(text) {
    return text
      .toLowerCase()
      .replace(/[^\w\s]/g, "")
      .split(/\s+/)
      .filter((word) => word.length > 2);
  }

  search(query) {
    const queryWords = this.extractWords(query);
    const postIds = queryWords.map(
      (word) => this.searchIndex.get(word) || new Set()
    );

    // Find intersection of all word matches
    return this.intersectSets(postIds);
  }
}

Advanced Filtering

const filterOptions = {
  sortBy: ["date", "title", "readingTime"],
  sortOrder: ["asc", "desc"],
  categories: ["Web Development", "Design", "Tutorials"],
  status: ["published", "draft", "archived"],
  featured: [true, false],
};

function applyFilters(posts, filters) {
  let filtered = [...posts];

  // Apply category filter
  if (filters.category) {
    filtered = filtered.filter((post) =>
      post.categories.includes(filters.category)
    );
  }

  // Apply tag filter
  if (filters.tags && filters.tags.length) {
    filtered = filtered.filter((post) =>
      filters.tags.some((tag) => post.tags.includes(tag))
    );
  }

  // Apply sorting
  filtered.sort((a, b) => {
    const key = filters.sortBy || "createdDate";
    const order = filters.sortOrder === "asc" ? 1 : -1;
    return a[key] > b[key] ? order : -order;
  });

  return filtered;
}

Testing Strategies πŸ§ͺ

Unit Testing for Blog Functions

// Simple testing framework for blog functions
class BlogTester {
  constructor() {
    this.tests = [];
    this.results = { passed: 0, failed: 0 };
  }

  test(description, testFunction) {
    try {
      testFunction();
      this.results.passed++;
      console.log(`βœ… ${description}`);
    } catch (error) {
      this.results.failed++;
      console.error(`❌ ${description}: ${error.message}`);
    }
  }

  assert(condition, message) {
    if (!condition) {
      throw new Error(message || "Assertion failed");
    }
  }
}

// Example tests
const tester = new BlogTester();

tester.test("Should create a new post", () => {
  const storage = new BlogStorage();
  const post = storage.createPost({
    title: "Test Post",
    content: "Test content",
  });
  tester.assert(post.id, "Post should have an ID");
  tester.assert(post.title === "Test Post", "Post should have correct title");
});

Browser Testing

// Feature detection for browser compatibility
function checkBrowserSupport() {
  const features = {
    localStorage: typeof Storage !== "undefined",
    flexbox: CSS.supports("display", "flex"),
    grid: CSS.supports("display", "grid"),
    es6: () => {
      try {
        eval("const x = () => {};");
        return true;
      } catch (e) {
        return false;
      }
    },
  };

  const unsupported = Object.entries(features)
    .filter(([name, supported]) => !supported)
    .map(([name]) => name);

  if (unsupported.length) {
    console.warn("Unsupported features:", unsupported);
  }

  return unsupported.length === 0;
}

Deployment Options πŸš€

GitHub Pages

rem Enable GitHub Pages in repository settings
rem Your blog will be available at: https://username.github.io/repository-name

Netlify

rem Drag and drop your folder to Netlify
rem Or connect your GitHub repository for automatic deployments

Vercel

npm install -g vercel
vercel --prod

Traditional Web Hosting

  • Upload files via FTP to your hosting provider
  • Ensure index.html is in the root directory
  • Configure custom domain if needed

Helpful Commands

rem Validate HTML
npx html-validate index.html

rem Check CSS
npx stylelint style.css

rem Minify CSS for production
npx clean-css-cli -o style.min.css style.css

rem Minify JavaScript for production
npx terser script.js -o script.min.js

rem Check accessibility
npx pa11y http://localhost:5500

rem Generate sitemap (if using build tools)
npx sitemap-generator http://localhost:5500

Tips for Success πŸ’‘

  1. Content First: Focus on creating a great writing and reading experience
  2. Performance: Optimize for fast loading, especially with many posts
  3. Accessibility: Ensure your blog is readable by screen readers
  4. Mobile Experience: Test thoroughly on mobile devices
  5. Data Backup: Implement export functionality to prevent data loss
  6. User Experience: Make navigation intuitive and search powerful

Common Challenges & Solutions

Managing Large Numbers of Posts

// Pagination for performance
class PostPagination {
  constructor(posts, postsPerPage = 10) {
    this.posts = posts;
    this.postsPerPage = postsPerPage;
    this.currentPage = 1;
  }

  getCurrentPosts() {
    const start = (this.currentPage - 1) * this.postsPerPage;
    const end = start + this.postsPerPage;
    return this.posts.slice(start, end);
  }

  getTotalPages() {
    return Math.ceil(this.posts.length / this.postsPerPage);
  }
}

Rich Text Editing

// Sanitize HTML content to prevent XSS
function sanitizeHTML(html) {
  const temp = document.createElement("div");
  temp.innerHTML = html;

  // Remove script tags and event handlers
  const scripts = temp.querySelectorAll("script");
  scripts.forEach((script) => script.remove());

  // Remove dangerous attributes
  const allElements = temp.querySelectorAll("*");
  allElements.forEach((el) => {
    const attrs = [...el.attributes];
    attrs.forEach((attr) => {
      if (attr.name.startsWith("on")) {
        el.removeAttribute(attr.name);
      }
    });
  });

  return temp.innerHTML;
}

Data Import/Export

// Export blog data as JSON
function exportBlogData() {
  const data = {
    posts: blogStorage.getAllPosts(),
    categories: blogStorage.getCategories(),
    tags: blogStorage.getAllTags(),
    exportDate: new Date().toISOString(),
    version: "1.0",
  };

  const blob = new Blob([JSON.stringify(data, null, 2)], {
    type: "application/json",
  });

  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = `blog-export-${new Date().toISOString().split("T")[0]}.json`;
  a.click();
  URL.revokeObjectURL(url);
}

Content Ideas πŸ’­

Sample Blog Posts

  • Tutorial Posts: Step-by-step guides with code examples
  • Opinion Pieces: Thoughts on industry trends and technologies
  • Project Showcases: Detailed breakdowns of your projects
  • Learning Notes: Document your learning journey
  • Reviews: Tools, books, courses, or conferences

Blog Categories

  • Development: Programming tutorials and tips
  • Design: UI/UX insights and inspiration
  • Career: Professional development and advice
  • Tools: Reviews and recommendations
  • Personal: Life updates and reflections

Resources for Learning

Need Help?

  • Check browser Console for JavaScript errors
  • Test localStorage functionality in private/incognito mode
  • Validate your HTML and CSS with online tools
  • Test your blog with different content lengths
  • Use Lighthouse for performance auditing

Bonus Features 🌟

Once you complete the basic blogging app, try these enhancements:

  • Markdown Editor: Implement live markdown preview
  • Theme System: Multiple visual themes for readers
  • Analytics Dashboard: Track post views and popular content
  • Comment System: Reader engagement with local storage
  • Newsletter: Export post lists for email marketing
  • RSS Feed: Generate RSS XML for feed readers
  • Social Integration: Share buttons and Open Graph meta tags
  • Offline Reading: Service worker for offline post access

Sample Content Structure πŸ“‹

// Example blog post data
const samplePosts = [
  {
    id: "getting-started-with-javascript",
    title: "Getting Started with JavaScript in 2025",
    summary: "A comprehensive guide for beginners learning JavaScript",
    content: `
      <h2>Introduction</h2>
      <p>JavaScript is the programming language of the web...</p>
      <h2>Setting Up Your Environment</h2>
      <p>Before we start coding, let's set up our development environment...</p>
    `,
    author: "John Doe",
    categories: ["JavaScript", "Tutorials"],
    tags: ["beginner", "web-development", "programming"],
    createdDate: "2025-06-01T10:00:00Z",
    readingTime: 8,
    featured: true,
    status: "published",
  },
];

Happy blogging! ✍️


"A blog is a digital garden where ideas grow and readers discover new perspectives!"

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages