Ability to load archive, remote posts, save archives and code improvements.

This commit is contained in:
pedrocx486 2023-02-12 04:11:26 -03:00
parent eac0aa15eb
commit 14694f7082
4 changed files with 163 additions and 26 deletions

View file

@ -7,10 +7,13 @@ import { generateTimestamp, parseTimestampToUTC, generatePostObj } from './post-
import { getArchive, getPost } from './http-helper.service'; import { getArchive, getPost } from './http-helper.service';
// TODO: // TODO:
// Modal requesting blog install base url. // Ability to delete posts from the archive.
// Fetch archive from install and show posts in a list.
// Allow selection of posts form list and load them. let timestampUpdateInterval: number;
// Save post and archive.
const blogInstallLocation = ref('');
const archive = ref<Archive[]>();
const postTitle = ref(''); const postTitle = ref('');
const postContent = ref(''); const postContent = ref('');
@ -20,21 +23,48 @@ const isDraft = ref(false);
const isEditingExisting = ref(false); const isEditingExisting = ref(false);
let timestampUpdateInterval: number; const onlyLoadArchive = ref(false);
const archive = ref<Archive[]>(); const openLoadModal = (archiveOnly = false): void => {
onlyLoadArchive.value = archiveOnly;
(document.getElementById('dialog') as HTMLDialogElement).showModal();
}
const openLoadModal = (): void => { const closeLoadModal = (): void => {
alert('Not implemented, yet.'); (document.getElementById('dialog') as HTMLDialogElement).close();
}
const reset = (): void => {
archive.value = undefined;
postTitle.value = '';
postContent.value = '';
postTimestamp.value = '';
editedTimestamp.value = '';
isDraft.value = false;
isEditingExisting.value = false;
editedTimestamp.value = '';
clearInterval(timestampUpdateInterval);
timestampUpdateInterval = setInterval(() => {
postTimestamp.value = generateTimestamp();
}, 33);
} }
const loadArchive = (baseUrl: string): void => { const loadArchive = (baseUrl: string): void => {
getArchive(baseUrl).then(res => { getArchive(baseUrl).then(res => {
archive.value = res; archive.value = res;
if (onlyLoadArchive.value) {
onlyLoadArchive.value = false;
closeLoadModal();
}
}); });
} }
const loadPostFromFile = (file: any): void => { const loadPostFromFile = (file: any): void => {
archive.value = undefined;
loadFromFile(file.target.files[0]).then((res: Post) => { loadFromFile(file.target.files[0]).then((res: Post) => {
postTitle.value = res.postTitle; postTitle.value = res.postTitle;
postContent.value = res.postContent; postContent.value = res.postContent;
@ -65,10 +95,12 @@ const loadPost = (baseUrl: string, filename: string): void => {
timestampUpdateInterval = setInterval(() => { timestampUpdateInterval = setInterval(() => {
editedTimestamp.value = generateTimestamp(); editedTimestamp.value = generateTimestamp();
}, 33); }, 33);
closeLoadModal();
}); });
} }
const savePost = (): void => { const savePost = (saveArchive?: boolean): void => {
if (!postTitle.value) { if (!postTitle.value) {
postTitle.value = 'No title.'; postTitle.value = 'No title.';
} }
@ -90,6 +122,17 @@ const savePost = (): void => {
), ),
computedFilename.value computedFilename.value
); );
if (saveArchive) {
if (!archive.value?.filter(post => post.filename === computedFilename.value) && !isDraft) {
archive.value?.push({
postTitle: postTitle.value,
timestamp: postTimestamp.value,
filename: computedFilename.value
});
}
downloadFile(archive.value, 'archive');
}
} }
onMounted(() => { onMounted(() => {
@ -105,6 +148,37 @@ const computedEditedTimestamp = computed(() => parseTimestampToUTC(editedTimesta
<template> <template>
<div class="wrapper"> <div class="wrapper">
<dialog id="dialog">
<div class="dialog-header">
<div v-if="onlyLoadArchive">
<h4>Load Archive...</h4>
</div>
<div v-else>
<h4>Load post from Archive...</h4>
</div>
<div class="close-btn">
<button class="btn-primary" @click="closeLoadModal()">
X
</button>
</div>
</div>
<div v-if="!archive">
Blog Location:
https://
<input type="text" v-model="blogInstallLocation" placeholder="pedrocx486.club/blog" />
/assets/posts/archive.json
<br />
<button class="btn-primary btn-wide" :disabled="!blogInstallLocation" @click="loadArchive(blogInstallLocation)">
Load Archive
</button>
</div>
<div v-for="post in archive" v-if="!onlyLoadArchive">
<button class="btn-secondary btn-wide" @click="loadPost(blogInstallLocation, post.filename)">
{{ post.postTitle }}
</button>
</div>
</dialog>
<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="postTitle" /> <input class="title" type="text" placeholder="Post title..." v-model="postTitle" />
@ -115,17 +189,22 @@ const computedEditedTimestamp = computed(() => parseTimestampToUTC(editedTimesta
</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="postContent" flavor="github" :options="{ emoji: true }" tag="span" /> <VueShowdown :markdown="postContent" flavor="github" :options="{ emoji: true }" tag="span" />
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
Filename: {{ computedFilename }} <br /> Filename: {{ computedFilename }}<span v-if="postTitle">.json</span> <br />
Created on: {{ computedTimestamp }} <br /> Created on: {{ computedTimestamp }} <br />
<span v-if="isEditingExisting">Edited on: {{ computedEditedTimestamp }} <br /></span> <span v-if="isEditingExisting">Edited on: {{ computedEditedTimestamp }} <br /></span>
Is it a draft? {{ isDraft? 'Yes': 'No' }}. <br /> Is it a draft? {{ isDraft? 'Yes': 'No' }}. <br />
<div class="footer-buttons"> <div class="footer-buttons">
<button class="btn-primary" @click="savePost">Save (ngx-retroblog format)</button> &nbsp; <button class="btn-tertiary" @click="reset()">Reset Editor</button> &nbsp;
<button class="btn-primary" @click="openLoadModal">Load Post (from Archive)</button> &nbsp; <br /> <button class="btn-primary" @click="openLoadModal(true)">Load Archive</button> &nbsp;
<button class="btn-primary" @click="openLoadModal()">Load Post (from Archive)</button> &nbsp;
<button class="btn-primary" @click="savePost()">Save Post</button> &nbsp;
<button class="btn-primary" @click="savePost(true)" :disabled="!archive || isDraft">Save Post & Archive</button>
&nbsp;
<br />
<input type="file" accept=".json" id="file-input" @change="loadPostFromFile($event)"> <input type="file" accept=".json" id="file-input" @change="loadPostFromFile($event)">
<label for="file-input">Load Post (from File)</label> <label for="file-input">Load Post (from File)</label>
</div> </div>
@ -153,12 +232,65 @@ button {
background-color: rgb(97, 0, 162); background-color: rgb(97, 0, 162);
color: white; color: white;
font-size: .8rem; font-size: .8rem;
margin-top: 0.5rem;
}
.btn-primary[disabled] {
background-color: rgb(144, 144, 144);
color: rgb(75, 75, 75);
font-size: .8rem;
} }
.btn-primary:hover { .btn-primary:hover {
box-shadow: 0px 0px 7px rgb(97, 0, 162); box-shadow: 0px 0px 7px rgb(97, 0, 162);
} }
.btn-secondary {
background-color: rgb(0, 241, 221);
color: rgb(78, 78, 78);
font-size: .8rem;
}
.btn-secondary:hover {
box-shadow: 0px 0px 7px rgb(0, 241, 221);
}
.btn-tertiary {
background-color: rgb(241, 8, 0);
color: rgb(233, 233, 233);
font-size: .8rem;
}
.btn-tertiary:hover {
box-shadow: 0px 0px 7px rgb(241, 8, 0);
}
.close-btn {
text-align: end;
margin-bottom: 0.5rem;
}
.btn-wide {
min-width: -webkit-fill-available;
margin-top: 0.5rem;
}
#dialog {
max-width: 90vw;
}
.dialog-header {
display: flex;
justify-content: space-between;
}
.dialog-header>* h4 {
padding: 0;
margin: 0;
margin-top: .5rem;
}
.wrapper { .wrapper {
display: flex; display: flex;
grid-gap: 1rem; grid-gap: 1rem;
@ -213,6 +345,7 @@ button {
.footer-buttons { .footer-buttons {
text-align: center; text-align: center;
padding-bottom: 3rem;
} }
#file-input { #file-input {
@ -236,10 +369,11 @@ button {
background-color: rgb(97, 0, 162); background-color: rgb(97, 0, 162);
position: absolute; position: absolute;
font-size: 0.8rem; font-size: 0.8rem;
margin-top: 0.3rem; margin-top: 0.5rem;
width: 20.7rem; width: 20.7rem;
text-align: center; text-align: center;
margin-left: -11.2rem; margin-left: -11.2rem;
margin-bottom: 1rem;
} }
#file-input:focus+label, #file-input:focus+label,

View file

@ -21,13 +21,13 @@ export const parseFilename = (titleToFilename: string): string => {
} }
} }
return titleToFilename + '.json'; return titleToFilename;
} }
export const downloadFile = (postObj: any, fileName: string, archiveFile?: boolean): void => { export const downloadFile = (dataPbj: any, fileName: string): void => {
const blob = new Blob([JSON.stringify(postObj, null, 2)], { type: 'text/plain;charset=utf-8;' }); const blob = new Blob([JSON.stringify(dataPbj, null, 2)], { type: 'text/plain;charset=utf-8;' });
const a = document.createElement('a'); const a = document.createElement('a');
a.setAttribute('download', archiveFile ? 'archive.json' : fileName); a.setAttribute('download', `${fileName}.json`);
a.setAttribute('href', window.URL.createObjectURL(blob)); a.setAttribute('href', window.URL.createObjectURL(blob));
a.click(); a.click();
} }

View file

@ -17,15 +17,18 @@ httpService.interceptors.response.use(
}, },
); );
export async function getArchive(baseUrl: string): Promise<Archive[]> {
export async function getArchive(baseUrl: string) { return await httpService.get<Archive[]>(`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;
}).catch(err => {
alert(`Failed to load Archive! We've got a ${err.message}!\nAre you connected to the internet or is the location correct?`)
}); });
} }
export async function getPost(baseUrl: string, post: string) { export async function getPost(baseUrl: string, post: string): Promise<Post> {
return await httpService.get<Post>(`http://${baseUrl}/assets/posts/${post}.json`).then((res) => { return await httpService.get<Post>(`https://${baseUrl}/assets/posts/${post}.json`).then((res: any) => {
return res.data; return res.data;
}).catch(err => {
alert(`Failed to load Post! We've got a ${err.message}!\nAre you connected to the internet?`)
}); });
} }

View file

@ -7,8 +7,8 @@ export type Post = {
draft: boolean draft: boolean
} }
export type Archive = { export type Archive = {
postTitle: string, postTitle: string,
timestamp: string, timestamp: string,
filename: string filename: string
} }