488 lines
13 KiB
Vue
488 lines
13 KiB
Vue
<template>
|
|
<div>
|
|
<div class="row justify-content-center">
|
|
<div class="col-9" style="max-width: 700px">
|
|
<div class="mb-3">
|
|
<div class="form-floating">
|
|
<input
|
|
v-model="post.title"
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="Post title"
|
|
/>
|
|
<label>Write a SEO post title</label>
|
|
</div>
|
|
<small>
|
|
<span class="text-secondary">{{ getPostFullUrl }}</span>
|
|
</small>
|
|
</div>
|
|
|
|
<div class="form-floating mb-3">
|
|
<textarea
|
|
v-model="post.excerpt"
|
|
class="form-control"
|
|
style="min-height: 150px"
|
|
placeholder="Enter a post excerpt/summary"
|
|
></textarea>
|
|
<label
|
|
>Write a simple excerpt to convince & entice users to view this
|
|
post!</label
|
|
>
|
|
</div>
|
|
|
|
<native-image-block
|
|
ref="imageBlock"
|
|
class="mb-3"
|
|
:input-image="post.featured_image"
|
|
@saved="imageSaved"
|
|
></native-image-block>
|
|
|
|
<div v-if="showEditorJs" class="card">
|
|
<div class="card-body">
|
|
<vue-editor-js
|
|
v-on:saved="editorSaved"
|
|
:config="config"
|
|
:initialized="onInitialized"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-3">
|
|
<div class="d-grid mb-2">
|
|
<select
|
|
class="form-select mb-2"
|
|
aria-label="Default select example"
|
|
v-on:change="statusChanged"
|
|
>
|
|
<option
|
|
v-for="item in status"
|
|
v-bind:key="item"
|
|
:selected="item == post.status"
|
|
:value="item"
|
|
>
|
|
Post Status: {{ item }}
|
|
</option>
|
|
</select>
|
|
<div class="fw-bold">Publish Date</div>
|
|
<div class="input-icon mb-2">
|
|
<span class="input-icon-addon"
|
|
><!-- Download SVG icon from http://tabler-icons.io/i/calendar -->
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="icon"
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
stroke-width="2"
|
|
stroke="currentColor"
|
|
fill="none"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
<path
|
|
d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12z"
|
|
></path>
|
|
<path d="M16 3v4"></path>
|
|
<path d="M8 3v4"></path>
|
|
<path d="M4 11h16"></path>
|
|
<path d="M11 15h1"></path>
|
|
<path d="M12 15v3"></path>
|
|
</svg>
|
|
</span>
|
|
<input
|
|
type="date"
|
|
v-model="post.publish_date"
|
|
class="form-control"
|
|
placeholder="Select a date"
|
|
id="datepicker-icon-prepend"
|
|
/>
|
|
</div>
|
|
<button
|
|
@click="checkAndSave"
|
|
class="btn btn-primary"
|
|
style="height: 50px"
|
|
>
|
|
<div
|
|
v-if="isSaving"
|
|
class="spinner-border"
|
|
role="status"
|
|
:disabled="isSaving"
|
|
:class="isSaving ? 'disabled' : ''"
|
|
>
|
|
<span class="visually-hidden">Saving...</span>
|
|
</div>
|
|
<span v-else>Save as {{ post.status }}</span>
|
|
</button>
|
|
</div>
|
|
<div class="card mb-2">
|
|
<div class="card-header fw-bold">Country Locality</div>
|
|
<div class="card-body">
|
|
<select class="form-select" v-on:change="localeChanged">
|
|
<option
|
|
v-for="item in countryLocales"
|
|
v-bind:key="item.id"
|
|
:value="item.slug"
|
|
:selected="item.slug == post.locale_slug"
|
|
>
|
|
{{ item.name }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="card mb-2">
|
|
<div class="card-header fw-bold">Categories</div>
|
|
<div class="card-body">
|
|
<div
|
|
class="py-1"
|
|
v-for="item in localeCategories"
|
|
v-bind:key="item.id"
|
|
>
|
|
<label>
|
|
<input
|
|
type="radio"
|
|
:id="item.id"
|
|
:value="item.id"
|
|
v-model="post.categories"
|
|
/>
|
|
{{ item.name }}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card mb-2">
|
|
<div class="card-header fw-bold">Authors</div>
|
|
<div class="card-body">
|
|
<div class="py-1" v-for="item in authors" v-bind:key="item.id">
|
|
<label>
|
|
<input
|
|
type="radio"
|
|
:id="item.id"
|
|
:value="item.id"
|
|
v-model="post.author_id"
|
|
/>
|
|
{{ item.name }}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card mb-2">
|
|
<div class="card-header fw-bold">Other Settings</div>
|
|
<div class="card-body">
|
|
<div class="form-check form-switch">
|
|
<input
|
|
v-model="post.featured"
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
role="switch"
|
|
/>
|
|
<label class="form-check-label">Feature this Post</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import VueEditorJs from "./VueEditorJs.vue";
|
|
|
|
import List from "@editorjs/list";
|
|
import Header from "@editorjs/header";
|
|
|
|
import { mapActions, mapState } from "pinia";
|
|
|
|
import { usePostStore } from "@/stores/postStore.js";
|
|
|
|
import axios from "axios";
|
|
import route from "ziggy-js/src/js/index";
|
|
|
|
export default {
|
|
components: { VueEditorJs, List, Header },
|
|
props: {
|
|
postId: {
|
|
type: Number, // The prop type is Number
|
|
default: null, // Default value if the prop is not provided
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
isSaving: false,
|
|
showEditorJs: false,
|
|
post: {
|
|
id: null,
|
|
title: "",
|
|
slug: "",
|
|
excerpt: "",
|
|
author_id: null,
|
|
featured: false,
|
|
publish_date: null,
|
|
featured_image: null,
|
|
body: {
|
|
time: 1591362820044,
|
|
blocks: [],
|
|
version: "2.25.0",
|
|
},
|
|
locale_slug: null,
|
|
locale_id: null,
|
|
status: "draft",
|
|
categories: null,
|
|
},
|
|
|
|
status: ["publish", "future", "draft", "private", "trash"],
|
|
|
|
config: {
|
|
placeholder: "Write something (ノ◕ヮ◕)ノ*:・゚✧",
|
|
tools: {
|
|
header: {
|
|
class: Header,
|
|
config: {
|
|
placeholder: "Enter a header",
|
|
levels: [2, 3, 4],
|
|
defaultLevel: 3,
|
|
},
|
|
},
|
|
list: {
|
|
class: List,
|
|
inlineToolbar: true,
|
|
},
|
|
},
|
|
onReady: () => {},
|
|
onChange: (args) => {},
|
|
data: {
|
|
time: 1591362820044,
|
|
blocks: [],
|
|
version: "2.25.0",
|
|
},
|
|
},
|
|
};
|
|
},
|
|
watch: {
|
|
"post.title": {
|
|
deep: true,
|
|
handler(after, before) {
|
|
this.post.slug = this.slugify(after);
|
|
},
|
|
},
|
|
},
|
|
computed: {
|
|
...mapState(usePostStore, [
|
|
"countryLocales",
|
|
"localeCategories",
|
|
"defaultLocaleSlug",
|
|
"authors",
|
|
]),
|
|
getPostFullUrl() {
|
|
if (this.post.slug?.length > 0) {
|
|
return (
|
|
"https://productalert.co/" +
|
|
this.post.locale_slug +
|
|
"/posts/" +
|
|
this.post.slug
|
|
);
|
|
}
|
|
return (
|
|
"https://productalert.co/" +
|
|
this.post.locale_slug +
|
|
"/posts/enter-a-post-title-to-autogen-slug"
|
|
);
|
|
},
|
|
},
|
|
methods: {
|
|
...mapActions(usePostStore, [
|
|
"fetchCountryLocales",
|
|
"fetchLocaleCategories",
|
|
"fetchAuthors",
|
|
]),
|
|
checkAndSave() {
|
|
let errors = [];
|
|
|
|
if (!(this.post.title?.length > 0)) {
|
|
errors.push("post title");
|
|
}
|
|
|
|
if (!(this.post.publish_date?.length > 0)) {
|
|
errors.push("publish date");
|
|
}
|
|
|
|
if (!(this.post.slug?.length > 0)) {
|
|
errors.push("post slug");
|
|
}
|
|
|
|
if (!(this.post.excerpt?.length > 0)) {
|
|
errors.push("post excerpt");
|
|
}
|
|
|
|
if (!(this.post.featured_image?.length > 0)) {
|
|
errors.push("post featured image");
|
|
}
|
|
|
|
if (!(this.post.body.blocks?.length > 0)) {
|
|
errors.push("Post body");
|
|
}
|
|
|
|
if (
|
|
!(this.post.locale_slug?.length > 0) ||
|
|
!(this.post.locale_id != null)
|
|
) {
|
|
errors.push("Country locality");
|
|
}
|
|
|
|
if (!(this.post.categories != null)) {
|
|
errors.push("Category");
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
alert("HAIYA many errors! pls fix " + errors.join(", "));
|
|
} else {
|
|
this.savePost();
|
|
}
|
|
},
|
|
savePost() {
|
|
this.isSaving = true;
|
|
|
|
const formData = new FormData();
|
|
|
|
for (const [key, _item] of Object.entries(this.post)) {
|
|
if (_item != null) {
|
|
if (key == "body") {
|
|
formData.append(key, JSON.stringify(_item));
|
|
} else {
|
|
formData.append(key, _item);
|
|
}
|
|
}
|
|
}
|
|
|
|
axios
|
|
.post(route("api.admin.post.upsert"), formData, {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
.then((response) => {
|
|
console.warn(response);
|
|
});
|
|
|
|
setTimeout(
|
|
function () {
|
|
this.isSaving = false;
|
|
}.bind(this),
|
|
1000
|
|
);
|
|
},
|
|
onInitialized(editor) {},
|
|
imageSaved(src) {
|
|
this.post.featured_image = src;
|
|
},
|
|
editorSaved(payload) {
|
|
this.post.body = payload;
|
|
},
|
|
statusChanged(e) {
|
|
this.post.status = e.target.value;
|
|
},
|
|
localeChanged(e) {
|
|
this.post.locale_slug = e.target.value;
|
|
this.post.locale_id = this.getLocaleIdBySlug(e.target.value);
|
|
this.post.categories = [];
|
|
|
|
setTimeout(
|
|
function () {
|
|
this.fetchLocaleCategories(this.post.locale_slug);
|
|
}.bind(this),
|
|
100
|
|
);
|
|
},
|
|
setDefaultLocale() {
|
|
if (this.post.locale_slug == null || this.post.locale_slug == "") {
|
|
this.post.locale_slug = this.defaultLocaleSlug;
|
|
this.post.locale_id = this.getLocaleIdBySlug(this.defaultLocaleSlug);
|
|
}
|
|
},
|
|
getLocaleIdBySlug(slug) {
|
|
for (const [key, _item] of Object.entries(this.countryLocales)) {
|
|
if (_item.slug == slug) {
|
|
return _item.id;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
async fetchPostData(id) {
|
|
const response = await axios.get(route("api.admin.post.get", { id: id }));
|
|
|
|
if (response?.data?.post != null) {
|
|
let tmp = this.post;
|
|
let post = response.data.post;
|
|
|
|
tmp.id = post.id;
|
|
tmp.title = post.title;
|
|
tmp.slug = post.slug;
|
|
tmp.publish_date = post.publish_date;
|
|
tmp.excerpt = post.excerpt;
|
|
tmp.author_id = post.author_id;
|
|
tmp.featured = post.featured;
|
|
tmp.featured_image = post.featured_image;
|
|
tmp.body = post.body;
|
|
tmp.locale_slug = post.post_category.category.country_locale_slug;
|
|
tmp.locale_id = post.post_category.category.country_locale_id;
|
|
tmp.status = post.status;
|
|
tmp.categories = post.post_category.category.id;
|
|
|
|
this.post = tmp;
|
|
|
|
this.config.data = post.body;
|
|
}
|
|
|
|
console.log(response.data.post);
|
|
},
|
|
slugify: function (title) {
|
|
var slug = "";
|
|
// Change to lower case
|
|
var titleLower = title.toLowerCase();
|
|
// Replace characters that are not alphabets (a-z), digits (0-9), or spaces with an empty string
|
|
slug = titleLower.replace(/[^a-z0-9\s]/g, "");
|
|
// Replace consecutive spaces with a single space
|
|
slug = slug.replace(/\s+/g, " ");
|
|
// Trim any leading or trailing spaces
|
|
slug = slug.trim();
|
|
// Replace spaces with a single dash
|
|
slug = slug.replace(/\s+/g, "-");
|
|
return slug;
|
|
},
|
|
},
|
|
mounted() {
|
|
this.fetchCountryLocales().then(() => {
|
|
this.setDefaultLocale();
|
|
|
|
setTimeout(
|
|
function () {
|
|
this.fetchLocaleCategories(this.post.locale_slug);
|
|
this.fetchAuthors();
|
|
|
|
if (this.postId != null) {
|
|
this.fetchPostData(this.postId).then(() => {
|
|
setTimeout(
|
|
function () {
|
|
this.showEditorJs = true;
|
|
}.bind(this),
|
|
1000
|
|
);
|
|
});
|
|
} else {
|
|
setTimeout(
|
|
function () {
|
|
this.showEditorJs = true;
|
|
}.bind(this),
|
|
1000
|
|
);
|
|
}
|
|
}.bind(this),
|
|
100
|
|
);
|
|
});
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style></style>
|