Post editing (from file!)
This commit is contained in:
parent
ccce6a3e20
commit
6ce1bb2a48
3 changed files with 129 additions and 19 deletions
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
88
src/App.vue
88
src/App.vue
|
@ -1,11 +1,17 @@
|
||||||
<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 { VueShowdown } from 'vue-showdown';
|
||||||
import { downloadFile, parseFilename } from './file-manager.service';
|
import { downloadFile, parseFilename, loadFromFile } from './file-manager.service';
|
||||||
import { Archive } from './types';
|
import { Archive, Post } from './types';
|
||||||
import { generateTimestamp, parseTimestampToUTC, generatePostObj } from './post-utilities.service';
|
import { generateTimestamp, parseTimestampToUTC, generatePostObj } from './post-utilities.service';
|
||||||
import { getArchive, getPost } from './http-helper.service';
|
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.
|
||||||
|
|
||||||
const postTitle = ref('');
|
const postTitle = ref('');
|
||||||
const postContent = ref('');
|
const postContent = ref('');
|
||||||
const postTimestamp = ref('');
|
const postTimestamp = ref('');
|
||||||
|
@ -18,12 +24,33 @@ let timestampUpdateInterval: number;
|
||||||
|
|
||||||
const archive = ref<Archive[]>();
|
const archive = ref<Archive[]>();
|
||||||
|
|
||||||
const loadArchive = (baseUrl: string) => {
|
const openLoadModal = (): void => {
|
||||||
|
alert('Not implemented, yet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadArchive = (baseUrl: string): void => {
|
||||||
getArchive(baseUrl).then(res => {
|
getArchive(baseUrl).then(res => {
|
||||||
archive.value = res;
|
archive.value = res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadPostFromFile = (file: any): void => {
|
||||||
|
loadFromFile(file.target.files[0]).then((res: Post) => {
|
||||||
|
postTitle.value = res.postTitle;
|
||||||
|
postContent.value = res.postContent;
|
||||||
|
postTimestamp.value = res.timestamp;
|
||||||
|
isDraft.value = res.draft;
|
||||||
|
|
||||||
|
isEditingExisting.value = true;
|
||||||
|
|
||||||
|
clearInterval(timestampUpdateInterval);
|
||||||
|
|
||||||
|
timestampUpdateInterval = setInterval(() => {
|
||||||
|
editedTimestamp.value = generateTimestamp();
|
||||||
|
}, 33);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const loadPost = (baseUrl: string, filename: string): void => {
|
const loadPost = (baseUrl: string, filename: string): void => {
|
||||||
getPost(baseUrl, filename).then(res => {
|
getPost(baseUrl, filename).then(res => {
|
||||||
postTitle.value = res.postTitle;
|
postTitle.value = res.postTitle;
|
||||||
|
@ -69,9 +96,6 @@ onMounted(() => {
|
||||||
timestampUpdateInterval = setInterval(() => {
|
timestampUpdateInterval = setInterval(() => {
|
||||||
postTimestamp.value = generateTimestamp();
|
postTimestamp.value = generateTimestamp();
|
||||||
}, 33);
|
}, 33);
|
||||||
|
|
||||||
// For debugging.
|
|
||||||
// loadPost('localhost:3000', 'hello-world');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const computedFilename = computed(() => parseFilename(postTitle.value))
|
const computedFilename = computed(() => parseFilename(postTitle.value))
|
||||||
|
@ -99,7 +123,12 @@ const computedEditedTimestamp = computed(() => parseTimestampToUTC(editedTimesta
|
||||||
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 />
|
||||||
<button class="btn-primary" @click="savePost">Save (ngx-retroblog format)</button>
|
<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 />
|
||||||
|
<input type="file" accept=".json" id="file-input" @change="loadPostFromFile($event)">
|
||||||
|
<label for="file-input">Load Post (from File)</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -173,15 +202,56 @@ button {
|
||||||
border-bottom: solid 1px;
|
border-bottom: solid 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview > * {
|
.preview>* {
|
||||||
max-width: 50vw;
|
max-width: 50vw;
|
||||||
line-break: normal;
|
line-break: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview > * p {
|
.preview>* p {
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-buttons {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-input {
|
||||||
|
width: 0.1px;
|
||||||
|
height: 0.1px;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-input+label {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.55rem;
|
||||||
|
border: none;
|
||||||
|
box-shadow: #000a 4px 4px;
|
||||||
|
color: white;
|
||||||
|
background-color: rgb(97, 0, 162);
|
||||||
|
position: absolute;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
width: 20.7rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: -11.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-input:focus+label,
|
||||||
|
#file-input+label:hover {
|
||||||
|
box-shadow: 0px 0px 7px rgb(97, 0, 162);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (hover: none) {
|
@media only screen and (hover: none) {
|
||||||
.wrapper {
|
.wrapper {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Post } from "./types";
|
||||||
|
|
||||||
export const parseFilename = (titleToFilename: string): string => {
|
export const parseFilename = (titleToFilename: string): string => {
|
||||||
if (!titleToFilename) {
|
if (!titleToFilename) {
|
||||||
return 'None yet.'
|
return 'None yet.'
|
||||||
|
@ -9,11 +11,13 @@ export const parseFilename = (titleToFilename: string): string => {
|
||||||
titleToFilename = titleToFilename.slice(0, -1);
|
titleToFilename = titleToFilename.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (titleToFilename.length > 50) { // Limit filename size
|
// Limit filename size
|
||||||
|
if (titleToFilename.length > 50) {
|
||||||
titleToFilename = titleToFilename.substring(0, 50);
|
titleToFilename = titleToFilename.substring(0, 50);
|
||||||
|
|
||||||
if (titleToFilename.includes('-')) {
|
if (titleToFilename.includes('-')) {
|
||||||
titleToFilename = titleToFilename.substring(0, Math.min(titleToFilename.length, titleToFilename.lastIndexOf('-'))); // Re-trim to avoid cutting a word in half.
|
// Re-trim to avoid cutting a word in half.
|
||||||
|
titleToFilename = titleToFilename.substring(0, Math.min(titleToFilename.length, titleToFilename.lastIndexOf('-')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,4 +30,48 @@ export const downloadFile = (postObj: any, fileName: string, archiveFile?: boole
|
||||||
a.setAttribute('download', archiveFile ? 'archive.json' : fileName);
|
a.setAttribute('download', archiveFile ? 'archive.json' : fileName);
|
||||||
a.setAttribute('href', window.URL.createObjectURL(blob));
|
a.setAttribute('href', window.URL.createObjectURL(blob));
|
||||||
a.click();
|
a.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Bad code ahead. Sail with caution!
|
||||||
|
export const loadFromFile = (file: any): Promise<Post> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
reject('No file!');
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.onload = (event => {
|
||||||
|
const contents: any = (event.target as FileReader).result;
|
||||||
|
|
||||||
|
// Yes, this is extremely hacky.
|
||||||
|
const objCompare: Post = {
|
||||||
|
postTitle: "",
|
||||||
|
timestamp: "",
|
||||||
|
editedTimestamp: "",
|
||||||
|
postContent: "",
|
||||||
|
filename: "",
|
||||||
|
draft: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// And yes, I know this is crazy but it works.
|
||||||
|
try {
|
||||||
|
if (Object.keys(JSON.parse(contents.toString())).toString() === Object.keys(objCompare).toString()) {
|
||||||
|
resolve(JSON.parse(contents.toString()));
|
||||||
|
} else {
|
||||||
|
alert('Invalid file! Are you sure it\'s an Dumblog compatible JSON?');
|
||||||
|
reject('Invalid file!');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Error loading file! Check the console for more info.');
|
||||||
|
console.log(e);
|
||||||
|
reject('Error loading file!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.onerror = reject;
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
}
|
}
|
Loading…
Reference in a new issue