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">
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 content = ref('');
const timestamp = ref('');
const editedTimestamp = ref('');
const draft = ref(false);
const postTitle = ref('');
const postContent = ref('');
const postTimestamp = ref('');
const editedTimestamp = ref('');
const isDraft = ref(false);
const archive = ref([{ postTitle: '', timestamp: '', filename: '' }])
const post = ref({
postTitle: "",
timestamp: "",
editedTimestamp: "",
postContent: "",
filename: "",
draft: ""
});
const isEditingExisting = ref(false);
const generateTimestamp = (): string => {
return Math.round((new Date()).getTime() / 1000).toString();
let timestampUpdateInterval: number;
const archive = ref<Archive[]>();
const loadArchive = (baseUrl: string) => {
getArchive(baseUrl).then(res => {
archive.value = res;
});
}
const parseTimestamp = (unixTimestamp: string): string => {
if (!unixTimestamp) {
return ''
}
const loadPost = (baseUrl: string, filename: string): void => {
getPost(baseUrl, filename).then(res => {
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 => {
if (!titleToFilename) {
return 'None yet.'
}
clearInterval(timestampUpdateInterval);
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';
}
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();
timestampUpdateInterval = setInterval(() => {
editedTimestamp.value = generateTimestamp();
}, 33);
});
}
const savePost = (): void => {
if (!title.value) {
title.value = 'No title.';
if (!postTitle.value) {
postTitle.value = 'No title.';
}
if (!content.value) {
content.value = 'No content.';
if (!postContent.value) {
postContent.value = 'No content.';
}
if (draft.value) { // Save post as draft (no timestamps)
downloadFile(true);
} else { // Save post for publishing
// Reminder for when we have an editedTimestamp. Requires way more than this and I have no patience right now.
// if (timestamp.value) {
// editedTimestamp.value = generateTimestamp();
// }
downloadFile();
// Reminder when we get to the point of saving archives.
//saveAs(new Blob([JSON.stringify(this.archives, null, 2)], { type: 'text/plain;charset=utf-8;' }), 'archive.json');
}
// If draft is true, it'll have no timestamps.
downloadFile(
generatePostObj(
postTitle.value,
postTimestamp.value,
postContent.value,
computedFilename.value,
isDraft.value,
isEditingExisting.value,
editedTimestamp.value
),
computedFilename.value
);
}
onMounted(() => {
// Refactor this later when we have an editedTimestamp.
setInterval(() => {
timestamp.value = generateTimestamp();
}, 1000)
timestampUpdateInterval = setInterval(() => {
postTimestamp.value = generateTimestamp();
}, 33);
// For debugging.
// loadPost('localhost:3000', 'hello-world');
});
const computedFilename = computed(() => parseFilename(title.value))
const computedTimestamp = computed(() => parseTimestamp(timestamp.value));
const computedEditedTimestamp = computed(() => parseTimestamp(timestamp.value));
const computedFilename = computed(() => parseFilename(postTitle.value))
const computedTimestamp = computed(() => parseTimestampToUTC(postTimestamp.value));
const computedEditedTimestamp = computed(() => parseTimestampToUTC(editedTimestamp.value));
</script>
<template>
<div class="wrapper">
<div class="editor-area">
<div class="title-area">
<input class="title" type="text" placeholder="Post title..." v-model="title" />
<input type="checkbox" id="draft" v-model="draft" /><label for="draft">Draft</label>
<input class="title" type="text" placeholder="Post title..." v-model="postTitle" />
<input type="checkbox" id="draft" v-model="isDraft" /><label for="draft">Draft</label>
</div>
<br>
<textarea class="editor" v-model="content" placeholder="Post content..." />
<textarea class="editor" v-model="postContent" placeholder="Post content..." />
</div>
<div class="preview">
<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 class="footer">
Filename: {{ computedFilename }} <br />
Created on Timestamp: {{ computedTimestamp }} <br />
<!-- Edited on Timestamp: {{ computedEditedTimestamp }} <br /> -->
Is it a draft? {{ draft? 'Yes': 'No' }}. <br />
Created on: {{ computedTimestamp }} <br />
<span v-if="isEditingExisting">Edited on: {{ computedEditedTimestamp }} <br /></span>
Is it a draft? {{ isDraft? 'Yes': 'No' }}. <br />
<button class="btn-primary" @click="savePost">Save (ngx-retroblog format)</button>
</div>
</template>
@ -167,6 +138,7 @@ button {
width: 100%;
height: 85vh;
position: relative;
margin-bottom: -1.3rem;
}
.editor-area {
@ -198,16 +170,22 @@ button {
.preview-title {
margin-top: 0;
border-bottom: solid 1px;
}
.preview>* {
.preview > * {
max-width: 50vw;
line-break: normal;
}
.preview > * p {
padding: 0.2rem;
}
@media only screen and (hover: none) {
.wrapper {
flex-direction: column;
margin-bottom: unset !important;
}
.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 { Archive, Post } from './types';
const httpService = axios.create({
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Expires': '0',
@ -20,13 +19,13 @@ httpService.interceptors.response.use(
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;
});
}
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;
});
}

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,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"skipLibCheck": true
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]