Cleanup logic, rewrite some of it and more.

This commit is contained in:
pedrocx486 2023-02-11 16:39:48 -03:00
parent 274a4e09ff
commit ccce6a3e20
7 changed files with 158 additions and 99 deletions

View File

@ -0,0 +1,8 @@
{
"postTitle": "Hello World!",
"timestamp": "1654062776",
"editedTimestamp": "",
"postContent": "Welcome to my humble blog! This blog is hosted using a blog platform loosely based on my [ngx-dumblog](http:\/\/github.com\/pedroCX486\/ngx-dumblog) project. My intent is to make this look more and more retro with time.\r\n \r\nAnd before you ask, it's availabe [here](http:\/\/git.pedrocx486.club\/pedrocx486\/ngx-retroblog).\r\n \r\nFor more info (maybe), check [The Club](https:\/\/pedrocx486.club)!",
"filename": "hello-world",
"draft": false
}

View File

@ -1,133 +1,104 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { VueShowdown } from 'vue-showdown';
import { downloadFile, parseFilename } from './file-manager.service';
import { Archive } from './types';
import { generateTimestamp, parseTimestampToUTC, generatePostObj } from './post-utilities.service';
import { getArchive, getPost } from './http-helper.service';
const title = ref(''); const postTitle = ref('');
const content = ref(''); const postContent = ref('');
const timestamp = ref(''); const postTimestamp = ref('');
const editedTimestamp = ref(''); const editedTimestamp = ref('');
const draft = ref(false); const isDraft = ref(false);
const archive = ref([{ postTitle: '', timestamp: '', filename: '' }]) const isEditingExisting = ref(false);
const post = ref({
postTitle: "",
timestamp: "",
editedTimestamp: "",
postContent: "",
filename: "",
draft: ""
});
const generateTimestamp = (): string => { let timestampUpdateInterval: number;
return Math.round((new Date()).getTime() / 1000).toString();
const archive = ref<Archive[]>();
const loadArchive = (baseUrl: string) => {
getArchive(baseUrl).then(res => {
archive.value = res;
});
} }
const parseTimestamp = (unixTimestamp: string): string => { const loadPost = (baseUrl: string, filename: string): void => {
if (!unixTimestamp) { getPost(baseUrl, filename).then(res => {
return '' postTitle.value = res.postTitle;
} postContent.value = res.postContent;
postTimestamp.value = res.timestamp;
isDraft.value = res.draft;
return new Date(Number(unixTimestamp) * 1000).toUTCString(); isEditingExisting.value = true;
}
const parseFilename = (titleToFilename: string): string => { clearInterval(timestampUpdateInterval);
if (!titleToFilename) {
return 'None yet.'
}
titleToFilename = titleToFilename.replace(/[^a-zA-Z0-9_]+/gi, '-').toLowerCase(); timestampUpdateInterval = setInterval(() => {
editedTimestamp.value = generateTimestamp();
while (titleToFilename.endsWith('-')) { }, 33);
titleToFilename = titleToFilename.slice(0, -1); });
}
if (titleToFilename.length > 50) { // Limit filename size
titleToFilename = titleToFilename.substring(0, 50);
if (titleToFilename.includes('-')) {
titleToFilename = titleToFilename.substring(0, Math.min(titleToFilename.length, titleToFilename.lastIndexOf('-'))); // Re-trim to avoid cutting a word in half.
}
}
return titleToFilename + '.json';
}
const generatePostObj = (isDraft?: boolean): Object => {
return {
postTitle: title.value,
timestamp: isDraft ? '' : timestamp.value,
//editedTimestamp: isDraft ? '' : editedTimestamp.value,
postContent: content.value,
filename: computedFilename.value,
draft: draft.value
}
}
const downloadFile = (isDraft?: boolean, archiveFile?: boolean): void => {
const blob = new Blob([JSON.stringify(generatePostObj(isDraft), null, 2)], { type: 'text/plain' });
const a = document.createElement('a');
a.setAttribute('download', archiveFile ? 'archive.json' : computedFilename.value);
a.setAttribute('href', window.URL.createObjectURL(blob));
a.click();
} }
const savePost = (): void => { const savePost = (): void => {
if (!title.value) { if (!postTitle.value) {
title.value = 'No title.'; postTitle.value = 'No title.';
} }
if (!content.value) { if (!postContent.value) {
content.value = 'No content.'; postContent.value = 'No content.';
} }
if (draft.value) { // Save post as draft (no timestamps) // If draft is true, it'll have no timestamps.
downloadFile(true); downloadFile(
} else { // Save post for publishing generatePostObj(
postTitle.value,
// Reminder for when we have an editedTimestamp. Requires way more than this and I have no patience right now. postTimestamp.value,
// if (timestamp.value) { postContent.value,
// editedTimestamp.value = generateTimestamp(); computedFilename.value,
// } isDraft.value,
isEditingExisting.value,
downloadFile(); editedTimestamp.value
),
// Reminder when we get to the point of saving archives. computedFilename.value
//saveAs(new Blob([JSON.stringify(this.archives, null, 2)], { type: 'text/plain;charset=utf-8;' }), 'archive.json'); );
}
} }
onMounted(() => { onMounted(() => {
// Refactor this later when we have an editedTimestamp. timestampUpdateInterval = setInterval(() => {
setInterval(() => { postTimestamp.value = generateTimestamp();
timestamp.value = generateTimestamp(); }, 33);
}, 1000)
// For debugging.
// loadPost('localhost:3000', 'hello-world');
}); });
const computedFilename = computed(() => parseFilename(title.value)) const computedFilename = computed(() => parseFilename(postTitle.value))
const computedTimestamp = computed(() => parseTimestamp(timestamp.value)); const computedTimestamp = computed(() => parseTimestampToUTC(postTimestamp.value));
const computedEditedTimestamp = computed(() => parseTimestamp(timestamp.value)); const computedEditedTimestamp = computed(() => parseTimestampToUTC(editedTimestamp.value));
</script> </script>
<template> <template>
<div class="wrapper"> <div class="wrapper">
<div class="editor-area"> <div class="editor-area">
<div class="title-area"> <div class="title-area">
<input class="title" type="text" placeholder="Post title..." v-model="title" /> <input class="title" type="text" placeholder="Post title..." v-model="postTitle" />
<input type="checkbox" id="draft" v-model="draft" /><label for="draft">Draft</label> <input type="checkbox" id="draft" v-model="isDraft" /><label for="draft">Draft</label>
</div> </div>
<br> <br>
<textarea class="editor" v-model="content" placeholder="Post content..." /> <textarea class="editor" v-model="postContent" placeholder="Post content..." />
</div> </div>
<div class="preview"> <div class="preview">
<p class="preview-title">&nbsp; Preview: </p> <p class="preview-title">&nbsp; Preview: </p>
<VueShowdown v-bind:markdown="content" flavor="github" :options="{ emoji: true }" tag="span" /> <VueShowdown v-bind:markdown="postContent" flavor="github" :options="{ emoji: true }" tag="span" />
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
Filename: {{ computedFilename }} <br /> Filename: {{ computedFilename }} <br />
Created on Timestamp: {{ computedTimestamp }} <br /> Created on: {{ computedTimestamp }} <br />
<!-- Edited on Timestamp: {{ computedEditedTimestamp }} <br /> --> <span v-if="isEditingExisting">Edited on: {{ computedEditedTimestamp }} <br /></span>
Is it a draft? {{ draft? 'Yes': 'No' }}. <br /> Is it a draft? {{ isDraft? 'Yes': 'No' }}. <br />
<button class="btn-primary" @click="savePost">Save (ngx-retroblog format)</button> <button class="btn-primary" @click="savePost">Save (ngx-retroblog format)</button>
</div> </div>
</template> </template>
@ -167,6 +138,7 @@ button {
width: 100%; width: 100%;
height: 85vh; height: 85vh;
position: relative; position: relative;
margin-bottom: -1.3rem;
} }
.editor-area { .editor-area {
@ -198,16 +170,22 @@ button {
.preview-title { .preview-title {
margin-top: 0; margin-top: 0;
border-bottom: solid 1px;
} }
.preview>* { .preview > * {
max-width: 50vw; max-width: 50vw;
line-break: normal; line-break: normal;
} }
.preview > * p {
padding: 0.2rem;
}
@media only screen and (hover: none) { @media only screen and (hover: none) {
.wrapper { .wrapper {
flex-direction: column; flex-direction: column;
margin-bottom: unset !important;
} }
.preview { .preview {

View File

@ -0,0 +1,29 @@
export const parseFilename = (titleToFilename: string): string => {
if (!titleToFilename) {
return 'None yet.'
}
titleToFilename = titleToFilename.replace(/[^a-zA-Z0-9_]+/gi, '-').toLowerCase();
while (titleToFilename.endsWith('-')) {
titleToFilename = titleToFilename.slice(0, -1);
}
if (titleToFilename.length > 50) { // Limit filename size
titleToFilename = titleToFilename.substring(0, 50);
if (titleToFilename.includes('-')) {
titleToFilename = titleToFilename.substring(0, Math.min(titleToFilename.length, titleToFilename.lastIndexOf('-'))); // Re-trim to avoid cutting a word in half.
}
}
return titleToFilename + '.json';
}
export const downloadFile = (postObj: any, fileName: string, archiveFile?: boolean): void => {
const blob = new Blob([JSON.stringify(postObj, null, 2)], { type: 'text/plain;charset=utf-8;' });
const a = document.createElement('a');
a.setAttribute('download', archiveFile ? 'archive.json' : fileName);
a.setAttribute('href', window.URL.createObjectURL(blob));
a.click();
}

View File

@ -1,9 +1,8 @@
import axios from 'axios'; import axios from 'axios';
import { Archive, Post } from './types';
const httpService = axios.create({ const httpService = axios.create({
headers: { headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
'Pragma': 'no-cache', 'Pragma': 'no-cache',
'Expires': '0', 'Expires': '0',
@ -20,13 +19,13 @@ httpService.interceptors.response.use(
export async function getArchive(baseUrl: string) { export async function getArchive(baseUrl: string) {
return await httpService.get(`https://${baseUrl}/assets/posts/archive.json`).then((res: any) => { return await httpService.get<Archive[]>(`https://${baseUrl}/assets/posts/archive.json`).then((res) => {
return res.data; return res.data;
}); });
} }
export async function getPost(baseUrl: string, post: string) { export async function getPost(baseUrl: string, post: string) {
return await httpService.get(`https://${baseUrl}/assets/posts/${post}.json`).then((res: any) => { return await httpService.get<Post>(`http://${baseUrl}/assets/posts/${post}.json`).then((res) => {
return res.data; return res.data;
}); });
} }

View File

@ -0,0 +1,30 @@
export const generateTimestamp = (): string => {
return Math.round((new Date()).getTime() / 1000).toString();
}
export const parseTimestampToUTC = (unixTimestamp: string): string => {
if (!unixTimestamp) {
return ''
}
return new Date(Number(unixTimestamp) * 1000).toUTCString();
}
export const generatePostObj = (
postTitle: string,
timestamp: string,
postContent: string,
filename: string,
draft: boolean,
isEdit: boolean,
editedTimestamp: string
): Object => {
return {
postTitle,
timestamp: draft ? '' : timestamp,
editedTimestamp: draft ? '' : (isEdit ? editedTimestamp : ''),
postContent,
filename,
draft
}
}

14
src/types.ts Normal file
View File

@ -0,0 +1,14 @@
export type Post = {
postTitle: string,
timestamp: string,
editedTimestamp: string,
postContent: string,
filename: string,
draft: boolean
}
export type Archive = {
postTitle: string,
timestamp: string,
filename: string
}

View File

@ -11,7 +11,8 @@
"isolatedModules": true, "isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext", "dom"], "lib": ["esnext", "dom"],
"skipLibCheck": true "skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]