Ability to load archive, remote posts, save archives and code improvements.
This commit is contained in:
parent
eac0aa15eb
commit
14694f7082
4 changed files with 163 additions and 26 deletions
162
src/App.vue
162
src/App.vue
|
@ -7,10 +7,13 @@ import { generateTimestamp, parseTimestampToUTC, generatePostObj } from './post-
|
|||
import { getArchive, getPost } from './http-helper.service';
|
||||
|
||||
// TODO:
|
||||
// Modal requesting blog install base url.
|
||||
// Fetch archive from install and show posts in a list.
|
||||
// Allow selection of posts form list and load them.
|
||||
// Save post and archive.
|
||||
// Ability to delete posts from the archive.
|
||||
|
||||
let timestampUpdateInterval: number;
|
||||
|
||||
const blogInstallLocation = ref('');
|
||||
|
||||
const archive = ref<Archive[]>();
|
||||
|
||||
const postTitle = ref('');
|
||||
const postContent = ref('');
|
||||
|
@ -20,21 +23,48 @@ const isDraft = 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 => {
|
||||
alert('Not implemented, yet.');
|
||||
const closeLoadModal = (): void => {
|
||||
(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 => {
|
||||
getArchive(baseUrl).then(res => {
|
||||
archive.value = res;
|
||||
|
||||
if (onlyLoadArchive.value) {
|
||||
onlyLoadArchive.value = false;
|
||||
closeLoadModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const loadPostFromFile = (file: any): void => {
|
||||
archive.value = undefined;
|
||||
|
||||
loadFromFile(file.target.files[0]).then((res: Post) => {
|
||||
postTitle.value = res.postTitle;
|
||||
postContent.value = res.postContent;
|
||||
|
@ -65,10 +95,12 @@ const loadPost = (baseUrl: string, filename: string): void => {
|
|||
timestampUpdateInterval = setInterval(() => {
|
||||
editedTimestamp.value = generateTimestamp();
|
||||
}, 33);
|
||||
|
||||
closeLoadModal();
|
||||
});
|
||||
}
|
||||
|
||||
const savePost = (): void => {
|
||||
const savePost = (saveArchive?: boolean): void => {
|
||||
if (!postTitle.value) {
|
||||
postTitle.value = 'No title.';
|
||||
}
|
||||
|
@ -90,6 +122,17 @@ const savePost = (): void => {
|
|||
),
|
||||
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(() => {
|
||||
|
@ -105,6 +148,37 @@ const computedEditedTimestamp = computed(() => parseTimestampToUTC(editedTimesta
|
|||
|
||||
<template>
|
||||
<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="title-area">
|
||||
<input class="title" type="text" placeholder="Post title..." v-model="postTitle" />
|
||||
|
@ -115,17 +189,22 @@ const computedEditedTimestamp = computed(() => parseTimestampToUTC(editedTimesta
|
|||
</div>
|
||||
<div class="preview">
|
||||
<p class="preview-title"> 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 class="footer">
|
||||
Filename: {{ computedFilename }} <br />
|
||||
Filename: {{ computedFilename }}<span v-if="postTitle">.json</span> <br />
|
||||
Created on: {{ computedTimestamp }} <br />
|
||||
<span v-if="isEditingExisting">Edited on: {{ computedEditedTimestamp }} <br /></span>
|
||||
Is it a draft? {{ isDraft? 'Yes': 'No' }}. <br />
|
||||
<div class="footer-buttons">
|
||||
<button class="btn-primary" @click="savePost">Save (ngx-retroblog format)</button>
|
||||
<button class="btn-primary" @click="openLoadModal">Load Post (from Archive)</button> <br />
|
||||
<button class="btn-tertiary" @click="reset()">Reset Editor</button>
|
||||
<button class="btn-primary" @click="openLoadModal(true)">Load Archive</button>
|
||||
<button class="btn-primary" @click="openLoadModal()">Load Post (from Archive)</button>
|
||||
<button class="btn-primary" @click="savePost()">Save Post</button>
|
||||
<button class="btn-primary" @click="savePost(true)" :disabled="!archive || isDraft">Save Post & Archive</button>
|
||||
|
||||
<br />
|
||||
<input type="file" accept=".json" id="file-input" @change="loadPostFromFile($event)">
|
||||
<label for="file-input">Load Post (from File)</label>
|
||||
</div>
|
||||
|
@ -153,12 +232,65 @@ button {
|
|||
background-color: rgb(97, 0, 162);
|
||||
color: white;
|
||||
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 {
|
||||
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 {
|
||||
display: flex;
|
||||
grid-gap: 1rem;
|
||||
|
@ -213,6 +345,7 @@ button {
|
|||
|
||||
.footer-buttons {
|
||||
text-align: center;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
#file-input {
|
||||
|
@ -236,10 +369,11 @@ button {
|
|||
background-color: rgb(97, 0, 162);
|
||||
position: absolute;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 0.3rem;
|
||||
margin-top: 0.5rem;
|
||||
width: 20.7rem;
|
||||
text-align: center;
|
||||
margin-left: -11.2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#file-input:focus+label,
|
||||
|
|
|
@ -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 => {
|
||||
const blob = new Blob([JSON.stringify(postObj, null, 2)], { type: 'text/plain;charset=utf-8;' });
|
||||
export const downloadFile = (dataPbj: any, fileName: string): void => {
|
||||
const blob = new Blob([JSON.stringify(dataPbj, null, 2)], { type: 'text/plain;charset=utf-8;' });
|
||||
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.click();
|
||||
}
|
||||
|
|
|
@ -17,15 +17,18 @@ httpService.interceptors.response.use(
|
|||
},
|
||||
);
|
||||
|
||||
|
||||
export async function getArchive(baseUrl: string) {
|
||||
return await httpService.get<Archive[]>(`https://${baseUrl}/assets/posts/archive.json`).then((res) => {
|
||||
export async function getArchive(baseUrl: string): Promise<Archive[]> {
|
||||
return await httpService.get<Archive[]>(`https://${baseUrl}/assets/posts/archive.json`).then((res: any) => {
|
||||
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) {
|
||||
return await httpService.get<Post>(`http://${baseUrl}/assets/posts/${post}.json`).then((res) => {
|
||||
export async function getPost(baseUrl: string, post: string): Promise<Post> {
|
||||
return await httpService.get<Post>(`https://${baseUrl}/assets/posts/${post}.json`).then((res: any) => {
|
||||
return res.data;
|
||||
}).catch(err => {
|
||||
alert(`Failed to load Post! We've got a ${err.message}!\nAre you connected to the internet?`)
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue