Update blog page asset and md loading

This commit is contained in:
adilallo
2025-09-05 09:46:48 -06:00
parent 5e83655f49
commit b85b4248c0
11 changed files with 126 additions and 170 deletions
+9 -4
View File
@@ -24,7 +24,8 @@ export async function generateStaticParams() {
*/
export async function generateMetadata({ params }) {
try {
const post = getBlogPostBySlug(params.slug);
const { slug } = await params;
const post = getBlogPostBySlug(slug);
if (!post) {
return {
@@ -57,9 +58,10 @@ export async function generateMetadata({ params }) {
/**
* Dynamic blog post page
*/
export default function BlogPostPage({ params }) {
export default async function BlogPostPage({ params }) {
// Get the blog post data
const post = getBlogPostBySlug(params.slug);
const { slug } = await params;
const post = getBlogPostBySlug(slug);
// If post doesn't exist, show 404
if (!post) {
@@ -71,7 +73,10 @@ export default function BlogPostPage({ params }) {
const relatedPosts = allPosts.filter((p) => p.slug !== post.slug).slice(0, 3); // Show up to 3 related posts
return (
<div className="min-h-screen bg-gray-50">
<div
className="min-h-screen"
style={{ backgroundColor: "var(--color-content-default-primary)" }}
>
{/* Main Content */}
<article className="max-w-4xl mx-auto px-4 py-8">
{/* Article Header */}
+4 -3
View File
@@ -1,14 +1,15 @@
"use client";
import React from "react";
import { getAssetPath, ASSETS } from "../../lib/assetUtils";
const ContentContainer = ({ post, width = "200px", size = "responsive" }) => {
// Get the corresponding icon based on the same logic as background images
const getIconImage = (slug) => {
const icons = [
"/assets/Content_Thumbnail/Icon_1.svg",
"/assets/Content_Thumbnail/Icon_2.svg",
"/assets/Content_Thumbnail/Icon_3.svg",
getAssetPath(ASSETS.ICON_1),
getAssetPath(ASSETS.ICON_2),
getAssetPath(ASSETS.ICON_3),
];
if (!slug) return icons[0];
+15 -11
View File
@@ -3,6 +3,7 @@
import React from "react";
import Link from "next/link";
import ContentContainer from "./ContentContainer";
import { getAssetPath, ASSETS } from "../../lib/assetUtils";
/**
* ContentThumbnailTemplate component for displaying blog post previews
@@ -16,15 +17,15 @@ const ContentThumbnailTemplate = ({
// Post-specific background selection - different SVG for each post
const getBackgroundImage = (slug, variant) => {
const verticalImages = [
"/assets/Content_Thumbnail/Vertical_1.svg",
"/assets/Content_Thumbnail/Vertical_2.svg",
"/assets/Content_Thumbnail/Vertical_3.svg",
getAssetPath(ASSETS.VERTICAL_1),
getAssetPath(ASSETS.VERTICAL_2),
getAssetPath(ASSETS.VERTICAL_3),
];
const horizontalImages = [
"/assets/Content_Thumbnail/Horizontal_1.svg",
"/assets/Content_Thumbnail/Horizontal_2.svg",
"/assets/Content_Thumbnail/Horizontal_3.svg",
getAssetPath(ASSETS.HORIZONTAL_1),
getAssetPath(ASSETS.HORIZONTAL_2),
getAssetPath(ASSETS.HORIZONTAL_3),
];
const images = variant === "vertical" ? verticalImages : horizontalImages;
@@ -32,12 +33,15 @@ const ContentThumbnailTemplate = ({
if (!slug) return images[0];
// Use the slug to deterministically select an image
const hash = slug.split("").reduce((a, b) => {
a = (a << 5) - a + b.charCodeAt(0);
return a & a;
}, 0);
// More robust hash function using djb2 algorithm
let hash = 5381;
for (let i = 0; i < slug.length; i++) {
hash = (hash << 5) + hash + slug.charCodeAt(i);
}
return images[Math.abs(hash) % images.length];
// Ensure positive number and get index
const index = Math.abs(hash) % images.length;
return images[index];
};
const backgroundImage = getBackgroundImage(post.slug, variant);
+3 -2
View File
@@ -1,5 +1,6 @@
import Logo from "./Logo";
import Separator from "./Separator";
import { getAssetPath, ASSETS } from "../../lib/assetUtils";
export default function Footer() {
// Schema markup for organization information
@@ -70,7 +71,7 @@ export default function Footer() {
aria-label="Follow us on Bluesky"
>
<img
src="assets/Bluesky_Logo.svg"
src={getAssetPath(ASSETS.BLUESKY_LOGO)}
alt="Bluesky"
width={24}
height={22}
@@ -86,7 +87,7 @@ export default function Footer() {
aria-label="Follow us on GitLab"
>
<img
src="assets/GitLab_Icon.png"
src={getAssetPath(ASSETS.GITLAB_ICON)}
alt="GitLab"
width={22}
height={22}
+4 -3
View File
@@ -4,6 +4,7 @@ import MenuBarItem from "./MenuBarItem";
import Button from "./Button";
import AvatarContainer from "./AvatarContainer";
import Avatar from "./Avatar";
import { getAssetPath, ASSETS } from "../../lib/assetUtils";
// Configuration data for testing
export const navigationItems = [
@@ -13,9 +14,9 @@ export const navigationItems = [
];
export const avatarImages = [
{ src: "assets/Avatar_1.png", alt: "Avatar 1" },
{ src: "assets/Avatar_2.png", alt: "Avatar 2" },
{ src: "assets/Avatar_3.png", alt: "Avatar 3" },
{ src: getAssetPath(ASSETS.AVATAR_1), alt: "Avatar 1" },
{ src: getAssetPath(ASSETS.AVATAR_2), alt: "Avatar 2" },
{ src: getAssetPath(ASSETS.AVATAR_3), alt: "Avatar 3" },
];
export const logoConfig = [
+23 -21
View File
@@ -1,3 +1,5 @@
import { getAssetPath, ASSETS } from "../../lib/assetUtils";
export default function Logo({ size = "default", showText = true }) {
// Size configurations
const sizes = {
@@ -91,26 +93,26 @@ export default function Logo({ size = "default", showText = true }) {
size === "homeHeaderXsmall"
? sizes.homeHeaderXsmall
: size === "homeHeaderSm"
? sizes.homeHeaderSm
: size === "homeHeaderMd"
? sizes.homeHeaderMd
: size === "homeHeaderLg"
? sizes.homeHeaderLg
: size === "homeHeaderXl"
? sizes.homeHeaderXl
: size === "header"
? sizes.header
: size === "headerMd"
? sizes.headerMd
: size === "headerLg"
? sizes.headerLg
: size === "headerXl"
? sizes.headerXl
: size === "footer"
? sizes.footer
: size === "footerLg"
? sizes.footerLg
: sizes.default;
? sizes.homeHeaderSm
: size === "homeHeaderMd"
? sizes.homeHeaderMd
: size === "homeHeaderLg"
? sizes.homeHeaderLg
: size === "homeHeaderXl"
? sizes.homeHeaderXl
: size === "header"
? sizes.header
: size === "headerMd"
? sizes.headerMd
: size === "headerLg"
? sizes.headerLg
: size === "headerXl"
? sizes.headerXl
: size === "footer"
? sizes.footer
: size === "footerLg"
? sizes.footerLg
: sizes.default;
return (
<div
@@ -142,7 +144,7 @@ export default function Logo({ size = "default", showText = true }) {
{/* Vector Icon */}
<img
src="assets/Logo.svg"
src={getAssetPath(ASSETS.LOGO)}
alt="CommunityRule Logo Icon"
width={27.05}
height={27.05}
+2 -1
View File
@@ -1,6 +1,7 @@
import { Inter, Bricolage_Grotesque, Space_Grotesk } from "next/font/google";
import "./globals.css";
import HomeHeader from "./components/HomeHeader";
import Header from "./components/Header";
import Footer from "./components/Footer";
const inter = Inter({
@@ -86,7 +87,7 @@ export default function RootLayout({ children }) {
className={`${inter.variable} ${bricolageGrotesque.variable} ${spaceGrotesk.variable}`}
>
<div className="min-h-screen flex flex-col">
<HomeHeader />
<Header />
<main className="flex-1">{children}</main>
<Footer />
</div>
+2
View File
@@ -5,6 +5,7 @@ import RuleStack from "./components/RuleStack";
import QuoteBlock from "./components/QuoteBlock";
import FeatureGrid from "./components/FeatureGrid";
import AskOrganizer from "./components/AskOrganizer";
import HomeHeader from "./components/HomeHeader";
export default function Page() {
const heroBannerData = {
@@ -53,6 +54,7 @@ export default function Page() {
return (
<div>
<HomeHeader />
<HeroBanner {...heroBannerData} />
<LogoWall />
<NumberedCards {...numberedCardsData} />
+5 -124
View File
@@ -7,131 +7,12 @@ related:
["operational-security-mutual-aid", "making-decisions-without-hierarchy"]
---
# Resolving Active Conflicts
Many groups strive to work without bosses, managers, or traditional leadership structures. But when no one's in charge, how do decisions get made? Non-hierarchical groups often rely on collective processes that prioritize trust, transparency, and shared responsibility. These approaches can take more time upfront, but they help build stronger, more equitable communities in the long run.
Conflict is a natural part of any community or organization. When people work together with different perspectives, experiences, and goals, disagreements are inevitable. However, how we handle these conflicts can make the difference between a thriving community and one that falls apart.
One common method is consensus-based decision-making. In this approach, the goal is not just to get majority agreement but to ensure that everyone can live with the outcome. Consensus doesn't mean everyone gets exactly what they want—it means no one is actively opposed. This usually requires open discussion, active listening, and a willingness to compromise. It also works best when the group has shared values and clear communication norms.
## Understanding Conflict
Another option is to use roles or working groups that have specific scopes of responsibility, even if the group itself is flat. For example, one team might handle finances while another focuses on outreach. These roles can rotate or be chosen by the group, and decisions within those areas can be made autonomously—provided there's transparency and accountability back to the wider group.
Before we can resolve conflicts effectively, we need to understand what they are and why they occur. Conflicts arise when:
Tools also matter. Structured facilitation, shared agendas, and decision logs can keep the process from getting stuck or dominated by a few voices. Some groups use hand signals or colored cards during meetings to check for consensus or surface concerns. Others rely on asynchronous tools like polls, shared documents, or messaging platforms to give everyone a chance to weigh in.
- **Interests clash**: Different people want different outcomes
- **Values differ**: People have different beliefs about what's important
- **Communication breaks down**: Misunderstandings lead to tension
- **Resources are limited**: Competition for scarce resources creates friction
## The Conflict Resolution Process
### 1. Create a Safe Space
The first step in resolving any conflict is to create an environment where all parties feel safe to express their concerns honestly.
- Choose a neutral location
- Set ground rules for respectful communication
- Ensure confidentiality when appropriate
- Allow everyone to speak without interruption
### 2. Listen Actively
Active listening is crucial for understanding the root causes of conflict.
- Focus entirely on what the other person is saying
- Ask clarifying questions
- Reflect back what you've heard
- Avoid formulating your response while they're speaking
### 3. Identify Common Ground
Even in the most heated conflicts, there's usually some shared interest or value.
- Look for shared goals
- Acknowledge valid concerns on all sides
- Focus on what you agree on
- Build from areas of consensus
### 4. Generate Solutions Together
The best solutions come from collaborative problem-solving.
- Brainstorm multiple options
- Consider creative alternatives
- Evaluate solutions based on shared criteria
- Be willing to compromise
## Maintaining Trust Through Conflict
Trust is often the first casualty of conflict, but it's also essential for resolution.
### Be Transparent
- Share relevant information openly
- Explain your reasoning and concerns
- Admit when you don't know something
- Be honest about your limitations
### Follow Through
- Keep your commitments
- Do what you say you'll do
- Check in regularly on progress
- Address setbacks promptly
### Show Respect
- Value different perspectives
- Acknowledge others' expertise
- Give credit where it's due
- Treat everyone with dignity
## When Conflicts Persist
Sometimes conflicts don't resolve easily, even with the best intentions.
### Seek Mediation
When direct resolution isn't working, consider bringing in a neutral third party who can:
- Facilitate communication
- Help identify underlying issues
- Suggest alternative approaches
- Guide the process impartially
### Know When to Step Back
Not every conflict needs to be resolved immediately. Sometimes:
- Taking a break can provide perspective
- Time can reveal new information
- External factors may change
- The conflict may resolve itself
## Building Conflict-Resilient Communities
The best approach to conflict is prevention through building strong community foundations.
### Establish Clear Processes
- Define how decisions are made
- Create channels for raising concerns
- Set up regular check-ins
- Document agreements and changes
### Invest in Relationships
- Build personal connections
- Learn about each other's backgrounds
- Celebrate successes together
- Support each other through challenges
### Practice Regular Communication
- Hold regular team meetings
- Create opportunities for informal interaction
- Share updates and information
- Encourage feedback and suggestions
## Conclusion
Conflict resolution is not about eliminating disagreements—it's about handling them in ways that strengthen rather than weaken your community. By approaching conflicts with patience, respect, and a genuine desire to find solutions that work for everyone, you can turn challenging situations into opportunities for growth and deeper connection.
Remember: the goal isn't to win the argument, but to find a path forward that everyone can support.
Non-hierarchical decision-making isn't about having no structure—it's about choosing structures that reflect the group's values and support participation. It takes intention and care, but done well, it creates space for more voices, deeper buy-in, and decisions that reflect collective wisdom, not just individual authority.
+54
View File
@@ -0,0 +1,54 @@
/**
* Asset path utilities for handling different environments
* - Web app: uses absolute paths starting with /
* - Storybook: uses relative paths for proper asset resolution
*/
/**
* Get the correct asset path based on environment
* @param {string} assetPath - The asset path (e.g., "assets/Logo.svg")
* @returns {string} - The correct path for the current environment
*/
export function getAssetPath(assetPath) {
// Check if we're in Storybook environment
const isStorybook =
typeof window !== "undefined" &&
(window.location?.pathname?.includes("iframe.html") ||
window.navigator?.userAgent?.includes("Storybook"));
// In Storybook, use relative paths
if (isStorybook) {
return assetPath;
}
// In web app, use absolute paths
return assetPath.startsWith("/") ? assetPath : `/${assetPath}`;
}
/**
* Asset paths for common components
*/
export const ASSETS = {
// Logo
LOGO: "assets/Logo.svg",
// Avatars
AVATAR_1: "assets/Avatar_1.png",
AVATAR_2: "assets/Avatar_2.png",
AVATAR_3: "assets/Avatar_3.png",
// Social media
BLUESKY_LOGO: "assets/Bluesky_Logo.svg",
GITLAB_ICON: "assets/GitLab_Icon.png",
// Content thumbnails
VERTICAL_1: "assets/Content_Thumbnail/Vertical_1.svg",
VERTICAL_2: "assets/Content_Thumbnail/Vertical_2.svg",
VERTICAL_3: "assets/Content_Thumbnail/Vertical_3.svg",
HORIZONTAL_1: "assets/Content_Thumbnail/Horizontal_1.svg",
HORIZONTAL_2: "assets/Content_Thumbnail/Horizontal_2.svg",
HORIZONTAL_3: "assets/Content_Thumbnail/Horizontal_3.svg",
ICON_1: "assets/Content_Thumbnail/Icon_1.svg",
ICON_2: "assets/Content_Thumbnail/Icon_2.svg",
ICON_3: "assets/Content_Thumbnail/Icon_3.svg",
};
+5 -1
View File
@@ -9,7 +9,9 @@ const tagCache = new Map();
const authorCache = new Map();
// Cache configuration
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
const isDevelopment =
process.env.NODE_ENV === "development" || !process.env.NODE_ENV;
const CACHE_TTL = isDevelopment ? 0 : 5 * 60 * 1000; // 0 in dev, 5 minutes in production
const MAX_CACHE_SIZE = 100; // Maximum number of cached items
/**
@@ -22,6 +24,8 @@ class CacheEntry {
}
isExpired() {
// In development, always consider cache expired (no caching)
if (isDevelopment) return true;
return Date.now() - this.timestamp > CACHE_TTL;
}
}