Browse Source

Add WYSIWYG editor and split auth/no-auth post routes

feature/add-admin-posts-frontend
Tovi Jaeschke-Rogers 2 years ago
parent
commit
53cf5a05e5
14 changed files with 735 additions and 233 deletions
  1. +1
    -0
      Api/JsonSerialization/VerifyJson.go
  2. +27
    -156
      Api/Posts.go
  3. +283
    -0
      Api/PostsAdmin.go
  4. +12
    -8
      Api/Routes.go
  5. +6
    -0
      Api/Users.go
  6. +19
    -52
      Database/Posts.go
  7. +97
    -0
      Database/PostsAdmin.go
  8. +15
    -0
      Frontend/vue/src/assets/css/admin.css
  9. +204
    -0
      Frontend/vue/src/components/admin/components/editor/AdminEditor.vue
  10. +1
    -1
      Frontend/vue/src/components/admin/components/list/AdminListHeader.vue
  11. +63
    -10
      Frontend/vue/src/components/admin/views/posts/AdminPostsForm.vue
  12. +1
    -1
      Frontend/vue/src/components/admin/views/posts/AdminPostsList.vue
  13. +1
    -1
      Frontend/vue/src/router/index.js
  14. +5
    -4
      Frontend/vue/src/utils/http/index.js

+ 1
- 0
Api/JsonSerialization/VerifyJson.go View File

@ -55,6 +55,7 @@ func CanConvert(t reflect.Type, v reflect.Value) bool {
f float64
srcInt bool
)
isPtr = t.Kind() == reflect.Ptr
isStruct = t.Kind() == reflect.Struct
isArray = t.Kind() == reflect.Array


+ 27
- 156
Api/Posts.go View File

