codewithgarry
Version:
Girish Sharma's NPX business card - DevOps Engineer & Cloud Architect - Connect with me directly via terminal!
1,646 lines (1,381 loc) • 43.3 kB
JavaScript
// Advanced Blogging System with Markdown Support
// This file contains the complete blogging platform functionality
class BlogManager {
constructor() {
this.posts = [];
this.categories = ['DevOps', 'Kubernetes', 'Cloud', 'Terraform', 'Tutorials', 'Best Practices'];
this.currentPost = null;
this.searchQuery = '';
this.selectedCategory = 'All';
this.sortBy = 'date';
}
// Initialize blog system
async initialize() {
await this.loadMarkdownParser();
this.loadSamplePosts();
this.renderBlogInterface();
}
// Load markdown parser
async loadMarkdownParser() {
return new Promise((resolve) => {
if (window.marked) {
resolve();
return;
}
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.2/marked.min.js';
script.onload = () => {
// Configure marked options
marked.setOptions({
highlight: function(code, lang) {
if (window.Prism && lang && Prism.languages[lang]) {
return Prism.highlight(code, Prism.languages[lang], lang);
}
return code;
},
breaks: true,
gfm: true
});
resolve();
};
document.head.appendChild(script);
});
}
// Load sample blog posts
loadSamplePosts() {
this.posts = [
{
id: 'kubernetes-production-ready',
title: 'Making Kubernetes Production Ready: A Complete Guide',
excerpt: 'Learn essential practices for running Kubernetes clusters in production environments with confidence.',
content: this.getSampleMarkdownContent('kubernetes-production'),
author: 'Girish Sharma',
date: '2024-03-15',
category: 'Kubernetes',
tags: ['kubernetes', 'production', 'devops', 'containers'],
readTime: '12 min read',
featured: true,
image: 'https://via.placeholder.com/800x400/1a1a2e/00ff00?text=Kubernetes+Production'
},
{
id: 'terraform-best-practices',
title: 'Terraform Best Practices for Enterprise Infrastructure',
excerpt: 'Discover proven patterns and practices for managing large-scale infrastructure with Terraform.',
content: this.getSampleMarkdownContent('terraform-practices'),
author: 'Girish Sharma',
date: '2024-03-10',
category: 'Terraform',
tags: ['terraform', 'iac', 'devops', 'automation'],
readTime: '15 min read',
featured: true,
image: 'https://via.placeholder.com/800x400/16213e/00bfff?text=Terraform+Best+Practices'
},
{
id: 'monitoring-with-promql',
title: 'Advanced Monitoring with PromQL: Beyond the Basics',
excerpt: 'Master PromQL queries to build powerful monitoring and alerting systems.',
content: this.getSampleMarkdownContent('promql-monitoring'),
author: 'Girish Sharma',
date: '2024-03-05',
category: 'DevOps',
tags: ['promql', 'monitoring', 'prometheus', 'observability'],
readTime: '10 min read',
featured: false,
image: 'https://via.placeholder.com/800x400/0c0c0c/ff00ff?text=PromQL+Monitoring'
},
{
id: 'gcp-devops-pipeline',
title: 'Building CI/CD Pipelines on Google Cloud Platform',
excerpt: 'Step-by-step guide to creating robust deployment pipelines using GCP services.',
content: this.getSampleMarkdownContent('gcp-pipeline'),
author: 'Girish Sharma',
date: '2024-02-28',
category: 'Cloud',
tags: ['gcp', 'cicd', 'cloud-build', 'devops'],
readTime: '18 min read',
featured: false,
image: 'https://via.placeholder.com/800x400/1a1a2e/ffff00?text=GCP+CI%2FCD'
}
];
}
// Get sample markdown content
getSampleMarkdownContent(type) {
const content = {
'kubernetes-production': `
# Making Kubernetes Production Ready: A Complete Guide
Running Kubernetes in production requires careful planning and implementation of best practices. This comprehensive guide covers everything you need to know.
## Table of Contents
1. [Cluster Setup and Configuration](#cluster-setup)
2. [Security Hardening](#security)
3. [Resource Management](#resources)
4. [Monitoring and Observability](#monitoring)
5. [Backup and Disaster Recovery](#backup)
## Cluster Setup and Configuration {#cluster-setup}
### High Availability Control Plane
For production workloads, always deploy a multi-master setup:
\`\`\`yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
controlPlaneEndpoint: "k8s-api.example.com:6443"
etcd:
external:
endpoints:
- https://etcd1.example.com:2379
- https://etcd2.example.com:2379
- https://etcd3.example.com:2379
\`\`\`
### Node Configuration
Ensure proper node configuration for production workloads:
\`\`\`bash
# Configure kubelet
cat > /etc/kubernetes/kubelet-config.yaml << EOF
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
maxPods: 110
serverTLSBootstrap: true
rotateCertificates: true
EOF
\`\`\`
## Security Hardening {#security}
### Network Policies
Implement network segmentation using Kubernetes Network Policies:
\`\`\`yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
egress:
- to: []
ports:
- protocol: TCP
port: 53
- protocol: UDP
port: 53
\`\`\`
### Pod Security Standards
Configure Pod Security Standards for enhanced security:
\`\`\`yaml
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
\`\`\`
## Resource Management {#resources}
### Resource Quotas
Implement resource quotas to prevent resource exhaustion:
\`\`\`yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
hard:
requests.cpu: "100"
requests.memory: 200Gi
limits.cpu: "200"
limits.memory: 400Gi
persistentvolumeclaims: "10"
\`\`\`
### Horizontal Pod Autoscaler
Configure HPA for automatic scaling:
\`\`\`yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app
minReplicas: 3
maxReplicas: 100
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
\`\`\`
## Monitoring and Observability {#monitoring}
### Prometheus Configuration
Deploy Prometheus for comprehensive monitoring:
\`\`\`yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "kubernetes-*.rules"
scrape_configs:
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
relabel_configs:
- source_labels: [__address__]
regex: '(.*):10250'
target_label: __address__
replacement: '\${1}:9100'
\`\`\`
### Alerting Rules
Set up critical alerting rules:
\`\`\`yaml
groups:
- name: kubernetes.rules
rules:
- alert: KubernetesPodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} is crash looping"
description: "Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} is restarting frequently"
\`\`\`
## Best Practices Checklist
- ✅ Multi-master setup for HA
- ✅ Network policies implemented
- ✅ Resource quotas configured
- ✅ Pod security standards enforced
- ✅ Monitoring and alerting in place
- ✅ Backup strategy implemented
- ✅ Cluster autoscaling configured
- ✅ Image scanning enabled
- ✅ Secrets management with external tools
- ✅ Regular security audits
## Conclusion
Making Kubernetes production-ready requires attention to many details. Start with these fundamentals and gradually implement more advanced features as your needs grow.
> **Pro Tip**: Always test your disaster recovery procedures in a staging environment before relying on them in production.
*Have questions about Kubernetes in production? Feel free to reach out or check out my [Kubernetes course](/courses/kubernetes-fundamentals) for hands-on practice.*
`,
'terraform-practices': `
# Terraform Best Practices for Enterprise Infrastructure
Managing infrastructure at scale requires disciplined practices and proper tooling. Here's your guide to Terraform excellence.
## Directory Structure
Organize your Terraform code for maintainability:
\`\`\`
terraform/
├── environments/
│ ├── dev/
│ ├── staging/
│ └── production/
├── modules/
│ ├── vpc/
│ ├── compute/
│ └── storage/
└── shared/
├── variables.tf
└── outputs.tf
\`\`\`
## Module Development
### Creating Reusable Modules
\`\`\`hcl
# modules/vpc/main.tf
resource "google_compute_network" "vpc" {
name = var.network_name
auto_create_subnetworks = false
routing_mode = "REGIONAL"
}
resource "google_compute_subnetwork" "subnet" {
count = length(var.subnets)
name = var.subnets[count.index].name
ip_cidr_range = var.subnets[count.index].cidr
region = var.region
network = google_compute_network.vpc.id
}
\`\`\`
### Variable Validation
\`\`\`hcl
variable "environment" {
description = "Environment name"
type = string
validation {
condition = can(regex("^(dev|staging|production)$", var.environment))
error_message = "Environment must be dev, staging, or production."
}
}
\`\`\`
## State Management
### Remote State Configuration
\`\`\`hcl
terraform {
backend "gcs" {
bucket = "terraform-state-bucket"
prefix = "terraform/state"
}
}
\`\`\`
### State Locking
Always use state locking to prevent conflicts:
\`\`\`hcl
terraform {
backend "gcs" {
bucket = "terraform-state-bucket"
prefix = "terraform/state"
}
}
\`\`\`
## Testing Strategy
### Terratest Example
\`\`\`go
func TestVPCModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"network_name": "test-vpc",
"region": "us-central1",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
networkName := terraform.Output(t, terraformOptions, "network_name")
assert.Equal(t, "test-vpc", networkName)
}
\`\`\`
## CI/CD Pipeline
### GitLab CI Example
\`\`\`yaml
stages:
- validate
- plan
- apply
variables:
TF_ROOT: terraform/environments/production
terraform-validate:
stage: validate
script:
- cd $TF_ROOT
- terraform init -backend=false
- terraform validate
- terraform fmt -check
terraform-plan:
stage: plan
script:
- cd $TF_ROOT
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- $TF_ROOT/tfplan
terraform-apply:
stage: apply
script:
- cd $TF_ROOT
- terraform apply tfplan
when: manual
only:
- main
\`\`\`
## Conclusion
Following these practices will help you build maintainable, scalable infrastructure with Terraform.
`,
'promql-monitoring': `
# Advanced Monitoring with PromQL: Beyond the Basics
PromQL is the query language for Prometheus, enabling powerful monitoring and alerting capabilities.
## Query Fundamentals
### Instant Vectors
\`\`\`promql
# CPU usage by instance
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# Memory usage percentage
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
\`\`\`
### Range Vectors
\`\`\`promql
# Request rate over 5 minutes
rate(http_requests_total[5m])
# 99th percentile response time
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
\`\`\`
## Advanced Techniques
### Aggregation Operators
\`\`\`promql
# Top 5 instances by CPU usage
topk(5, node_cpu_usage_percent)
# Average response time by service
avg by (service) (http_request_duration_seconds)
\`\`\`
### Binary Operators
\`\`\`promql
# Compare current vs previous hour
rate(http_requests_total[5m]) / rate(http_requests_total[5m] offset 1h)
# Calculate error rate
rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])
\`\`\`
## Kubernetes Specific Queries
### Pod Resource Usage
\`\`\`promql
# Pod CPU usage
rate(container_cpu_usage_seconds_total{pod!=""}[5m])
# Pod memory usage
container_memory_working_set_bytes{pod!=""}
# Pod network I/O
rate(container_network_receive_bytes_total{pod!=""}[5m])
\`\`\`
### Cluster Health
\`\`\`promql
# Node availability
up{job="kubernetes-nodes"}
# Pod restart rate
increase(kube_pod_container_status_restarts_total[1h])
\`\`\`
## Best Practices
1. **Use appropriate time ranges**: Match your scrape interval
2. **Aggregate before alerting**: Reduce noise with proper grouping
3. **Avoid high cardinality**: Be careful with label combinations
4. **Use recording rules**: Pre-compute expensive queries
## Recording Rules Example
\`\`\`yaml
groups:
- name: kubernetes.rules
rules:
- record: kubernetes:pod_cpu_usage:rate5m
expr: rate(container_cpu_usage_seconds_total{pod!=""}[5m])
- record: kubernetes:pod_memory_usage:bytes
expr: container_memory_working_set_bytes{pod!=""}
\`\`\`
`,
'gcp-pipeline': `
# Building CI/CD Pipelines on Google Cloud Platform
Learn to create robust deployment pipelines using GCP's native services.
## Cloud Build Configuration
### Basic Pipeline
\`\`\`yaml
# cloudbuild.yaml
steps:
# Build the container image
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA', '.']
# Push to Container Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA']
# Deploy to GKE
- name: 'gcr.io/cloud-builders/gke-deploy'
args:
- run
- --filename=k8s/
- --image=gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA
- --location=us-central1-a
- --cluster=production-cluster
options:
logging: CLOUD_LOGGING_ONLY
\`\`\`
### Multi-Environment Pipeline
\`\`\`yaml
steps:
# Test stage
- name: 'gcr.io/cloud-builders/docker'
args: ['run', '--rm', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA', 'npm', 'test']
# Security scanning
- name: 'gcr.io/cloud-builders/gcloud'
args: ['beta', 'container', 'images', 'scan', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA']
# Deploy to staging
- name: 'gcr.io/cloud-builders/gke-deploy'
args: ['run', '--filename=k8s/staging/', '--image=gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA']
\`\`\`
## Terraform Integration
### Infrastructure Pipeline
\`\`\`yaml
steps:
# Terraform init
- name: 'hashicorp/terraform:1.0'
entrypoint: 'sh'
args:
- '-c'
- |
cd terraform/
terraform init
# Terraform plan
- name: 'hashicorp/terraform:1.0'
entrypoint: 'sh'
args:
- '-c'
- |
cd terraform/
terraform plan -out=tfplan
# Terraform apply
- name: 'hashicorp/terraform:1.0'
entrypoint: 'sh'
args:
- '-c'
- |
cd terraform/
terraform apply tfplan
\`\`\`
## Advanced Features
### Parallel Builds
\`\`\`yaml
steps:
# Frontend build
- name: 'node:16'
id: 'frontend-build'
entrypoint: 'npm'
args: ['run', 'build:frontend']
# Backend build
- name: 'gcr.io/cloud-builders/docker'
id: 'backend-build'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/backend:$COMMIT_SHA', './backend']
# Integration tests (wait for both builds)
- name: 'gcr.io/cloud-builders/docker'
args: ['run', '--rm', 'gcr.io/$PROJECT_ID/test-runner', 'npm', 'run', 'test:integration']
waitFor: ['frontend-build', 'backend-build']
\`\`\`
## Monitoring and Notifications
### Slack Integration
\`\`\`yaml
steps:
# ... build steps ...
# Notify on success
- name: 'gcr.io/cloud-builders/curl'
args:
- '-X'
- 'POST'
- '-H'
- 'Content-type: application/json'
- '--data'
- '{"text":"✅ Build successful for commit $COMMIT_SHA"}'
- '$_SLACK_WEBHOOK_URL'
\`\`\`
## Best Practices
1. **Use semantic versioning**: Tag releases properly
2. **Implement proper testing**: Unit, integration, and security tests
3. **Use secrets management**: Store sensitive data in Secret Manager
4. **Monitor pipeline performance**: Track build times and success rates
5. **Implement approval workflows**: Use manual triggers for production
## Conclusion
GCP provides powerful tools for building enterprise-grade CI/CD pipelines. Start simple and add complexity as needed.
`
};
return content[type] || '# Sample Content\n\nThis is sample markdown content.';
}
// Render blog interface
renderBlogInterface() {
const container = document.getElementById('blogContent');
if (!container) return;
container.innerHTML = `
<div class="blog-header">
<div class="blog-controls">
<div class="search-container">
<input type="text" class="search-input" placeholder="Search articles..."
value="${this.searchQuery}" onchange="blogManager.handleSearch(this.value)">
<i class="fas fa-search search-icon"></i>
</div>
<div class="filter-container">
<select class="category-filter" onchange="blogManager.handleCategoryFilter(this.value)">
<option value="All">All Categories</option>
${this.categories.map(cat => `
<option value="${cat}" ${this.selectedCategory === cat ? 'selected' : ''}>${cat}</option>
`).join('')}
</select>
</div>
<div class="sort-container">
<select class="sort-select" onchange="blogManager.handleSort(this.value)">
<option value="date" ${this.sortBy === 'date' ? 'selected' : ''}>Latest First</option>
<option value="title" ${this.sortBy === 'title' ? 'selected' : ''}>Title A-Z</option>
<option value="readTime" ${this.sortBy === 'readTime' ? 'selected' : ''}>Read Time</option>
</select>
</div>
</div>
</div>
<div class="featured-posts">
<h2>Featured Articles</h2>
<div class="featured-grid">
${this.renderFeaturedPosts()}
</div>
</div>
<div class="all-posts">
<h2>All Articles</h2>
<div class="posts-grid">
${this.renderAllPosts()}
</div>
</div>
`;
}
// Render featured posts
renderFeaturedPosts() {
const featuredPosts = this.getFilteredPosts().filter(post => post.featured);
return featuredPosts.map(post => this.renderPostCard(post, 'featured')).join('');
}
// Render all posts
renderAllPosts() {
const allPosts = this.getFilteredPosts();
return allPosts.map(post => this.renderPostCard(post, 'regular')).join('');
}
// Render individual post card
renderPostCard(post, type = 'regular') {
const cardClass = type === 'featured' ? 'post-card featured-post' : 'post-card';
return `
<article class="${cardClass}" onclick="blogManager.openPost('${post.id}')">
<div class="post-image">
<img src="${post.image}" alt="${post.title}" loading="lazy">
<div class="post-category">${post.category}</div>
</div>
<div class="post-content">
<h3 class="post-title">${post.title}</h3>
<p class="post-excerpt">${post.excerpt}</p>
<div class="post-meta">
<div class="post-author">
<i class="fas fa-user"></i>
<span>${post.author}</span>
</div>
<div class="post-date">
<i class="fas fa-calendar"></i>
<span>${this.formatDate(post.date)}</span>
</div>
<div class="post-read-time">
<i class="fas fa-clock"></i>
<span>${post.readTime}</span>
</div>
</div>
<div class="post-tags">
${post.tags.map(tag => `<span class="tag">#${tag}</span>`).join('')}
</div>
</div>
</article>
`;
}
// Open full post view
openPost(postId) {
const post = this.posts.find(p => p.id === postId);
if (!post) return;
this.currentPost = post;
this.renderFullPost();
}
// Render full post view
renderFullPost() {
const container = document.getElementById('blogContent');
if (!container || !this.currentPost) return;
container.innerHTML = `
<div class="full-post">
<div class="post-header">
<button class="back-btn" onclick="blogManager.closeFull
<i class="fas fa-arrow-left"></i>
Back to Blog
</button>
<div class="post-meta-header">
<span class="post-category">${this.currentPost.category}</span>
<span class="post-date">${this.formatDate(this.currentPost.date)}</span>
</div>
</div>
<div class="post-hero">
<img src="${this.currentPost.image}" alt="${this.currentPost.title}" class="hero-image">
<div class="hero-content">
<h1 class="post-title">${this.currentPost.title}</h1>
<div class="post-meta">
<span class="author">By ${this.currentPost.author}</span>
<span class="read-time">${this.currentPost.readTime}</span>
</div>
<div class="post-tags">
${this.currentPost.tags.map(tag => `<span class="tag">#${tag}</span>`).join('')}
</div>
</div>
</div>
<div class="post-content">
<div class="content-sidebar">
<div class="table-of-contents">
<h4>Table of Contents</h4>
<div id="tocContainer">
${this.generateTableOfContents(this.currentPost.content)}
</div>
</div>
<div class="post-actions">
<button class="action-btn" onclick="blogManager.sharePost()">
<i class="fas fa-share"></i> Share
</button>
<button class="action-btn" onclick="blogManager.bookmarkPost()">
<i class="fas fa-bookmark"></i> Bookmark
</button>
<button class="action-btn" onclick="blogManager.printPost()">
<i class="fas fa-print"></i> Print
</button>
</div>
</div>
<div class="content-main">
<div class="markdown-content" id="markdownContent">
${marked.parse(this.currentPost.content)}
</div>
<div class="post-footer">
<div class="author-bio">
<div class="author-avatar">
<i class="fas fa-user-circle"></i>
</div>
<div class="author-info">
<h4>${this.currentPost.author}</h4>
<p>DevOps Engineer & Cloud Architect passionate about Kubernetes, Infrastructure as Code, and modern development practices.</p>
<div class="social-links">
<a href="https://twitter.com/codewithgarry" target="_blank">
<i class="fab fa-twitter"></i>
</a>
<a href="https://github.com/codewithgarry" target="_blank">
<i class="fab fa-github"></i>
</a>
<a href="https://linkedin.com/in/codewithgarry" target="_blank">
<i class="fab fa-linkedin"></i>
</a>
</div>
</div>
</div>
<div class="related-posts">
<h3>Related Articles</h3>
<div class="related-grid">
${this.renderRelatedPosts()}
</div>
</div>
</div>
</div>
</div>
</div>
`;
// Apply syntax highlighting
if (window.Prism) {
Prism.highlightAllUnder(document.getElementById('markdownContent'));
}
// Smooth scroll for TOC links
this.setupTableOfContentsNavigation();
}
// Generate table of contents
generateTableOfContents(content) {
const headings = content.match(/^#{1,3}\s+(.+)/gm) || [];
return headings.map(heading => {
const level = heading.match(/^#+/)[0].length;
const text = heading.replace(/^#+\s+/, '');
const id = text.toLowerCase().replace(/[^a-z0-9]+/g, '-');
return `
<div class="toc-item toc-level-${level}">
<a href="#${id}" onclick="blogManager.scrollToHeading('${id}')">${text}</a>
</div>
`;
}).join('');
}
// Setup TOC navigation
setupTableOfContentsNavigation() {
const headings = document.querySelectorAll('.markdown-content h1, .markdown-content h2, .markdown-content h3');
headings.forEach(heading => {
const id = heading.textContent.toLowerCase().replace(/[^a-z0-9]+/g, '-');
heading.id = id;
});
}
// Scroll to heading
scrollToHeading(id) {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}
// Render related posts
renderRelatedPosts() {
const relatedPosts = this.posts
.filter(post =>
post.id !== this.currentPost.id &&
(post.category === this.currentPost.category ||
post.tags.some(tag => this.currentPost.tags.includes(tag)))
)
.slice(0, 3);
return relatedPosts.map(post => `
<div class="related-post" onclick="blogManager.openPost('${post.id}')">
<img src="${post.image}" alt="${post.title}">
<div class="related-content">
<h4>${post.title}</h4>
<p>${post.excerpt.substring(0, 100)}...</p>
</div>
</div>
`).join('');
}
// Close full post view
closeFullPost() {
this.currentPost = null;
this.renderBlogInterface();
}
// Filter and search functionality
handleSearch(query) {
this.searchQuery = query.toLowerCase();
this.renderBlogInterface();
}
handleCategoryFilter(category) {
this.selectedCategory = category;
this.renderBlogInterface();
}
handleSort(sortBy) {
this.sortBy = sortBy;
this.renderBlogInterface();
}
getFilteredPosts() {
let filtered = [...this.posts];
// Apply search filter
if (this.searchQuery) {
filtered = filtered.filter(post =>
post.title.toLowerCase().includes(this.searchQuery) ||
post.excerpt.toLowerCase().includes(this.searchQuery) ||
post.tags.some(tag => tag.toLowerCase().includes(this.searchQuery))
);
}
// Apply category filter
if (this.selectedCategory !== 'All') {
filtered = filtered.filter(post => post.category === this.selectedCategory);
}
// Apply sorting
filtered.sort((a, b) => {
switch (this.sortBy) {
case 'date':
return new Date(b.date) - new Date(a.date);
case 'title':
return a.title.localeCompare(b.title);
case 'readTime':
return parseInt(a.readTime) - parseInt(b.readTime);
default:
return 0;
}
});
return filtered;
}
// Utility functions
formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
sharePost() {
if (navigator.share && this.currentPost) {
navigator.share({
title: this.currentPost.title,
text: this.currentPost.excerpt,
url: window.location.href
});
} else {
// Fallback: copy URL to clipboard
navigator.clipboard.writeText(window.location.href).then(() => {
youTubeManager.showNotification('Link copied to clipboard!', 'success');
});
}
}
bookmarkPost() {
if (!this.currentPost) return;
const bookmarks = JSON.parse(localStorage.getItem('bookmarkedPosts') || '[]');
const isBookmarked = bookmarks.some(b => b.id === this.currentPost.id);
if (isBookmarked) {
const updated = bookmarks.filter(b => b.id !== this.currentPost.id);
localStorage.setItem('bookmarkedPosts', JSON.stringify(updated));
youTubeManager.showNotification('Bookmark removed', 'info');
} else {
bookmarks.push({
id: this.currentPost.id,
title: this.currentPost.title,
url: window.location.href,
timestamp: new Date().toISOString()
});
localStorage.setItem('bookmarkedPosts', JSON.stringify(bookmarks));
youTubeManager.showNotification('Post bookmarked!', 'success');
}
}
printPost() {
if (this.currentPost) {
window.print();
}
}
}
// Initialize blog manager
const blogManager = new BlogManager();
// Export for global use
window.blogManager = blogManager;
// CSS for blog system
const blogCSS = `
.blog-header {
margin-bottom: 3rem;
}
.blog-controls {
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
background: var(--bg-secondary);
padding: 1.5rem;
border-radius: 1rem;
border: 1px solid var(--border-color);
}
.search-container {
position: relative;
flex: 1;
min-width: 300px;
}
.search-input {
width: 100%;
padding: 0.75rem 1rem 0.75rem 2.5rem;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
color: var(--text-primary);
font-size: 1rem;
}
.search-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(0, 255, 0, 0.2);
}
.search-icon {
position: absolute;
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
}
.filter-container, .sort-container {
display: flex;
align-items: center;
gap: 0.5rem;
}
.category-filter, .sort-select {
padding: 0.75rem 1rem;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
color: var(--text-primary);
cursor: pointer;
}
.featured-posts, .all-posts {
margin-bottom: 4rem;
}
.featured-posts h2, .all-posts h2 {
color: var(--primary-color);
margin-bottom: 2rem;
font-size: 2rem;
text-align: center;
}
.featured-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 2rem;
margin-bottom: 3rem;
}
.posts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.post-card {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 1rem;
overflow: hidden;
transition: all 0.3s ease;
cursor: pointer;
height: fit-content;
}
.post-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 40px var(--shadow-secondary);
border-color: var(--primary-color);
}
.featured-post {
border: 2px solid var(--primary-color);
background: linear-gradient(135deg, var(--bg-secondary), var(--bg-tertiary));
}
.post-image {
position: relative;
height: 200px;
overflow: hidden;
}
.post-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.post-card:hover .post-image img {
transform: scale(1.05);
}
.post-category {
position: absolute;
top: 1rem;
left: 1rem;
background: var(--primary-color);
color: var(--bg-primary);
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.8rem;
font-weight: 600;
}
.post-content {
padding: 1.5rem;
}
.post-title {
color: var(--primary-color);
margin-bottom: 1rem;
font-size: 1.3rem;
line-height: 1.4;
}
.post-excerpt {
color: var(--text-secondary);
margin-bottom: 1.5rem;
line-height: 1.6;
}
.post-meta {
display: flex;
gap: 1rem;
align-items: center;
margin-bottom: 1rem;
font-size: 0.9rem;
color: var(--text-muted);
flex-wrap: wrap;
}
.post-meta > div {
display: flex;
align-items: center;
gap: 0.25rem;
}
.post-tags {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.tag {
background: rgba(0, 255, 0, 0.1);
color: var(--primary-color);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.8rem;
border: 1px solid rgba(0, 255, 0, 0.3);
}
/* Full Post Styles */
.full-post {
max-width: 1000px;
margin: 0 auto;
}
.post-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
.back-btn {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 0.75rem 1rem;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
}
.back-btn:hover {
background: var(--primary-color);
color: var(--bg-primary);
}
.post-meta-header {
display: flex;
gap: 1rem;
align-items: center;
}
.post-hero {
position: relative;
height: 400px;
border-radius: 1rem;
overflow: hidden;
margin-bottom: 3rem;
}
.hero-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.hero-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
padding: 2rem;
color: white;
}
.hero-content .post-title {
color: white;
font-size: 2.5rem;
margin-bottom: 1rem;
}
.post-content {
display: grid;
grid-template-columns: 250px 1fr;
gap: 3rem;
align-items: start;
}
.content-sidebar {
position: sticky;
top: 100px;
}
.table-of-contents {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.table-of-contents h4 {
color: var(--primary-color);
margin-bottom: 1rem;
font-size: 1.1rem;
}
.toc-item {
margin-bottom: 0.5rem;
}
.toc-item a {
color: var(--text-secondary);
text-decoration: none;
font-size: 0.9rem;
transition: color 0.3s ease;
}
.toc-item a:hover {
color: var(--primary-color);
}
.toc-level-1 { margin-left: 0; font-weight: 600; }
.toc-level-2 { margin-left: 1rem; }
.toc-level-3 { margin-left: 2rem; font-size: 0.8rem; }
.post-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.action-btn {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 0.75rem;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.action-btn:hover {
background: var(--primary-color);
color: var(--bg-primary);
}
.content-main {
min-width: 0;
}
.markdown-content {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 1rem;
padding: 2rem;
line-height: 1.7;
}
.markdown-content h1,
.markdown-content h2,
.markdown-content h3,
.markdown-content h4 {
color: var(--primary-color);
margin: 2rem 0 1rem 0;
}
.markdown-content h1 { font-size: 2rem; }
.markdown-content h2 { font-size: 1.6rem; }
.markdown-content h3 { font-size: 1.3rem; }
.markdown-content p {
margin-bottom: 1.5rem;
color: var(--text-secondary);
}
.markdown-content ul,
.markdown-content ol {
margin-bottom: 1.5rem;
padding-left: 2rem;
}
.markdown-content li {
margin-bottom: 0.5rem;
color: var(--text-secondary);
}
.markdown-content blockquote {
border-left: 4px solid var(--primary-color);
background: rgba(0, 255, 0, 0.1);
padding: 1rem 1.5rem;
margin: 1.5rem 0;
border-radius: 0 0.5rem 0.5rem 0;
}
.markdown-content blockquote p {
margin: 0;
font-style: italic;
}
.markdown-content pre {
background: #1e1e1e;
padding: 1.5rem;
border-radius: 0.5rem;
overflow-x: auto;
margin: 1.5rem 0;
border: 1px solid var(--border-color);
}
.markdown-content code {
font-family: 'Fira Code', monospace;
font-size: 0.9em;
}
.markdown-content :not(pre) > code {
background: rgba(0, 255, 0, 0.1);
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
color: var(--primary-color);
}
.post-footer {
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid var(--border-color);
}
.author-bio {
display: flex;
gap: 1rem;
background: var(--bg-tertiary);
padding: 1.5rem;
border-radius: 0.5rem;
margin-bottom: 3rem;
}
.author-avatar {
font-size: 3rem;
color: var(--primary-color);
}
.author-info h4 {
color: var(--primary-color);
margin-bottom: 0.5rem;
}
.author-info p {
color: var(--text-secondary);
margin-bottom: 1rem;
}
.social-links {
display: flex;
gap: 1rem;
}
.social-links a {
color: var(--text-muted);
font-size: 1.2rem;
transition: color 0.3s ease;
}
.social-links a:hover {
color: var(--primary-color);
}
.related-posts h3 {
color: var(--primary-color);
margin-bottom: 1.5rem;
}
.related-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.related-post {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
}
.related-post:hover {
transform: translateY(-2px);
border-color: var(--primary-color);
}
.related-post img {
width: 100%;
height: 120px;
object-fit: cover;
}
.related-content {
padding: 1rem;
}
.related-content h4 {
color: var(--primary-color);
margin-bottom: 0.5rem;
font-size: 1rem;
}
.related-content p {
color: var(--text-muted);
font-size: 0.9rem;
line-height: 1.4;
}
/* Responsive Design */
@media (max-width: 1024px) {
.post-content {
grid-template-columns: 1fr;
gap: 2rem;
}
.content-sidebar {
position: static;
order: 2;
}
.table-of-contents {
display: none;
}
}
@media (max-width: 768px) {
.blog-controls {
flex-direction: column;
align-items: stretch;
}
.search-container {
min-width: auto;
}
.featured-grid,
.posts-grid {
grid-template-columns: 1fr;
}
.post-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.hero-content .post-title {
font-size: 1.8rem;
}
.related-grid {
grid-template-columns: 1fr;
}
.author-bio {
flex-direction: column;
text-align: center;
}
}
/* Print Styles */
@media print {
.content-sidebar,
.post-header,
.post-footer {
display: none !important;
}
.post-content {
grid-template-columns: 1fr;
}
.markdown-content {
background: white;
border: none;
padding: 0;
}
}
`;
// Add CSS to document
const blogStyle = document.createElement('style');
blogStyle.textContent = blogCSS;
document.head.appendChild(blogStyle);
export { blogManager };