Blog Engine with Fresh: Creating Post Item
Tags: javascript, webdev, react, fresh
Published: 2022-08-09 09:24: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/login](https://khmerweb-fresh.deno.dev/login)
<br/>

```js
// routes/admin/post.jsx

/** @jsx h */
import { h } from "preact";
import VPost from '../../components/admin/post.jsx';
import CPost from "../../controllers/admin/post.js";


export const handler = {
  async GET(req, ctx){
      return await CPost.getPage(req, ctx);
  },  

  async POST(req, ctx){
      return await CPost.createPost(req, ctx);
  },
}


export default function Template(props){
    return (
        <VPost data={ props.data } />
    )
}
```

```js
// controllers/admin/post.js

import { getCookies, deleteCookie } from "cookies";
import { setting, secret_key, myredis } from 'setting';
import { verify } from "jwt";
import postdb from "../../models/post.ts";


class Post{
    async getPage(req, ctx){
        const cookies = getCookies(req.headers);

        if((cookies)&&(cookies.session_id)){
            const jwt = await myredis.get(cookies.session_id);
            try{
                const payload = await verify(jwt, secret_key, "HS512");
                if(payload.user){
                    const config = setting();
                    config.page_title = "Post page";
                    config.username = payload.user.title;
                    config.count = await postdb.count();
                    config.items = await postdb.getPosts(config.post_amount);
                    
                    return await ctx.render({"setting": config});
                }
            }catch(error){
                console.log(error);
                const config = setting();
                config.page_title = "Login page";
                const resp = new Response();
                deleteCookie(resp.headers, "session_id");
                return new Response(undefined, { headers: {location: `/login`}, status: 302 });
            }
        }   

        return new Response(undefined, { headers: {location: `/login`}, status: 302 });
    }

    async createPost(req, ctx){
        const cookies = getCookies(req.headers);
        if((cookies)&&(cookies.session_id)){
            const jwt = await myredis.get(cookies.session_id);
            try{
                const payload = await verify(jwt, secret_key, "HS512");
                if(payload.user.role in {'Admin':1,'Editor':1,'Author':1}){
                    await postdb.insertPost(req, payload.user.id);
                }
                return new Response(undefined, { headers: {location: `/admin/post`}, status: 302 });
            }catch(error){
                console.log(error);
                const resp = new Response(undefined, { headers: {location: `/login`}, status: 302 });
                deleteCookie(resp.headers, "session_id");
                return resp;
            }
        }

        return new Response(undefined, { headers: {location: `/login`}, status: 302 });
    }
}

export default new Post();
```

```js
// components/admin/post.jsx

/** @jsx h */
import { h } from "preact";
import Index from "./index.jsx";


function PostJsx(props){
  const item = props.data.setting.item;
  let editor = ``
  let videos = ``

  if(item){
      editor = `
          <form action="/admin/post/edit/${item.id}" name="form" method="post" 
          onSubmit="submitForm(event)">
            <input type="text" name="title" value="${item.title}" required 
            placeholder="Post title" />
            <textarea id="editor" name="content" >${item.content}</textarea>
            <input type="text" name="categories" value="${item.categories.toString()}" required 
            placeholder="Categories" />
            <div class="wrapper"> 
                <select id="category" onChange="getCategory()">
                  <option>Select a category</option>
                  <option>News</option>
                  <option>Movie</option>
                  <option>Entertainment</option>
                  <option>Sport</option>
                </select>
                <input type="text" name="thumb" value="${item.thumb}" required 
                placeholder="Thumbnail" />
                <input type="datetime-local" value="${item.date}" name="datetime" required />
                <input type="submit" value="Publish" />
                <input type="hidden" name="videos" value='${item.videos}' />
            </div>
          </form>
      `
      videos = `
      let is_video = null
      is_video = JSON.parse('${item.videos}')
  
      if((is_video !== '') && (is_video !== '[]')){
        let html = ''
        let episode = is_video.length
      
        for(let video of is_video){
            html += "<div>"
            html += '<input value="'+video.type+'" required />'
            html += '<input value="'+video.id+'" required />'
            html += '<input value="'+video.status+'" required />'
            html += '<p title="Delete" onClick="deleteRow(event)" class="episode">'+(episode--)+'</p>'
            html += "</div>"
        }
  
        if($('.viddata div').html() === ''){
          $('.viddata div').append('<b>Type</b>')
          $('.viddata div').append('<b>Video id</b>')
          $('.viddata div').append('<b>Status</b>')
          $('.viddata div').append('<b>Part/Delete</b>')
        }
      
        $('.viddata div:eq(0)' ).after(html)
      }
      `
  }else{
      editor = `
          <form action="/admin/post" name="form" method="post" onSubmit="submitForm(event)">
            <input type="text" name="title" required placeholder="Post title" />
            <textarea id="editor" name="content"></textarea>
            <input type="text" name="categories" required placeholder="Categories" />
            <div class="wrapper"> 
                <select id="category" onChange="getCategory()">
                  <option>Slect a category</option>
                  <option>News</option>
                  <option>Movie</option>
                  <option>Entertainment</option>
                  <option>Sport</option>
                </select>
                <input type="text" name="thumb" required placeholder="Thumbnail" />
                <input type="datetime-local" name="datetime" required />
                <input type="submit" value="Publish" />
                <input type="hidden" name="videos" value="" />
            </div>
          </form>
      `
      videos = ``
  }

  return(
      <section class="Post">
          <script src="/scripts/ckeditor/ckeditor.js"></script>
          <script src="/scripts/addCategory.js"></script>
          <script src="/scripts/video.js"></script>
          <link rel="stylesheet" href="/styles/admin/post.css" />

          <div dangerouslySetInnerHTML={{__html: `
              ${editor}
          `}}/>
          
          <script src="/scripts/ckeditor/config.js"></script>

          <div class="wrapper" >
              <select name="type">
                <option>YouTube</option>
                <option>YouTubePL</option>
                <option>Facebook</option>
                <option>OK</option>
              </select>
              <input type="text" name="videoid" required placeholder="Video id" />
              <select name="status">
                <option>Ending</option>
                <option>Continue</option>
                <option>~ Ending</option>
              </select>
              <div dangerouslySetInnerHTML={{__html: `
                  <input onclick="genJson()" type="submit" value="Insert video" />
              `}} />
          </div>

          <div class='viddata'>
            <div></div>
          </div>
          <script dangerouslySetInnerHTML={{__html: `${videos}`}}/>
      </section>
  )
}

export default function Post(props){
  props.data.pageInner = PostJsx
  return(
    <Index data={props.data} />
  )
}
```

```js
// components/admin/index.jsx

/** @jsx h */
import { h } from "preact";
import Base from "../base.jsx"


function IndexJsx(props){
    const Page = props.data.pageInner;
    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>{(new Date(item.date)).toLocaleDateString('it-IT')}</div>
      </div>
      <div class="edit">
        <a href={`/admin/post/edit/${item.id}`}><img src={`/images/edit.png`} /></a>
        <a href={`/admin/post/delete/${item.id}`}><img src={`/images/delete.png`} /></a>
      </div>
    </li>
    )
    return(
        <section class="Index" >
            <link rel="stylesheet" href="/styles/admin/index.css" />
        <header>
          <div class="inner region">
              <div class="title">{props.data.setting.page_title}</div>
              <form action="/admin/search" method="post">
                <select name="admin_search">
                  <option>posts</option>
                  <option>books</option>
                </select>
                <input type="text" name="admin_q" required placeholder="Search" />
                <input type="submit" value="Search" />
              </form>
              <div class="logout"><span>{props.data.setting.username}</span> | <a href="/">Home</a> | <a href="/logout">Logout</a></div>
          </div>
        </header>

        <div class="main region">
          <div class="sidebar">
            <div class="inner">
              <a href="/admin"><img src="/images/movie.png" /></a>
              <a href="/admin">Post</a>

              <a href="/admin/book"><img src="/images/books.png" /></a>
              <a href="/admin/book">Book</a>

              <a href="/admin/category"><img src="/images/category.png" /></a>
              <a href="/admin/category">Category</a>

              <a href="/admin/upload"><img src="/images/upload.png" /></a>
              <a href="/admin/upload">Upload</a>

              <a href="/admin/user"><img src="/images/users.png" /></a>
              <a href="/admin/user">User</a>

              <a href="/admin/setting"><img src="/images/setting.png" /></a>
              <a href="/admin/setting">Setting</a>
            </div>
          </div>
          <div class="content">
            <Page data={props.data} />
          </div>
        </div>

        <div class="footer region">
          <div class="info">Total number of items: {props.data.setting.count}</div>
          <ul class="list">
              { listItems }
          </ul>
          <div class="pagination"><img src="/images/load-more.png" /></div>
          <div class="credit">&copy; <a href="https://khmerweb.vercel.app/">Khmer Web 2022</a></div>
        </div>
        </section>
    )
}

export default function Index(props){
    props.data.page = IndexJsx
    return(
        <Base data={props.data} />
    )
}
```

```js
// components/base.jsx

/** @jsx h */
import { h } from "preact";


export default function Base(props){
    const Page = props.data.page
    return(
        <html>
          <head>
            <meta charSet="UTF-8"/>
            <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
            <title>{props.data.setting.site_title} | {props.data.setting.page_title}</title>
            <link href="/styles/base.css" rel="stylesheet" />
            <link href="/fonts/setup.css" rel="stylesheet" />
            <script src="/scripts/jquery.js"></script>
          </head>
          <body>
            <Page data={props.data} />
          </body>
        </html>
    )
  }
```

```js
// static/scripts/addCategory.js 

function getCategory(){
    const category = $('#category option:selected').text()
    $('select').prop('selectedIndex',0)
    let categories = $('[name=categories]').val()
    if(categories === ''){
        categories += category
    }else{
        categories += (`, ${category}`)
    }
    
    $('[name=categories]').val(categories)
}
```

```js
// static/scripts/video.js

let episode = 0

const genJson = () => {
    let json = $('input[name="videos"]').val()
    if((json !== '')&&(json !== '[]')){
        json = JSON.parse(json)
        episode = json.length
    }else{
        episode = 0
    }

    const type = $('select[name="type"').val()
    const id = $('input[name="videoid"').val()
    const status = $('select[name="status"').val()
            
    let video = {
        type: type,
        id: id,
        status: status,
    }
        
    let success = false
    
    for(let v in video){
        if(video[v] === ''){
            alert('You need to fill the required field '+v)
            success = false
            break
        }else{
            success = true
        }
    }

    if(success){
        let json = $('input[name="videos"]').val()
        video = {
            type: type,
            id: id,
            status: status,
        }
        if((json === '')){
            json = JSON.stringify([video])
            $('input[name="videos"]').val(json)
        }else{
            json = JSON.parse(json)
            json.push(video)
            json = JSON.stringify(json)
            $('input[name="videos"').val(json)
        }

        let html =``

        for(let v in video){
            html += `<input value="${video[v]}" required />`
        }

        html += `<p title="Delete" onClick="deleteRow(event)" class="episode">${++episode}</p>`
        html = `<div>${html}</div>`
        
        if($('.viddata div').html() === ''){
            $('.viddata div').append('<b>Type​</b>')
            $('.viddata div').append('<b>Video id​</b>')
            $('.viddata div').append('<b>Status</b>')
            $('.viddata div').append('<b>Part/Delete</b>')
        }
        
        $('.viddata div:eq(0)' ).after(html)
    }
}

function submitForm(e){
    e.preventDefault()
    
    const is_video = $('input[name="videos"').val()

    if((is_video !== '') && (is_video !== '[]')){
        episode = JSON.parse(is_video).length
        let videos = []
        let part = {}
        let key = {0:'type', 1:'id', 2:'status'}

        for(let v=1; v<=episode; v++){
            for(let j=0; j<3; j++){
                part[key[j]] = $(`.viddata div:eq(${v}) input:eq(${j})`).val()
            }

            videos.push({...part})
        }
    
        const json = JSON.stringify(videos)
        $('input[name="videos"').val(json)
    }
    
    document.forms["form"].submit()
}

function deleteRow(e) {
    e.target.parentElement.remove()
    
    let index = parseInt(e.target.innerHTML)
    index = index - 1
    let json = $('input[name="videos"]').val()
    json = JSON.parse(json)
    json.splice(index, 1)
    episode = json.length
    json = JSON.stringify(json)
    $('input[name="videos"').val(json)

    for(let v=episode; v>-1; v--){
        $('.episode').eq(v).html(episode-v)
    }
}
```

```css
/* static/styles/admin/post.css */
.Index .main .content form .ck-editor__editable{
    min-height: 305px;
}

.Index .main .content form .wrapper,
.Index .main .content .wrapper,
.Index .main .content .viddata div{
    display: grid;
    grid-template-columns: 20% 32.5% 32.5% 15%;
}

.Index .main .content form input,
.Index .main .content form select,
.Index .main .content .wrapper input,
.Index .main .content .wrapper select{
    width: 100%;
    font: var(--body-font);
    padding: 2px 5px;
}

.Index .main .content .wrapper div input{
    height: 100%;
}

.Index .main .content form .submit{
    background: lavender;
    text-align: center;
    color: black;
    border: 1px solid grey;
    padding: 5px;
}

.Index .main .content .viddata div{
    width: 100%;
}

.Index .main .content .viddata div p:hover{
    cursor: pointer;
    color: red;
}

.Index .main .content .viddata b,
.Index .main .content .viddata input,
.Index .main .content .viddata p{
    font: var(--body-font);
    padding: 5px;
    background: lightgrey;
    border: 1px solid grey;
    text-align: center;
}

.Index .main .content .viddata input{
    background: white;
}
```