Add (new post)
This commit is contained in:
311
resources/js/vue/PostEditor.vue
Normal file
311
resources/js/vue/PostEditor.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<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
|
||||
class="mb-3"
|
||||
:input-image="post.featured_image"
|
||||
@saved="imageSaved"
|
||||
></native-image-block>
|
||||
|
||||
<div 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>
|
||||
<button @click="checkAndSave" class="btn btn-primary">
|
||||
Save as {{ post.status }}
|
||||
</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>
|
||||
</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";
|
||||
|
||||
export default {
|
||||
components: { VueEditorJs, List, Header },
|
||||
data() {
|
||||
return {
|
||||
post: {
|
||||
title: "",
|
||||
slug: "",
|
||||
excerpt: "",
|
||||
author_id: null,
|
||||
featured: false,
|
||||
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",
|
||||
]),
|
||||
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",
|
||||
]),
|
||||
checkAndSave() {
|
||||
let errors = [];
|
||||
|
||||
if (!(this.post.title?.length > 0)) {
|
||||
errors.push("post title");
|
||||
}
|
||||
|
||||
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() {},
|
||||
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;
|
||||
},
|
||||
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);
|
||||
}.bind(this),
|
||||
100
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
Reference in New Issue
Block a user