GitHub: https://github.com/Sokhavuth/deno-fresh
Deno Deploy: https://khmerweb-fresh.deno.dev/login
// 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 } />
)
}
// 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();
// 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} />
)
}
// 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">© <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} />
)
}
// 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>
)
}
// 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)
}
// 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)
}
}
/* 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;
}
Top comments (0)