Blog Engine with Fresh: Building Single Post Page
Tags: javascript, webdev, react, fresh
Published: 2022-08-10 12:35: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/post/[id].jsx

/** @jsx h */
import { h } from "preact";
import VPost from '../../components/front/post.jsx';
import CPost from "../../controllers/front/post.js";
 
 
export const handler = {
  async GET(req, ctx) {
      return await CPost.getPost(req, ctx);
  },
}
 
 
export default function Template(props){
  return (
    <VPost data={props.data} />
  )
}
```

```js
// controllers/front/post.js

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


class Post{
    async getPost(req, ctx){
        const config = setting();
        config.item = await postdb.getPost(ctx.params.id);
        return await ctx.render({"setting": config});
    }
}

export default new Post();
```

```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/post.jsx
 
/** @jsx h */
import { h } from "preact";
import Base from "../base.jsx";
 

function PostJsx(props){ 
    const item = props.data.setting.item;

    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" />
            <link rel="stylesheet" href="/styles/front/post.css" />
            <script src="/scripts/setPlayer.js"></script>

            <div class="main region">
                <div class="content">
                    <div class="post">
                        <h2 class="title">{item.title}</h2>
                        <div class="categories">{item.categories.toString()}</div>
                        <div class="date">{(new Date(item.date)).toLocaleDateString("it-IT")}</div>
                        
                        { ((item.videos !== '')&&(item.videos !== '[]')) &&
                        <section>
                        <div class="video">
                            <div class="screen"></div>
                            <div class="playlist"></div>
                        </div>

                        <script dangerouslySetInnerHTML={{__html: `
                            const videos = JSON.parse('${ item.videos }')
                            videos.reverse()
                    
                            let clicked = 0
                    
                            for(let index in videos){
                                let ending = ''
                                if(index == videos.length-1){
                                    ending = videos[index].status
                                }

                                let title = videos[index].title
                        
                                let result = '<div id="part'+index+'" class="part" onClick="setScreen(videos['+index+'],'+index+',true)">videoTitle part​ '+(++index)+' '+ending+'</div>'
                                let html = result.replace("videoTitle", "${item.title}")
                                $('.playlist').append(html)
                            }

                            setScreen(videos[0],0,false)
                        `}}/>
                        </section>
                        }

                        <article dangerouslySetInnerHTML={{__html: `
                            ${item.content}
                        `}}/>
                    </div>
                    <div id="disqus_thread"></div>
                    <script dangerouslySetInnerHTML={{__html: `
                        var disqus_config = function () {
                        this.page.url = "https://khmerweb-fresh.deno.dev/post/${item.id}"
                        this.page.identifier = "${item.id}"; 
                        };
                            
                        (function() { // DON'T EDIT BELOW THIS LINE
                        var d = document, s = d.createElement('script');
                        s.src = 'https://multimedia-15.disqus.com/embed.js';
                        s.setAttribute('data-timestamp', +new Date());
                        (d.head || d.body).appendChild(s);
                        })();
                    `}}/>

                </div>
                <div class="sidebar">Sidebar</div>
            </div>
        </section>
    )
}
  
 
export default function Post(props){
    props.data.page = PostJsx;
    return(
        <Base data={props.data} />
    )
}
```

```js
// static/scripts/setPlayer.js

function setScreen(entry,id,click){
    if(entry['type'] == 'OK'){
        var url = `//ok.ru/videoembed/${entry['id']}`
    }else if(entry['type'] == 'YouTube'){
        var url = `https://www.youtube.com/embed/${entry['id']}`
    }else if(entry['type'] == 'YouTubePlaylist'){
        var url = `https://www.youtube.com/embed/videoseries?list=${entry['id']}`
    }else if(entry['type'] === "Facebook"){
        var url = `https://www.facebook.com/watch/?v=${entry['id']}`
    }else if(entry['type'] === "GoogleDrive"){
        var url = `https://docs.google.com/file/d/${entry['id']}/preview`
    }else if(entry['type'] === "Vimeo"){
        var url = `https://player.vimeo.com/video/${entry['id']}`
    }else if(entry['type'] === "Dailymotion"){
        var url = `https://www.dailymotion.com/embed/video/${entry['id']}`
    }

    if(entry['type'] !== 'Facebook'){
        var iframe = `<div>
        <iframe id="video-player" src="${url}" frameborder="0" allowfullscreen></iframe>
        </div>`;
      }else{
        var iframe = `<div class="fb-video" data-href="${url}" data-width="auto" data-show-captions="true"></div>`;
    }

    if(click){
        $('.post .video .playlist #part'+clicked)
            .css({'background':'var(--background)','color':'black'})
    }
    $('.post .video .playlist #part'+id)
        .css({'background':'var(--background-dark)','color':'white'})


    $('.post .video .screen').html(iframe)
    if((entry['type'] === "Facebook")&&(fb_api)){
        FB.XFBML.parse()
    }  

    clicked = id
}
```

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

.Home .main .content .post .title{
    padding-bottom: 15px;
}

.Home .main .content .post .date{
    padding-bottom: 30px;
}

.Home .main .content .post .video{
    background: var(--background);
}

.Home .main .content .post .video .screen div{
    position: relative;
    padding-top: 56.25%;
}

.Home .main .content .post .video .screen div iframe{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.Home .main .content .post .video .playlist{
    max-height: 405px;
    overflow: auto;
    margin-bottom: 20px;
}

.Home .main .content .post .video .playlist .part{
    text-align: left;
    padding: 10px;
    color: black;
    display: block;
    height: auto;
    border: 1px solid black;
    border-top: none;
    font-size: 16px;
}

.Home .main .content .post .video .playlist .part:last-child{
    margin-bottom: 0;
}

.Home .main .content .post .video .playlist .part:hover{
    cursor: pointer;
    opacity: .7;
}

.Home .main .content .post article img{
    width: 100%;
}
```