@ -2,70 +2,16 @@ package Api
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"strconv"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/JsonSerialization"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util"
)
func getPosts(w http.ResponseWriter, r *http.Request) {
var (
posts []Models.Post
returnJson []byte
values url.Values
page, pageSize int
search string
err error
)
values = r.URL.Query()
page, err = strconv.Atoi(values.Get("page"))
if err != nil {
log.Println("Could not parse page url argument")
Util.JsonReturn(w, 500, "An error occured")
return
}
pageSize, err = strconv.Atoi(values.Get("pageSize"))
if err != nil {
log.Println("Could not parse pageSize url argument")
Util.JsonReturn(w, 500, "An error occured")
return
}
search = values.Get("search")
posts, err = Database.GetPosts(page, pageSize, search)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
if len(posts) == 0 {
Util.JsonReturn(w, 404, "No more data")
return
}
returnJson, err = json.MarshalIndent(posts, "", " ")
if err != nil {
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func getFrontPagePosts(w http.ResponseWriter, r *http.Request) {
var (
posts []Models.Post
@ -91,71 +37,48 @@ func getFrontPagePosts(w http.ResponseWriter, r *http.Request) {
w.Write(returnJson)
}
func getPost(w http.ResponseWriter, r *http.Request) {
func getPostsPublic(w http.ResponseWriter, r *http.Request) {
var (
postData Models.Post
returnJson []byte
err error
posts []Models.Post
returnJson []byte
values url.Values
page, pageSize int
search string
err error
)
postData, err = Util.GetPostById(w, r)
if err != nil {
return
}
values = r.URL.Query()
returnJson, err = json.MarshalIndent(postData, "", " ")
page, err = strconv.Atoi(values.Get("page"))
if err != nil {
log.Println("Could not parse page url argument")
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func createPost(w http.ResponseWriter, r *http.Request) {
var (
requestBody []byte
postData Models.Post
returnJson []byte
err error
)
_, err = Auth.CheckCookie(r)
pageSize, err = strconv.Atoi(values.Get("pageSize"))
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
log.Println("Could not parse pageSize url argument")
Util.JsonReturn(w, 500, "An error occured")
return
}
requestBody, err = ioutil.ReadAll(r.Body)
search = values.Get("search")
posts, err = Database.GetPostsPublic(page, pageSize, search)
if err != nil {
log.Printf("Error encountered reading POST body: %s\n", err.Error())
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
postData, err = JsonSerialization.DeserializePost(requestBody, []string{
"id",
"links",
"images",
"videos",
"audios",
}, false)
if err != nil {
log.Printf("Invalid data provided to posts API: %s\n", err.Error())
Util.JsonReturn(w, 405, "Invalid data")
if len(posts) == 0 {
Util.JsonReturn(w, 404, "No more data")
return
}
err = Database.CreatePost(&postData)
if err != nil {
Util.JsonReturn(w, 405, "Invalid data")
}
returnJson, err = json.MarshalIndent(postData, "", " ")
returnJson, err = json.MarshalIndent(posts, "", " ")
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
@ -165,52 +88,28 @@ func createPost(w http.ResponseWriter, r *http.Request) {
w.Write(returnJson)
}
func updatePost(w http.ResponseWriter, r *http.Request) {
func getPostPublic(w http.ResponseWriter, r *http.Request) {
var (
postData Models.Post
requestBody []byte
returnJson []byte
id string
err error
postData Models.Post
returnJson []byte
id string
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
id, err = Util.GetPostId(r)
if err != nil {
log.Printf("Error encountered getting id\n")
Util.JsonReturn(w, 500, "An error occured")
return
}
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error encountered reading POST body: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
postData, err = JsonSerialization.DeserializePost(requestBody, []string{}, true)
if err != nil {
log.Printf("Invalid data provided to posts API: %s\n", err.Error())
Util.JsonReturn(w, 405, "Invalid data")
return
}
postData, err = Database.UpdatePost(id, &postData)
postData, err = Database.GetPostByIdPublic(id)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
Util.JsonReturn(w, 404, "Not found")
return
}
returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
@ -219,31 +118,3 @@ func updatePost(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func deletePost(w http.ResponseWriter, r *http.Request) {
var (
postData Models.Post
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
postData, err = Util.GetPostById(w, r)
if err != nil {
return
}
err = Database.DeletePost(&postData)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
}

+ 283
- 0
Api/PostsAdmin.go View File

@ -0,0 +1,283 @@
package Api
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"strconv"
"time"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/JsonSerialization"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util"
)
func getPosts(w http.ResponseWriter, r *http.Request) {
var (
posts []Models.Post
returnJson []byte
values url.Values
page, pageSize int
search string
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
values = r.URL.Query()
page, err = strconv.Atoi(values.Get("page"))
if err != nil {
log.Println("Could not parse page url argument")
Util.JsonReturn(w, 500, "An error occured")
return
}
pageSize, err = strconv.Atoi(values.Get("pageSize"))
if err != nil {
log.Println("Could not parse pageSize url argument")
Util.JsonReturn(w, 500, "An error occured")
return
}
search = values.Get("search")
posts, err = Database.GetPosts(page, pageSize, search)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
if len(posts) == 0 {
Util.JsonReturn(w, 404, "No more data")
return
}
returnJson, err = json.MarshalIndent(posts, "", " ")
if err != nil {
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func getPost(w http.ResponseWriter, r *http.Request) {
var (
postData Models.Post
returnJson []byte
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
postData, err = Util.GetPostById(w, r)
if err != nil {
return
}
returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil {
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func createPost(w http.ResponseWriter, r *http.Request) {
var (
requestBody []byte
postData Models.Post
returnJson []byte
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error encountered reading POST body: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
postData, err = JsonSerialization.DeserializePost(requestBody, []string{
"id",
"links",
"images",
"videos",
"audios",
}, false)
if err != nil {
log.Printf("Invalid data provided to posts API: %s\n", err.Error())
Util.JsonReturn(w, 405, "Invalid data")
return
}
err = Database.CreatePost(&postData)
if err != nil {
Util.JsonReturn(w, 405, "Invalid data")
}
returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func updatePost(w http.ResponseWriter, r *http.Request) {
var (
postData Models.Post
requestBody []byte
returnJson []byte
id string
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
id, err = Util.GetPostId(r)
if err != nil {
log.Printf("Error encountered getting id\n")
Util.JsonReturn(w, 500, "An error occured")
return
}
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error encountered reading POST body: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
postData, err = JsonSerialization.DeserializePost(requestBody, []string{}, true)
if err != nil {
log.Printf("Invalid data provided to posts API: %s\n", err.Error())
Util.JsonReturn(w, 405, "Invalid data")
return
}
postData, err = Database.UpdatePost(id, &postData)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func publishPost(w http.ResponseWriter, r *http.Request) {
var (
now time.Time = time.Now()
postData Models.Post
returnJson []byte
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
postData, err = Util.GetPostById(w, r)
if err != nil {
log.Printf("Error encountered getting id\n")
Util.JsonReturn(w, 500, "An error occured")
return
}
if postData.PublishedAt == nil {
postData.PublishedAt = &now
} else {
postData.PublishedAt = nil
}
postData, err = Database.UpdatePost(postData.ID.String(), &postData)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
returnJson, err = json.MarshalIndent(postData, "", " ")
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJson)
}
func deletePost(w http.ResponseWriter, r *http.Request) {
var (
postData Models.Post
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
postData, err = Util.GetPostById(w, r)
if err != nil {
return
}
err = Database.DeletePost(&postData)
if err != nil {
log.Printf("An error occured: %s\n", err.Error())
Util.JsonReturn(w, 500, "An error occured")
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
}

+ 12
- 8
Api/Routes.go View File

@ -17,17 +17,21 @@ func InitApiEndpoints(router *mux.Router) {
api = router.PathPrefix("/api/v1/").Subrouter()
api.HandleFunc("/posts/front-page", getFrontPagePosts).Methods("GET")
api.HandleFunc("/post", getPostsPublic).Methods("GET")
api.HandleFunc("/post/{postID}", getPostPublic).Methods("GET")
// Define routes for posts api
api.HandleFunc("/post", getPosts).Methods("GET")
api.HandleFunc("/post", createPost).Methods("POST")
api.HandleFunc("/post/{postID}", getPost).Methods("GET")
api.HandleFunc("/post/{postID}", updatePost).Methods("PUT")
api.HandleFunc("/post/{postID}", deletePost).Methods("DELETE")
api.HandleFunc("/admin/post", getPosts).Methods("GET")
api.HandleFunc("/admin/post/{postID}", getPost).Methods("GET")
api.HandleFunc("/frontPagePosts", getFrontPagePosts).Methods("GET")
api.HandleFunc("/admin/post", createPost).Methods("POST")
api.HandleFunc("/admin/post/{postID}", updatePost).Methods("PUT")
api.HandleFunc("/admin/post/{postID}", deletePost).Methods("DELETE")
api.HandleFunc("/admin/post/{postID}/publish", publishPost).Methods("GET")
api.HandleFunc("/post/{postID}/image", createPostImage).Methods("POST")
api.HandleFunc("/post/{postID}/image/{imageID}", deletePostImage).Methods("DELETE")
api.HandleFunc("/admin/post/{postID}/image", createPostImage).Methods("POST")
api.HandleFunc("/admin/post/{postID}/image/{imageID}", deletePostImage).Methods("DELETE")
// Define routes for users api
api.HandleFunc("/admin/user", getUsers).Methods("GET")


+ 6
- 0
Api/Users.go View File

@ -109,6 +109,12 @@ func createUser(w http.ResponseWriter, r *http.Request) {
err error
)
_, err = Auth.CheckCookie(r)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error encountered reading POST body: %s\n", err.Error())


+ 19
- 52
Database/Posts.go View File

@ -9,7 +9,22 @@ import (
"gorm.io/gorm/clause"
)
func GetPosts(page, pageSize int, search string) ([]Models.Post, error) {
func GetFrontPagePosts() ([]Models.Post, error) {
var (
posts []Models.Post
err error
)
err = DB.Where("front_page = ?", true).
Where("published_at IS NOT NULL").
Order("order asc").
Find(&posts).
Error
return posts, err
}
func GetPostsPublic(page, pageSize int, search string) ([]Models.Post, error) {
var (
posts []Models.Post
query *gorm.DB
@ -30,6 +45,7 @@ func GetPosts(page, pageSize int, search string) ([]Models.Post, error) {
query = DB.Model(Models.Post{}).
Preload(clause.Associations).
Where("published_at IS NOT NULL").
Offset(offset).
Limit(pageSize).
Order("created_at desc")
@ -47,65 +63,16 @@ func GetPosts(page, pageSize int, search string) ([]Models.Post, error) {
return posts, err
}
func GetFrontPagePosts() ([]Models.Post, error) {
var (
posts []Models.Post
err error
)
err = DB.Where("front_page = ?", true).
Order("order asc").
Find(&posts).
Error
return posts, err
}
func GetPostById(id string) (Models.Post, error) {
func GetPostByIdPublic(id string) (Models.Post, error) {
var (
postData Models.Post
err error
)
err = DB.Preload(clause.Associations).
Where("published_at IS NOT NULL").
First(&postData, "id = ?", id).
Error
return postData, err
}
func CreatePost(postData *Models.Post) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(postData).
Error
}
func UpdatePost(id string, postData *Models.Post) (Models.Post, error) {
var (
err error
)
DB.Model(postData).
Where("id = ?", id).
Association("PostLinks").
Replace(postData.PostLinks)
err = DB.Model(&Models.Post{}).
Select("*").
Omit("id", "created_at", "updated_at", "deleted_at").
Where("id = ?", id).
Updates(postData).
Error
if err != nil {
return Models.Post{}, err
}
return GetPostById(id)
}
func DeletePost(postData *Models.Post) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(postData).
Error
}

+ 97
- 0
Database/PostsAdmin.go View File

@ -0,0 +1,97 @@
package Database
import (
"fmt"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetPosts(page, pageSize int, search string) ([]Models.Post, error) {
var (
posts []Models.Post
query *gorm.DB
offset int
err error
)
switch {
case pageSize > 100:
pageSize = 100
case pageSize <= 0:
pageSize = 10
}
offset = page * pageSize
search = fmt.Sprintf("%%%s%%", search)
query = DB.Model(Models.Post{}).
Preload(clause.Associations).
Offset(offset).
Limit(pageSize).
Order("created_at desc")
if search != "%%" {
query = query.
Where("title LIKE ?", search).
Or("content LIKE ?", search)
}
err = query.
Find(&posts).
Error
return posts, err
}
func GetPostById(id string) (Models.Post, error) {
var (
postData Models.Post
err error
)
err = DB.Preload(clause.Associations).
First(&postData, "id = ?", id).
Error
return postData, err
}
func CreatePost(postData *Models.Post) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(postData).
Error
}
func UpdatePost(id string, postData *Models.Post) (Models.Post, error) {
var (
err error
)
DB.Model(postData).
Where("id = ?", id).
Association("PostLinks").
Replace(postData.PostLinks)
err = DB.Model(&Models.Post{}).
Select("*").
Omit("id", "created_at", "updated_at", "deleted_at").
Where("id = ?", id).
Updates(postData).
Error
if err != nil {
return Models.Post{}, err
}
return GetPostById(id)
}
func DeletePost(postData *Models.Post) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(postData).
Error
}

+ 15
- 0
Frontend/vue/src/assets/css/admin.css View File

@ -118,3 +118,18 @@ label[role=alert] {
background-color: #e9ecef;
opacity: 1;
}
.editor-form .input-group {
display: inline-block;
width: unset;
padding-right: 0.3rem;
}
.ProseMirror {
outline: 0;
height: 100%;
}
.ProseMirror p:last-child {
margin-bottom: 0;
}

+ 204
- 0
Frontend/vue/src/components/admin/components/editor/AdminEditor.vue View File

@ -0,0 +1,204 @@
<template>
<div class="form-outline editor-form" v-if="editor">
<div class="mb-3">
<div class="input-group">
<button
@click="editor.chain().focus().toggleBold().run()"
:class="editor.isActive('bold') ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
<i class="fa-solid fa-bold"></i>
</button>
<button
@click="editor.chain().focus().toggleItalic().run()"
:class="editor.isActive('italic') ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
<i class="fa-solid fa-italic"></i>
</button>
<button
@click="editor.chain().focus().toggleStrike().run()"
:class="editor.isActive('strike') ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
<i class="fa-solid fa-strikethrough"></i>
</button>
<button
@click="editor.chain().focus().toggleCode().run()"
:class="editor.isActive('code') ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
<i class="fa-solid fa-code"></i>
</button>
<button
@click="editor.chain().focus().setParagraph().run()"
:class="editor.isActive('paragraph') ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
<i class="fa-solid fa-paragraph"></i>
</button>
</div>
<div class="input-group">
<button
@click="editor.chain().focus().toggleHeading({level: 1 }).run()"
:class="editor.isActive('heading', { level: 1 }) ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
H1
</button>
<button
@click="editor.chain().focus().toggleHeading({level: 2 }).run()"
:class="editor.isActive('heading', { level: 2 }) ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
H2
</button>
<button
@click="editor.chain().focus().toggleHeading({level: 3 }).run()"
:class="editor.isActive('heading', { level: 3 }) ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
H3
</button>
<button
@click="editor.chain().focus().toggleHeading({level: 4 }).run()"
:class="editor.isActive('heading', { level: 4 }) ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
H4
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 5 }).run()"
:class="editor.isActive('heading', { level: 5 }) ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
H5
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 6 }).run()"
:class="editor.isActive('heading', { level: 6 }) ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
H6
</button>
</div>
<div class="input-group">
<button
@click="editor.chain().focus().toggleBulletList().run()"
:class="editor.isActive('bulletList') ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
<i class="fa-solid fa-list"></i>
</button>
<button
@click="editor.chain().focus().toggleOrderedList().run()"
:class="editor.isActive('orderedList') ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
<i class="fa-solid fa-list-ol"></i>
</button>
<button
@click="editor.chain().focus().toggleCodeBlock().run()"
:class="editor.isActive('codeBlock') ? 'btn-dark' : 'btn-outline-dark'"
type="button"
class="btn"
>
<i class="fa-solid fa-file-code"></i>
</button>
<button
@click="editor.chain().focus().setHorizontalRule().run()"
type="button"
class="btn btn-outline-dark"
>
<i class="fa-solid fa-ruler-horizontal"></i>
</button>
</div>
<div class="input-group">
<button
@click="editor.chain().focus().undo().run()"
type="button"
class="btn btn-outline-dark"
>
<i class="fa-solid fa-rotate-left"></i>
</button>
<button
@click="editor.chain().focus().redo().run()"
type="button"
class="btn btn-outline-dark"
>
<i class="fa-solid fa-rotate-right"></i>
</button>
</div>
</div>
<editor-content :editor="editor" class="form-control"/>
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
export default {
props: {
modelValue: {
type: String,
default: '',
},
},
data () {
return {
editor: null,
}
},
components: {
EditorContent,
},
watch: {
modelValue(value) {
// HTML
const isSame = this.editor.getHTML() === value
// JSON
// const isSame = JSON.stringify(this.editor.getJSON()) === JSON.stringify(value)
if (isSame) {
return
}
this.editor.commands.setContent(value, false)
},
},
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
],
content: this.modelValue,
onUpdate: () => {
// HTML
this.$emit('update:modelValue', this.editor.getHTML())
// JSON
// this.$emit('update:modelValue', this.editor.getJSON())
},
})
},
beforeUnmount() {
this.editor.destroy()
},
}
</script>

+ 1
- 1
Frontend/vue/src/components/admin/components/list/AdminListHeader.vue View File

@ -18,7 +18,7 @@
type="button"
@click="searchFunction"
>
Search
<i class="fa-solid fa-magnifying-glass"></i>
</button>
</div>
</div>


+ 63
- 10
Frontend/vue/src/components/admin/views/posts/AdminPostsForm.vue View File

@ -21,7 +21,7 @@
<div class="card shadow-2-strong card-registration">
<div class="card-body p-4 p-md-5" v-if="tab === 'details'">
<h3 class="mb-4 pb-2 pb-md-0 mb-md-5">Update Post</h3>
<Form @submit="updatePost" v-slot="{ meta, errors }">
<Form @submit="updatePost" v-slot="{ errors }">
<div class="row">
<div class="col-md-8 mb-4">
@ -55,9 +55,18 @@
</div>
</div>
<div class="row">
<div class="col-12 mb-4">
<admin-editor
v-model="post.content"
/>
<label class="form-label" for="editor">Content</label>
</div>
</div>
<div class="row">
<div class="col-md-4 mb-4 pb-2">
<div class="form-outline">
<date-picker
@ -68,6 +77,7 @@
<label class="form-label" for="created_at">Created At</label>
</div>
</div>
<div class="col-md-4 mb-4 pb-2">
<div class="form-outline">
<date-picker
@ -78,13 +88,28 @@
<label class="form-label" for="updated_at">Updated At</label>
</div>
</div>
<div class="col-md-4 mb-4 pb-2">
<div class="form-outline">
<date-picker
v-model="post.published_at"
format="dd/MM/yyyy, HH:mm"
disabled="disabled"
id="published_at"/>
<label class="form-label" for="published_at">Published At</label>
</div>
</div>
</div>
<div class="mt-2 pt-2 right-align">
<button class="btn btn-danger btn-md" type="button">
Delete
</button>
<button :disabled="!meta.touched || !meta.valid" class="btn btn-primary btn-md" type="submit">
<button class="btn btn-outline-dark btn-md" type="button" @click="publishPost">
{{ publishedLabel }}
</button>
<button class="btn btn-primary btn-md" type="submit">
Update
</button>
</div>
@ -98,6 +123,7 @@
<script>
import AdminNavbar from '@/components/admin/components/navbar/AdminNavbar'
import AdminEditor from '@/components/admin/components/editor/AdminEditor'
import { Form, Field, ErrorMessage } from 'vee-validate'
export default {
@ -113,16 +139,23 @@ export default {
Form,
Field,
ErrorMessage,
AdminEditor,
},
mounted () {
async mounted () {
this.getPost()
},
computed: {
publishedLabel() {
return this.post.published_at === null ? 'Publish' : 'Unpublish'
}
},
methods: {
async getPost () {
try {
const response = await this.axios.get(`/post/${this.$route.params.id}`)
const response = await this.axios.get(`/admin/post/${this.$route.params.id}`)
if (response.status === 200) {
this.post = response.data
@ -135,22 +168,42 @@ export default {
async updatePost () {
try {
let response = await this.axios.put(
`/user/${this.$route.params.id}`,
`/admin/post/${this.$route.params.id}`,
{
first_name: this.user.first_name,
last_name: this.user.last_name,
email: this.user.email,
title: this.post.title,
content: this.post.content,
front_page: this.post.front_page,
order: this.post.order,
audios: this.post.audios,
images: this.post.images,
links: this.post.links,
videos: this.post.videos,
},
)
if (response.status === 200) {
this.$toast.success('Successfully updated user details.');
this.setPostFromResponse(response)
this.$toast.success('Successfully updated post.');
this.post = response.data
}
} catch (error) {
this.$toast.error('An error occured');
}
},
async publishPost () {
try {
let response = await this.axios.get(`/admin/post/${this.$route.params.id}/publish`)
if (response.status === 200) {
this.post = response.data
this.$toast.success(this.post.published_at !== null ? 'Successfully published post.' : 'Successfully unpublished post.');
}
} catch (error) {
this.$toast.error('An error occured');
}
}
}
}
</script>

+ 1
- 1
Frontend/vue/src/components/admin/views/posts/AdminPostsList.vue View File

@ -93,7 +93,7 @@ export default {
async getInitialPosts () {
try {
const response = await this.axios.get(
`/post?page=${this.page}&pageSize=${this.pageSize}&search=${this.search}`
`/admin/post?page=${this.page}&pageSize=${this.pageSize}&search=${this.search}`
)
if (response.status === 200) {


+ 1
- 1
Frontend/vue/src/router/index.js View File

@ -77,7 +77,7 @@ const router = createRouter({
});
router.beforeEach((to, from, next) => {
const user = admin.getters.getUser;
const user = admin.getUser;
if ((to.name == 'AdminLogin' || to.name == 'AdminSignup') && user !== null && !to.params.unauthorized) {
next({ name: 'AdminUsersList' });


+ 5
- 4
Frontend/vue/src/utils/http/index.js View File

@ -4,10 +4,11 @@ import router from '@/router'
import admin from '@/store/admin/index.js'
const instance = axios.create({
baseURL: "http://localhost:8080/api/v1/",
headers: {
"Content-Type": "application/json",
},
baseURL: "http://localhost:8080/api/v1/",
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
instance.interceptors.response.use(


Loading…
Cancel
Save