663 lines
20 KiB
Vue
663 lines
20 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>
|
|
<VueDatePicker
|
|
:timezone="timezone"
|
|
v-model="post.publish_date"
|
|
></VueDatePicker>
|
|
</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 ImageTool from "@editorjs/image";
|
|
|
|
import { mapActions, mapState } from "pinia";
|
|
|
|
import { usePostStore } from "@/stores/postStore.js";
|
|
|
|
import axios from "axios";
|
|
import route from "ziggy-js/src/js/index";
|
|
|
|
import VueDatePicker from "@vuepic/vue-datepicker";
|
|
import "@vuepic/vue-datepicker/dist/main.css";
|
|
|
|
import { addMinutes } from "date-fns";
|
|
|
|
export default {
|
|
components: { VueEditorJs, List, Header, VueDatePicker },
|
|
props: {
|
|
postId: {
|
|
type: Number, // The prop type is Number
|
|
default: null, // Default value if the prop is not provided
|
|
},
|
|
timezone: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
},
|
|
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,
|
|
},
|
|
image: {
|
|
class: ImageTool,
|
|
config: {
|
|
field: "file",
|
|
endpoints: {
|
|
byFile: null, // Your backend file uploader endpoint
|
|
byUrl: null, // Your endpoint that provides uploading by Url
|
|
},
|
|
},
|
|
},
|
|
},
|
|
onReady: () => {},
|
|
onChange: (args) => {},
|
|
data: {
|
|
time: 1690738306815,
|
|
blocks: [
|
|
{
|
|
id: "DYr36VT6KH",
|
|
data: {
|
|
text: "Introduction",
|
|
level: 3,
|
|
},
|
|
type: "header",
|
|
},
|
|
{
|
|
id: "TAh-E2RIrs",
|
|
data: {
|
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
},
|
|
type: "paragraph",
|
|
},
|
|
{
|
|
id: "sQWS7Ivg74",
|
|
data: {
|
|
text: "First Point",
|
|
level: 3,
|
|
},
|
|
type: "header",
|
|
},
|
|
{
|
|
id: "Y9GYmrtsEk",
|
|
data: {
|
|
file: {
|
|
url: "https://cdn1.productalert.co/uploads/1690738207_3b4cf9ff-c617-4062-b910-22e61e1751d0.jpg",
|
|
},
|
|
caption: "Picture of First Point",
|
|
stretched: false,
|
|
withBorder: false,
|
|
withBackground: false,
|
|
},
|
|
type: "image",
|
|
},
|
|
{
|
|
id: "7qzQF_jale",
|
|
data: {
|
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
},
|
|
type: "paragraph",
|
|
},
|
|
{
|
|
id: "_oYWs021IJ",
|
|
data: {
|
|
text: "Second Point",
|
|
level: 3,
|
|
},
|
|
type: "header",
|
|
},
|
|
{
|
|
id: "PzXRqEDx1Z",
|
|
data: {
|
|
file: {
|
|
url: "https://cdn1.productalert.co/uploads/1690738243_8eb9f5b2-f3ad-45d9-a626-8ef160ef4068.jpg",
|
|
},
|
|
caption: "Picture of Second Point",
|
|
stretched: false,
|
|
withBorder: false,
|
|
withBackground: false,
|
|
},
|
|
type: "image",
|
|
},
|
|
{
|
|
id: "oD5oZ_q0Qo",
|
|
data: {
|
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
},
|
|
type: "paragraph",
|
|
},
|
|
{
|
|
id: "am9pIHopIw",
|
|
data: {
|
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
},
|
|
type: "paragraph",
|
|
},
|
|
{
|
|
id: "iFvJ1tYZk-",
|
|
data: {
|
|
text: "Third Point",
|
|
level: 3,
|
|
},
|
|
type: "header",
|
|
},
|
|
{
|
|
id: "zqwukyGttU",
|
|
data: {
|
|
file: {
|
|
url: "https://cdn1.productalert.co/uploads/1690738271_180a520a-22df-4b98-aad3-9962e10832d6.jpg",
|
|
},
|
|
caption: "Picture of Third Point",
|
|
stretched: false,
|
|
withBorder: false,
|
|
withBackground: false,
|
|
},
|
|
type: "image",
|
|
},
|
|
{
|
|
id: "uuR88uia0m",
|
|
data: {
|
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
},
|
|
type: "paragraph",
|
|
},
|
|
{
|
|
id: "KNVtnJ5lou",
|
|
data: {
|
|
text: "Fourth Point",
|
|
level: 3,
|
|
},
|
|
type: "header",
|
|
},
|
|
{
|
|
id: "SWdpL4jh6G",
|
|
data: {
|
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
},
|
|
type: "paragraph",
|
|
},
|
|
{
|
|
id: "dQqWsgP_FO",
|
|
data: {
|
|
text: "Conclusion",
|
|
level: 3,
|
|
},
|
|
type: "header",
|
|
},
|
|
{
|
|
id: "I7FOByi69M",
|
|
data: {
|
|
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
},
|
|
type: "paragraph",
|
|
},
|
|
],
|
|
version: "2.27.2",
|
|
},
|
|
},
|
|
};
|
|
},
|
|
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 != null)) {
|
|
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 if (key == "publish_date") {
|
|
if (_item instanceof Date) {
|
|
// Now utcDate is the equivalent UTC date of your original date
|
|
let isoDate = _item.toISOString();
|
|
|
|
formData.append(key, isoDate);
|
|
} else {
|
|
formData.append(key, _item);
|
|
}
|
|
} else {
|
|
formData.append(key, _item);
|
|
}
|
|
}
|
|
}
|
|
|
|
axios
|
|
.post(route("api.admin.post.upsert"), formData, {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
.then((response) => {
|
|
console.warn(response);
|
|
if (response.data.action == "redirect_back") {
|
|
window.location.replace(route("posts.manage"));
|
|
}
|
|
});
|
|
|
|
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.config.tools.image.config.endpoints.byFile = route(
|
|
"api.admin.upload.cloud.image"
|
|
);
|
|
this.config.tools.image.config.additionalRequestHeaders = {
|
|
"X-CSRF-TOKEN": document
|
|
.querySelector('meta[name="csrf-token"]')
|
|
.getAttribute("content"),
|
|
};
|
|
|
|
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>
|