diff --git a/lib/posts-handler.js b/lib/posts-handler.js
index f03e3c4..2401ad0 100644
--- a/lib/posts-handler.js
+++ b/lib/posts-handler.js
@@ -1,92 +1,111 @@
-'use strict';
-const pug = require('pug');
-const { PrismaClient } = require('@prisma/client');
-const prisma = new PrismaClient();
-const util = require('./handler-util');
+'use strict'
+const pug = require('pug')
+const { PrismaClient } = require('@prisma/client')
+const prisma = new PrismaClient()
+const util = require('./handler-util')
+
+const dayjs = require('dayjs')
+const utc = require('dayjs/plugin/utc')
+const timezone = require('dayjs/plugin/timezone')
+const relativeTime = require('dayjs/plugin/relativeTime')
+require('dayjs/locale/ja')
+dayjs.locale('ja')
+dayjs.extend(utc)
+dayjs.extend(timezone)
+dayjs.extend(relativeTime)
+dayjs.tz.setDefault('Asia/Tokyo')
async function handle(req, res) {
switch (req.method) {
case 'GET':
res.writeHead(200, {
- 'Content-Type': 'text/html; charset=utf-8'
- });
+ 'Content-Type': 'text/html; charset=utf-8',
+ })
const posts = await prisma.post.findMany({
orderBy: {
- id: 'asc'
- }
- });
+ id: 'asc',
+ },
+ })
posts.forEach((post) => {
- post.content = post.content.replace(/\n/g, '
');
- });
- res.end(pug.renderFile('./views/posts.pug', { posts, user: req.user }));
+ post.content = post.content.replace(/\n/g, '
')
+ post.relativeCreatedAt = dayjs(post.createdAt).tz().fromNow()
+ post.formattedCreatedAt = dayjs(post.createdAt)
+ .tz()
+ .format('YYYY年MM月DD日 HH時mm分ss秒')
+ })
+ res.end(pug.renderFile('./views/posts.pug', { posts, user: req.user }))
console.info(
`閲覧されました: user: ${req.user}, ` +
- `remoteAddress: ${req.socket.remoteAddress}, ` +
- `userAgent: ${req.headers['user-agent']} `
- );
- break;
+ `remoteAddress: ${req.socket.remoteAddress}, ` +
+ `userAgent: ${req.headers['user-agent']} `
+ )
+ break
case 'POST':
- let body = '';
- req.on('data', (chunk) => {
- body += chunk;
- }).on('end', async () => {
- const params = new URLSearchParams(body);
- const content = params.get('content');
- console.info(`送信されました: ${content}`);
- await prisma.post.create({
- data: {
- content,
- postedBy: req.user
- }
- });
- handleRedirectPosts(req, res);
- });
- break;
+ let body = ''
+ req
+ .on('data', (chunk) => {
+ body += chunk
+ })
+ .on('end', async () => {
+ const params = new URLSearchParams(body)
+ const content = params.get('content')
+ console.info(`送信されました: ${content}`)
+ await prisma.post.create({
+ data: {
+ content,
+ postedBy: req.user,
+ },
+ })
+ handleRedirectPosts(req, res)
+ })
+ break
default:
- util.handleBadRequest(req, res);
- break;
+ util.handleBadRequest(req, res)
+ break
}
}
function handleRedirectPosts(req, res) {
res.writeHead(303, {
- 'Location': '/posts'
- });
- res.end();
+ Location: '/posts',
+ })
+ res.end()
}
function handleDelete(req, res) {
switch (req.method) {
case 'POST':
- let body = '';
- req.on('data', (chunk) => {
- body += chunk;
- }).on('end', async () => {
- const params = new URLSearchParams(body);
- const id = parseInt(params.get('id'));
- const post = await prisma.post.findUnique({
- where: { id }
- });
- if (req.user === post.postedBy || req.user === 'admin') {
- await prisma.post.delete({
- where: { id }
- });
- console.info(
- `削除されました: user: ${req.user}, ` +
- `remoteAddress: ${req.socket.remoteAddress}, ` +
- `userAgent: ${req.headers['user-agent']} `
- );
- handleRedirectPosts(req, res);
- }
- });
- break;
+ let body = ''
+ req
+ .on('data', (chunk) => {
+ body += chunk
+ })
+ .on('end', async () => {
+ const params = new URLSearchParams(body)
+ const id = parseInt(params.get('id'))
+ const post = await prisma.post.findUnique({
+ where: { id },
+ })
+ if (req.user === post.postedBy || req.user === 'admin') {
+ await prisma.post.delete({
+ where: { id },
+ })
+ console.info(
+ `削除されました: user: ${req.user}, ` +
+ `remoteAddress: ${req.socket.remoteAddress}, ` +
+ `userAgent: ${req.headers['user-agent']} `
+ )
+ handleRedirectPosts(req, res)
+ }
+ })
+ break
default:
- util.handleBadRequest(req, res);
- break;
+ util.handleBadRequest(req, res)
+ break
}
}
module.exports = {
handle,
- handleDelete
-};
\ No newline at end of file
+ handleDelete,
+}
diff --git a/package.json b/package.json
index 9afc3ee..0ad6a7d 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"license": "MIT",
"dependencies": {
"@prisma/client": "4.11.0",
+ "dayjs": "1.11.7",
"http-auth": "4.2.0",
"prisma": "4.11.0",
"pug": "3.0.2"
diff --git a/public/nn-chat.js b/public/nn-chat.js
index 08b7cee..3834113 100644
--- a/public/nn-chat.js
+++ b/public/nn-chat.js
@@ -1,31 +1,39 @@
-'use strict';
+'use strict'
// 一番下を表示
-window.onload = function() {
- window.scrollTo(0,document.body.scrollHeight);
+window.onload = function () {
+ window.scrollTo(0, document.body.scrollHeight)
}
// エンターキー と Ctrlキー(Macの場合はCommandキー)を押していたら送信
-const formElement = document.forms['message-form'];
-const textareaElement = formElement.elements['content'];
+const formElement = document.forms['message-form']
+const textareaElement = formElement.elements['content']
textareaElement.addEventListener('keydown', (event) => {
// 送信キーを押したら
if (isPressedSubmitKey(event)) {
// キーボード入力をキャンセルして送信
- event.preventDefault();
- formElement.submit();
+ event.preventDefault()
+ formElement.submit()
}
-});
+})
// 送信キーを押しているか判定
function isPressedSubmitKey(event) {
if (event.key !== 'Enter') {
- return false;
+ return false
}
if (event.ctrlKey) {
- return true;
+ return true
}
// MacのCommandキーはmetaKeyという名前
if (event.metaKey) {
- return true;
+ return true
}
-}
\ No newline at end of file
+}
+
+// ツールチップの有効化
+const tooltipTriggerElements = document.querySelectorAll(
+ '[data-bs-toggle="tooltip"]'
+)
+tooltipTriggerElements.forEach((tooltipTriggerElement) => {
+ new bootstrap.Tooltip(tooltipTriggerElement)
+})
diff --git a/views/posts.pug b/views/posts.pug
index d9f2b14..386ac77 100644
--- a/views/posts.pug
+++ b/views/posts.pug
@@ -29,7 +29,8 @@ html(lang="ja")
h5.card-title #{post.postedBy}
if post.postedBy === 'admin'
i.bi-patch-check-fill.ms-1
- small.card-text.text-muted.float-end #{post.createdAt}
+ - const tooltipTitle = `${post.formattedCreatedAt}`
+ small.card-text.text-muted.float-end(data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title=tooltipTitle) #{post.relativeCreatedAt}
p.card-text.lead!= post.content
- const isDeletable = (user === post.postedBy || user === 'admin')
if isDeletable