Cleanup logic, rewrite some of it and more.
This commit is contained in:
parent
274a4e09ff
commit
ccce6a3e20
7 changed files with 158 additions and 99 deletions
8
public/assets/posts/hello-world.json
Normal file
8
public/assets/posts/hello-world.json
Normal 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
|
||||
}
|
164
src/App.vue
164
src/App.vue
|
@ -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 postTitle = ref('');
|
||||
const postContent = ref('');
|
||||
const postTimestamp = ref('');
|
||||
const editedTimestamp = ref('');
|
||||
const draft = ref(false);
|
||||
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"> 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 {
|
||||
|
|
29
src/file-manager.service.ts
Normal file
29
src/file-manager.service.ts
Normal 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();
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
30
src/post-utilities.service.ts
Normal file
30
src/post-utilities.service.ts
Normal 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
14
src/types.ts
Normal 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
|
||||
}
|
|
@ -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" }]
|
||||
|
|
Loading…
Reference in a new issue