Blog Engine with Fresh: Listing Posts on Homepage
Tags: javascript, webdev, react, fresh
Published: 2022-08-10 09:45:00

{% embed https://dev.to/sokhavuth/table-of-contents-41pd %}
GitHub: [https://github.com/Sokhavuth/deno-fresh](https://github.com/Sokhavuth/deno-fresh)
Deno Deploy: [https://khmerweb-fresh.deno.dev](https://khmerweb-fresh.deno.dev)
<br/>

```jsx
// routes/index.tsx
 
/** @jsx h */
import { h } from "preact";
import { Handlers, PageProps } from "$fresh/server.ts";
import VHome from '../components/front/home.jsx';
import CHome from "../controllers/front/home.js";
 
 
export const handler: Handlers = {
  async GET(req, ctx) {
      return await CHome.getPosts(req, ctx);
  },
}
 
 
export default function Template(props: PageProps){
  return (
    <VHome data={props.data} />
  )
}
```

```js
// controllers/front/home.js

import { setting } from "setting";
import postdb from "../../models/post.ts"


class Home{
    async getPosts(req, ctx){
        const config = setting();
        config.items = await postdb.getPosts(config.homePostAmount);
        return await ctx.render({"setting": config});
    }
}

export default new Home();
```

```ts
// models/post.ts

import { mydb } from "setting"

interface PostSchema {
    _id: ObjectId;
    id: string; 
    title: string;
    content: string;
    categories: string[];
    thumb: string;
    date: string;
    videos: string;
    userid: string;
}

class Post{
    async count(query={}){
        const posts = mydb.collection<PostSchema>("posts")
        return await posts.countDocuments(query)
    }

    async insertPost(req, user_id: string){
        const id = crypto.randomUUID()
        const formData = await req.formData()

        let categories: string[]

        if(formData.get("categories").includes(',')){
            categories = formData.get("categories").split(',')
        }else{
            categories = [formData.get("categories")]
        }
        
        const new_post = {
            id: id, 
            title: formData.get("title"),
            content: formData.get("content"),
            categories: categories,
            thumb: formData.get("thumb"),
            date: formData.get("datetime"),
            videos: formData.get("videos"),
            userid: user_id,
        }
 
        const posts = mydb.collection<PostSchema>("posts")
        await posts.insertOne(new_post)
    }

    async getPosts(amount: number, query={}){
        const posts = mydb.collection<PostSchema>("posts")
        return await posts.find(query).sort({date:-1,_id:-1}).limit(amount).toArray()
    }

    async getPost(post_id: string){
        const posts = mydb.collection<PostSchema>("posts")
        return await posts.findOne({id: post_id})
    }

    async updatePost(req, post_id: string){
        const formData = await req.formData()

        let categories: string[]

        if(formData.get("categories").includes(',')){
            categories = formData.get("categories").split(',')
        }else{
            categories = [formData.get("categories")]
        }

        const edited_post = {$set:{
            title: formData.get("title"),
            content: formData.get("content"),
            categories: categories,
            thumb: formData.get("thumb"),
            date: formData.get("datetime"),
            videos: formData.get("videos"),
        }}

        const posts = mydb.collection<PostSchema>("posts")
        await posts.updateOne({id: post_id}, edited_post)
    }

    async deletePost(post_id: string){
        const posts = mydb.collection<PostSchema>("posts")
        await posts.deleteOne({id: post_id})
    }

    async paginatePosts(amount: number, page: number){
        const posts = mydb.collection<PostSchema>("posts")
        return await posts.find().skip(amount*page).sort({date:-1,_id:-1}).limit(amount).toArray();
    }

}

export default new Post()
```

```jsx
// components/front/home.jsx
 
/** @jsx h */
import { h } from "preact";
import Base from "../base.jsx";
 

function HomeJsx(props){ 
    const items = props.data.setting.items;
    const listItems = items.map((item) =>
    <li>
      <a class="thumb" href={`/post/${item.id}`}>
        <img src={item.thumb} />
        {((item.videos !== "" )&&(item.videos !== "[]")) &&
          <img class="play-icon" src={`/images/play.png`} />
        }
      </a>
      <div class="title">
        <a href={`/post/${item.id}`}>{item.title}</a>
        <div class="date">{(new Date(item.date)).toLocaleDateString('it-IT')}</div>
        <div class="text" dangerouslySetInnerHTML={{ __html: `${ item.content }` }} />
      </div>
    </li>
    )

    return(
        <section class="Home">
            <link href="/styles/front/home.css" rel="stylesheet" />
            <script src="/scripts/menu.js"></script>
            <header>
                <div class="inner region">
                    <div class="title"><a href="/">{ props.data.setting.site_title }</a></div>
                    <form action="search" method="post">
                        <select class="category" name="frontSearch">
                            <option>Posts</option>
                            <option>Books</option>
                        </select>
                        <input type="text" name="q" required placeholder="Search" />
                        <input type="submit" value="Submit" />
                    </form>
                    <div class="login">
                        <a href="/login">Login</a> | <a href="#">Register</a>
                    </div>
                </div>
            </header>
            <div class="menu">
                <div class="inner region">
                    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
                    <link rel="stylesheet" href="/styles/front/menu.css" />

                    <div class="topnav" id="myTopnav" dangerouslySetInnerHTML={{__html: `
                        <a href="/" class="active">Home</a>
                        <a href="#news">News</a>
                        <a href="#contact">Contact</a>
                        <a href="#about">About</a>
                        <a href="javascript:void(0);" class="icon" onclick="mobileMenu()">
                            <i class="fa fa-bars"></i>
                        </a>
                    `}}/>
                </div>
            </div>
            <link rel="stylesheet" href="styles/front/main.css" />
            <div class="main region">
                <div class="content">
                    <ul> { listItems } </ul>
                </div>
                <div class="sidebar">Sidebar</div>
            </div>
        </section>
    )
}
  
 
export default function Home(props){
    props.data.page = HomeJsx;
    return(
        <Base data={props.data} />
    )
}
```

```css
/* static/styles/front/main.css */

.Home .main{
    padding-top: 20px;
    display: grid;
    grid-template-columns: 70% calc(30% - 15px);
    grid-gap: 15px;
    padding-bottom: 30px;
}

.Home .main .content,
.Home .main .sidebar{
    background: white;
    padding: 20px;
}

.Home .main .content ul{
    list-style-type: none;
}

.Home .main .content ul li{
    display: grid;
    grid-template-columns: 35% 65%;
    margin-bottom: 20px;
    background: rgb(243, 243, 243);
}

.Home .main .content ul li:last-child{
    margin-bottom: 0;
}

.Home .main .content ul li .thumb{
    position: relative;
    padding-top: 56.25%
}

.Home .main .content ul li .thumb img{
    position: absolute;
    top: 0%;
    left: 0;
    width: 100%;
    height: 100%;
}

.Home .main .content ul li .thumb .play-icon{
    width: 20%;
    height: auto;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.Home .main .content ul li .title{
    padding: 5px 10px;
}

.Home .main .content ul li .title a{
    font: 22px/1.5 Oswald, Limonf3;
    padding-bottom: 5px;
    display: block;
    width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis
}

.Home .main .content ul li .title .text{
    padding-top: 10px;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
}

@media only screen and (max-width: 600px){
    .Home .main{
        grid-template-columns: 100%;
    }

    .Home .main .content ul li{
        grid-template-columns: 100%;
    }
}
```