mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-11-22 04:17:33 +00:00
feat: revamp with tag editor
This commit is contained in:
parent
31763528bf
commit
19e7aea06d
@ -152,6 +152,15 @@ func postCreate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func postUpdate(c *gin.Context) {
|
func postUpdate(c *gin.Context) {
|
||||||
|
_, ok := c.Get("user")
|
||||||
|
if !ok {
|
||||||
|
c.JSON(http.StatusForbidden, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: "User don't exist",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
|
|
||||||
var model models.PostUpdateModel
|
var model models.PostUpdateModel
|
||||||
@ -194,6 +203,15 @@ func postUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func postDelete(c *gin.Context) {
|
func postDelete(c *gin.Context) {
|
||||||
|
_, ok := c.Get("user")
|
||||||
|
if !ok {
|
||||||
|
c.JSON(http.StatusForbidden, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: "User don't exist",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
|
|
||||||
err := services.DeletePost(id)
|
err := services.DeletePost(id)
|
||||||
|
@ -3,6 +3,8 @@ package app
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Damillora/Shioriko/pkg/middleware"
|
||||||
|
"github.com/Damillora/Shioriko/pkg/models"
|
||||||
"github.com/Damillora/Shioriko/pkg/services"
|
"github.com/Damillora/Shioriko/pkg/services"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -11,8 +13,14 @@ func InitializeTagRoutes(g *gin.Engine) {
|
|||||||
unprotected := g.Group("/api/tag")
|
unprotected := g.Group("/api/tag")
|
||||||
{
|
{
|
||||||
unprotected.GET("/", tagGet)
|
unprotected.GET("/", tagGet)
|
||||||
|
unprotected.GET("/:tag", tagGetOne)
|
||||||
unprotected.GET("/autocomplete", tagAutocomplete)
|
unprotected.GET("/autocomplete", tagAutocomplete)
|
||||||
}
|
}
|
||||||
|
protected := g.Group("/api/tag").Use(middleware.AuthMiddleware())
|
||||||
|
{
|
||||||
|
protected.PUT("/:tag/note", tagUpdateNote)
|
||||||
|
protected.PUT("/:tag", tagUpdate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tagGet(c *gin.Context) {
|
func tagGet(c *gin.Context) {
|
||||||
@ -20,7 +28,94 @@ func tagGet(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, tags)
|
c.JSON(http.StatusOK, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tagGetOne(c *gin.Context) {
|
||||||
|
tag := c.Param("tag")
|
||||||
|
tagObj, err := services.GetTag(tag)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, models.TagReadModel{
|
||||||
|
TagID: tagObj.TagID,
|
||||||
|
TagName: tagObj.TagName,
|
||||||
|
TagType: tagObj.TagType,
|
||||||
|
TagNote: tagObj.TagNote,
|
||||||
|
PostCount: tagObj.PostCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func tagAutocomplete(c *gin.Context) {
|
func tagAutocomplete(c *gin.Context) {
|
||||||
tags := services.GetTagAutocomplete()
|
tags := services.GetTagAutocomplete()
|
||||||
c.JSON(http.StatusOK, tags)
|
c.JSON(http.StatusOK, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tagUpdateNote(c *gin.Context) {
|
||||||
|
_, ok := c.Get("user")
|
||||||
|
if !ok {
|
||||||
|
c.JSON(http.StatusForbidden, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: "User don't exist",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
var model models.TagNoteUpdateModel
|
||||||
|
err := c.ShouldBindJSON(&model)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := c.Param("tag")
|
||||||
|
err = services.UpdateTagNotes(tag, model.Note)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagUpdate(c *gin.Context) {
|
||||||
|
_, ok := c.Get("user")
|
||||||
|
if !ok {
|
||||||
|
c.JSON(http.StatusForbidden, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: "User don't exist",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
var model models.TagUpdateModel
|
||||||
|
err := c.ShouldBindJSON(&model)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := c.Param("tag")
|
||||||
|
err = services.UpdateTag(tag, model)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, nil)
|
||||||
|
}
|
||||||
|
@ -12,10 +12,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func InitializeTagTypeRoutes(g *gin.Engine) {
|
func InitializeTagTypeRoutes(g *gin.Engine) {
|
||||||
|
unprotected := g.Group(("/api/tagtype"))
|
||||||
|
{
|
||||||
|
unprotected.GET("/", tagTypeGet)
|
||||||
|
}
|
||||||
protected := g.Group("/api/tagtype").Use(middleware.AuthMiddleware())
|
protected := g.Group("/api/tagtype").Use(middleware.AuthMiddleware())
|
||||||
{
|
{
|
||||||
protected.GET("/", tagTypeGet)
|
|
||||||
protected.POST("/create", tagTypeCreate)
|
protected.POST("/create", tagTypeCreate)
|
||||||
protected.DELETE("/:id", tagTypeDelete)
|
protected.DELETE("/:id", tagTypeDelete)
|
||||||
}
|
}
|
||||||
|
@ -11,5 +11,6 @@ type Tag struct {
|
|||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
TagTypeID uint
|
TagTypeID uint
|
||||||
TagType TagType
|
TagType TagType
|
||||||
|
Note string `gorm:"type:text"`
|
||||||
Posts []Post `gorm:"many2many:post_tags"`
|
Posts []Post `gorm:"many2many:post_tags"`
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ type UserCreateModel struct {
|
|||||||
type UserUpdateModel struct {
|
type UserUpdateModel struct {
|
||||||
Email string `json:"email" validate:"required,email"`
|
Email string `json:"email" validate:"required,email"`
|
||||||
Username string `json:"username" validate:"required"`
|
Username string `json:"username" validate:"required"`
|
||||||
Password string `json:"password"`
|
OldPassword string `json:"oldPassword"`
|
||||||
|
NewPassword string `json:"newPassword"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TagTypeCreateModel struct {
|
type TagTypeCreateModel struct {
|
||||||
@ -26,6 +27,9 @@ type TagUpdateModel struct {
|
|||||||
TagTypeID uint `json:"tagTypeId" validate:"required"`
|
TagTypeID uint `json:"tagTypeId" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TagNoteUpdateModel struct {
|
||||||
|
Note string `json:"note" validate:"required"`
|
||||||
|
}
|
||||||
type PostCreateModel struct {
|
type PostCreateModel struct {
|
||||||
BlobID string `json:"blob_id" validate:"required"`
|
BlobID string `json:"blob_id" validate:"required"`
|
||||||
SourceURL string `json:"source_url"`
|
SourceURL string `json:"source_url"`
|
||||||
|
@ -10,3 +10,11 @@ type PostReadModel struct {
|
|||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
Uploader string `json:"uploader"`
|
Uploader string `json:"uploader"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TagReadModel struct {
|
||||||
|
TagID string `json:"tagId"`
|
||||||
|
TagName string `json:"tagName"`
|
||||||
|
TagType string `json:"tagType"`
|
||||||
|
TagNote string `json:"tagNote"`
|
||||||
|
PostCount int `json:"postCount"`
|
||||||
|
}
|
||||||
|
@ -42,6 +42,24 @@ func GetTagFilter(tagObjs []database.Tag) []models.TagListItem {
|
|||||||
Find(&tags, tagIds)
|
Find(&tags, tagIds)
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTag(tagString string) (*models.TagReadModel, error) {
|
||||||
|
tagObj, err := FindTag(tagString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var tagModel models.TagReadModel
|
||||||
|
|
||||||
|
database.DB.Model(&tagObj).
|
||||||
|
Joins("join tag_types on tag_types.id = tags.tag_type_id").
|
||||||
|
Joins("left join post_tags on post_tags.tag_id = tags.id").
|
||||||
|
Select("tags.id as tag_id, tags.name as tag_name, tag_types.name as tag_type, tags.note as tag_note, count(post_tags.post_id) as post_count").
|
||||||
|
Group("tags.id, tags.name, tag_types.name, tags.note").
|
||||||
|
First(&tagModel, "tags.id = ? ", tagObj.ID)
|
||||||
|
|
||||||
|
return &tagModel, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetTagAutocomplete() []string {
|
func GetTagAutocomplete() []string {
|
||||||
var tags []string
|
var tags []string
|
||||||
result := database.DB.Model(&database.Tag{}).
|
result := database.DB.Model(&database.Tag{}).
|
||||||
@ -134,6 +152,7 @@ func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
|
|||||||
return nil, errors.New("Malformed tag syntax")
|
return nil, errors.New("Malformed tag syntax")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindTag(tagSyntax string) (*database.Tag, error) {
|
func FindTag(tagSyntax string) (*database.Tag, error) {
|
||||||
tagFields := strings.Split(tagSyntax, ":")
|
tagFields := strings.Split(tagSyntax, ":")
|
||||||
var tagName string
|
var tagName string
|
||||||
@ -173,3 +192,36 @@ func ParseReadTags(tags []string) ([]database.Tag, error) {
|
|||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateTagNotes(tagString string, notes string) error {
|
||||||
|
tagObj, err := FindTag(tagString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tagObj.Note = notes
|
||||||
|
|
||||||
|
result := database.DB.Save(&tagObj)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateTag(tagString string, model models.TagUpdateModel) error {
|
||||||
|
tagObj, err := FindTag(tagString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tagObj.TagTypeID = model.TagTypeID
|
||||||
|
tagObj.Name = model.Name
|
||||||
|
|
||||||
|
result := database.DB.Save(&tagObj)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -50,7 +50,12 @@ func UpdateUser(id string, model models.UserUpdateModel) (*database.User, error)
|
|||||||
user.Username = model.Username
|
user.Username = model.Username
|
||||||
|
|
||||||
if user.Password != "" {
|
if user.Password != "" {
|
||||||
passwd, err := bcrypt.GenerateFromPassword([]byte(model.Password), bcrypt.DefaultCost)
|
verifyErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(model.OldPassword))
|
||||||
|
if verifyErr != nil {
|
||||||
|
return nil, verifyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
passwd, err := bcrypt.GenerateFromPassword([]byte(model.NewPassword), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,21 @@
|
|||||||
|
|
||||||
import Navbar from "./Navbar.svelte";
|
import Navbar from "./Navbar.svelte";
|
||||||
|
|
||||||
|
|
||||||
import Home from "./routes/Home.svelte";
|
import Home from "./routes/Home.svelte";
|
||||||
import Posts from "./routes/Posts.svelte";
|
|
||||||
import Post from "./routes/Post.svelte";
|
import Login from "./routes/Auth/Login.svelte";
|
||||||
import Login from "./routes/Login.svelte";
|
import Logout from "./routes/Auth/Logout.svelte";
|
||||||
import Logout from "./routes/Logout.svelte";
|
import Register from "./routes/Auth/Register.svelte";
|
||||||
import Upload from "./routes/Upload.svelte";
|
|
||||||
import Edit from "./routes/Edit.svelte";
|
import Posts from "./routes/Post/Posts.svelte";
|
||||||
import Tags from "./routes/Tags.svelte";
|
import Post from "./routes/Post/Post.svelte";
|
||||||
import Register from "./routes/Register.svelte";
|
import Upload from "./routes/Post/Upload.svelte";
|
||||||
|
|
||||||
|
import Tags from "./routes/Tags/Tags.svelte";
|
||||||
|
import Tag from "./routes/Tags/Tag.svelte";
|
||||||
|
|
||||||
|
import Profile from "./routes/User/Profile.svelte";
|
||||||
|
|
||||||
export let url = "";
|
export let url = "";
|
||||||
let baseURL = window.BASE_URL;
|
let baseURL = window.BASE_URL;
|
||||||
@ -23,12 +29,13 @@
|
|||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
<Route path="/posts" component={Posts} />
|
<Route path="/posts" component={Posts} />
|
||||||
<Route path="/post/:id" component={Post} />
|
<Route path="/post/:id" component={Post} />
|
||||||
<Route path="/post/edit/:id" component={Edit} />
|
|
||||||
<Route path="/auth/login" component={Login} />
|
<Route path="/auth/login" component={Login} />
|
||||||
<Route path="/auth/logout" component={Logout} />
|
<Route path="/auth/logout" component={Logout} />
|
||||||
<Route path="/upload" component={Upload} />
|
<Route path="/upload" component={Upload} />
|
||||||
<Route path="/tags" component={Tags} />
|
<Route path="/tags" component={Tags} />
|
||||||
|
<Route path="/tags/:tag" component={Tag} />
|
||||||
<Route path="/auth/register" component={Register} />
|
<Route path="/auth/register" component={Register} />
|
||||||
|
<Route path="/user/profile" component={Profile} />
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
|
@ -46,6 +46,9 @@
|
|||||||
{#if loggedIn}
|
{#if loggedIn}
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
<Link to="/user/profile" class="button is-primary">
|
||||||
|
Profile
|
||||||
|
</Link>
|
||||||
<Link to="/auth/logout" class="button is-light">
|
<Link to="/auth/logout" class="button is-light">
|
||||||
Log out
|
Log out
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -41,6 +41,13 @@ export async function getTags() {
|
|||||||
const response = await axios.get(endpoint);
|
const response = await axios.get(endpoint);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getTag({ tag }) {
|
||||||
|
const endpoint = url + "/api/tag/" + tag;
|
||||||
|
const response = await axios.get(endpoint);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getTagAutocomplete() {
|
export async function getTagAutocomplete() {
|
||||||
const endpoint = url + "/api/tag/autocomplete";
|
const endpoint = url + "/api/tag/autocomplete";
|
||||||
const response = await axios.get(endpoint);
|
const response = await axios.get(endpoint);
|
||||||
@ -123,6 +130,7 @@ export async function postUpdate(id, { source_url, tags }) {
|
|||||||
})
|
})
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postDelete({ id }) {
|
export async function postDelete({ id }) {
|
||||||
const endpoint = url + "/api/post/" + id;
|
const endpoint = url + "/api/post/" + id;
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
@ -135,3 +143,56 @@ export async function postDelete({ id }) {
|
|||||||
})
|
})
|
||||||
return response.status == 200;
|
return response.status == 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUserProfile() {
|
||||||
|
const endpoint = url + "/api/user/profile";
|
||||||
|
const response = await axios({
|
||||||
|
url: endpoint,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + current_token,
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
console.log(response.data);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateTagNotes(id, { note }) {
|
||||||
|
const endpoint = url + "/api/tag/" + id + "/note";
|
||||||
|
const response = await axios({
|
||||||
|
url: endpoint,
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + current_token,
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
data: {
|
||||||
|
note
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateTag(id, { name, tagTypeId }) {
|
||||||
|
const endpoint = url + "/api/tag/" + id;
|
||||||
|
const response = await axios({
|
||||||
|
url: endpoint,
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + current_token,
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
data: {
|
||||||
|
name, tagTypeId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getTagTypes() {
|
||||||
|
const endpoint = url + "/api/tagtype";
|
||||||
|
const response = await axios.get(endpoint);
|
||||||
|
return response.data;
|
||||||
|
}
|
94
web/app/src/components/Tag/EditTagPanel.svelte
Normal file
94
web/app/src/components/Tag/EditTagPanel.svelte
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
import { Link, navigate } from "svelte-routing";
|
||||||
|
import { getTagTypes, updateTag } from "../../api";
|
||||||
|
|
||||||
|
export let tag;
|
||||||
|
export let data;
|
||||||
|
export let toggleRenameMenu;
|
||||||
|
export let onSubmit;
|
||||||
|
|
||||||
|
let tagTypes = [];
|
||||||
|
let form = {
|
||||||
|
name: "",
|
||||||
|
tagTypeId: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
tagTypes = await getTagTypes();
|
||||||
|
form.name = data.tagName;
|
||||||
|
let tagType = tagTypes.filter((x) => x.name == data.tagType);
|
||||||
|
form.tagTypeId = tagType[0].id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFormSubmit = async () => {
|
||||||
|
await updateTag(tag, form);
|
||||||
|
|
||||||
|
navigate("/tags/"+form.name);
|
||||||
|
|
||||||
|
toggleRenameMenu();
|
||||||
|
onSubmit(form.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={onFormSubmit}>
|
||||||
|
<div class="panel is-warning">
|
||||||
|
<p class="panel-heading">Edit Tag</p>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Name:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
bind:value={form.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Category:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="field">
|
||||||
|
<div class="select">
|
||||||
|
<select bind:value={form.tagTypeId}>
|
||||||
|
{#each tagTypes as tagType}
|
||||||
|
<option
|
||||||
|
value={tagType.id}
|
||||||
|
selected={form.tagTypeId === tagType.id}
|
||||||
|
>
|
||||||
|
{tagType.name}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Posts:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{data.postCount} (<Link to="/posts?tags={tag}">Browse</Link>)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<button class="button is-primary" type="submit">Submit</button>
|
||||||
|
<button on:click|preventDefault={toggleRenameMenu} class="button"
|
||||||
|
>Cancel</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
38
web/app/src/components/Tag/ViewTagPanel.svelte
Normal file
38
web/app/src/components/Tag/ViewTagPanel.svelte
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<script>
|
||||||
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
|
export let tag;
|
||||||
|
export let data;
|
||||||
|
export let toggleRenameMenu;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="panel is-primary">
|
||||||
|
<p class="panel-heading">Tag</p>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Name:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">{data.tagName}</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Category:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">{data.tagType}</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Posts:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{data.postCount} (<Link to="/posts?tags={tag}">Browse</Link>)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={toggleRenameMenu}
|
||||||
|
class="button is-primary">Rename</button
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
47
web/app/src/components/TagNotes/EditTagNotesPanel.svelte
Normal file
47
web/app/src/components/TagNotes/EditTagNotesPanel.svelte
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { updateTagNotes } from "../../api";
|
||||||
|
|
||||||
|
export let tag;
|
||||||
|
export let data;
|
||||||
|
export let toggleEditMenu;
|
||||||
|
export let onSubmit;
|
||||||
|
|
||||||
|
let form = {
|
||||||
|
note: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
form.note = data.tagNote;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFormSubmit = async () => {
|
||||||
|
await updateTagNotes(tag, form);
|
||||||
|
toggleEditMenu();
|
||||||
|
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={onFormSubmit}>
|
||||||
|
<div class="panel is-warning">
|
||||||
|
<p class="panel-heading">Edit Notes</p>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<textarea
|
||||||
|
bind:value={form.note}
|
||||||
|
class="textarea has-fixed-size"
|
||||||
|
/>
|
||||||
|
<div class="content" />
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<button type="submit" class="button is-primary">Save</button>
|
||||||
|
<button on:click|preventDefault={toggleEditMenu} class="button"
|
||||||
|
>Cancel</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
25
web/app/src/components/TagNotes/ViewTagNotesPanel.svelte
Normal file
25
web/app/src/components/TagNotes/ViewTagNotesPanel.svelte
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script>
|
||||||
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
export let toggleEditMenu;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="panel is-info">
|
||||||
|
<p class="panel-heading">Notes</p>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="content pre-line">
|
||||||
|
{data.tagNote}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<button on:click|preventDefault={toggleEditMenu} class="button is-primary">Edit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.pre-line {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,6 +6,7 @@
|
|||||||
@import '../node_modules/bulma/sass/grid/_all';
|
@import '../node_modules/bulma/sass/grid/_all';
|
||||||
@import '../node_modules/bulma/sass/helpers/_all';
|
@import '../node_modules/bulma/sass/helpers/_all';
|
||||||
@import '../node_modules/bulma/sass/layout/_all';
|
@import '../node_modules/bulma/sass/layout/_all';
|
||||||
|
@import '../node_modules/bytemd/dist/index.css';
|
||||||
|
|
||||||
.tile.is-multiline {
|
.tile.is-multiline {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { login } from "../api.js";
|
import { login } from "../../api.js";
|
||||||
import { navigate } from "svelte-routing";
|
import { navigate } from "svelte-routing";
|
||||||
|
|
||||||
let username = "";
|
let username = "";
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { token } from "../stores.js";
|
import { token } from "../../stores.js";
|
||||||
import { navigate } from "svelte-routing";
|
import { navigate } from "svelte-routing";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { register } from "../api.js";
|
import { register } from "../../api.js";
|
||||||
import { navigate } from "svelte-routing";
|
import { navigate } from "svelte-routing";
|
||||||
|
|
||||||
let username = "";
|
let username = "";
|
@ -1,97 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { getPost, postUpdate, getTagAutocomplete } from "../api.js";
|
|
||||||
import { navigate } from "svelte-routing";
|
|
||||||
import Tags from "svelte-tags-input";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { Link } from "svelte-routing";
|
|
||||||
import AuthRequired from "../AuthRequired.svelte";
|
|
||||||
|
|
||||||
export let id;
|
|
||||||
|
|
||||||
let image_path = "";
|
|
||||||
|
|
||||||
let form = {
|
|
||||||
source_url: "",
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const getData = async () => {
|
|
||||||
const data = await getPost({ id });
|
|
||||||
form.source_url = data.source_url;
|
|
||||||
form.tags = data.tags;
|
|
||||||
image_path = data.image_path;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTagChange = (value) => {
|
|
||||||
form.tags = value.detail.tags;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAutocomplete = async () => {
|
|
||||||
const list = await getTagAutocomplete();
|
|
||||||
return list;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async () => {
|
|
||||||
const response = await postUpdate(id, form);
|
|
||||||
navigate(`/post/${response.id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
getData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AuthRequired />
|
|
||||||
|
|
||||||
<section class="hero is-primary">
|
|
||||||
<div class="hero-body">
|
|
||||||
<p class="title">Edit Post: {id}</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<section class="section">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-one-third box">
|
|
||||||
<p>
|
|
||||||
<Link class="button is-primary" to="/post/{id}">Back</Link>
|
|
||||||
</p>
|
|
||||||
<form on:submit|preventDefault={onSubmit}>
|
|
||||||
<div class="field">
|
|
||||||
<label for="source" class="label">Source URL</label>
|
|
||||||
<div class="control">
|
|
||||||
<input
|
|
||||||
id="source"
|
|
||||||
class="input"
|
|
||||||
type="url"
|
|
||||||
placeholder="Source URL"
|
|
||||||
bind:value={form.source_url}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="tags" class="label">Tags</label>
|
|
||||||
<div class="control" id="tags">
|
|
||||||
<Tags
|
|
||||||
tags={form.tags}
|
|
||||||
addKeys={[9, 32]}
|
|
||||||
on:tags={onTagChange}
|
|
||||||
autoComplete={onAutocomplete}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button type="submit" class="button is-primary"
|
|
||||||
>Submit</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<figure class="image">
|
|
||||||
<img alt={id} src={image_path} />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
@ -1,9 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { getPost, postDelete } from "../api.js";
|
import { getPost, postDelete } from "../../api.js";
|
||||||
import { navigate } from "svelte-routing";
|
import { navigate } from "svelte-routing";
|
||||||
import EditPostPanel from "../EditPostPanel.svelte";
|
import EditPostPanel from "../../EditPostPanel.svelte";
|
||||||
import ViewPostPanel from "../ViewPostPanel.svelte";
|
import ViewPostPanel from "../../ViewPostPanel.svelte";
|
||||||
export let id;
|
export let id;
|
||||||
let post;
|
let post;
|
||||||
const getData = async () => {
|
const getData = async () => {
|
@ -1,12 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { getPostSearchTag, getTagAutocomplete } from "../../api.js";
|
||||||
import { getPostSearchTag, getTagAutocomplete } from "../api.js";
|
|
||||||
import { Link, navigate } from "svelte-routing";
|
import { Link, navigate } from "svelte-routing";
|
||||||
import TagLinkNumbered from "../TagLinkNumbered.svelte";
|
import TagLinkNumbered from "../../TagLinkNumbered.svelte";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import Tags from "svelte-tags-input";
|
import Tags from "svelte-tags-input";
|
||||||
import { add_attribute } from "svelte/internal";
|
import { paginate } from "../../simple-pagination.js";
|
||||||
import { paginate } from "../simple-pagination.js";
|
|
||||||
|
|
||||||
export let location;
|
export let location;
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { uploadBlob, postCreate, getTagAutocomplete } from "../api.js";
|
import { uploadBlob, postCreate, getTagAutocomplete } from "../../api.js";
|
||||||
import { navigate, Link } from "svelte-routing";
|
import { navigate, Link } from "svelte-routing";
|
||||||
import Tags from "svelte-tags-input";
|
import Tags from "svelte-tags-input";
|
||||||
import AuthRequired from "../AuthRequired.svelte";
|
import AuthRequired from "../../AuthRequired.svelte";
|
||||||
|
|
||||||
let currentProgress = 0;
|
let currentProgress = 0;
|
||||||
|
|
70
web/app/src/routes/Tags/Tag.svelte
Normal file
70
web/app/src/routes/Tags/Tag.svelte
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
import { getTag } from "../../api";
|
||||||
|
import EditTagNotesPanel from "../../components/TagNotes/EditTagNotesPanel.svelte";
|
||||||
|
import ViewTagNotesPanel from "../../components/TagNotes/ViewTagNotesPanel.svelte";
|
||||||
|
import ViewTagPanel from "../../components/Tag/ViewTagPanel.svelte";
|
||||||
|
import EditTagPanel from "../../components/Tag/EditTagPanel.svelte";
|
||||||
|
|
||||||
|
export let tag;
|
||||||
|
let data;
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
if (tag) {
|
||||||
|
data = await getTag({ tag });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let renameMenuShown = false;
|
||||||
|
const toggleRenameMenu = () => {
|
||||||
|
renameMenuShown = !renameMenuShown;
|
||||||
|
};
|
||||||
|
|
||||||
|
let editMenuShown = false;
|
||||||
|
const toggleEditMenu = () => {
|
||||||
|
editMenuShown = !editMenuShown;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTagSubmit = (newName) => {
|
||||||
|
tag = newName;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
{#if data}
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-one-third">
|
||||||
|
{#if renameMenuShown}
|
||||||
|
<EditTagPanel
|
||||||
|
{tag}
|
||||||
|
{data}
|
||||||
|
{toggleRenameMenu}
|
||||||
|
onSubmit={onTagSubmit}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<ViewTagPanel {tag} {data} {toggleRenameMenu} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="column is-two-thirds">
|
||||||
|
{#if editMenuShown}
|
||||||
|
<EditTagNotesPanel
|
||||||
|
{tag}
|
||||||
|
{data}
|
||||||
|
{toggleEditMenu}
|
||||||
|
onSubmit={getData}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<ViewTagNotesPanel {data} {toggleEditMenu} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getTags } from "../api";
|
import { getTags } from "../../api";
|
||||||
import { Link } from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
let tags = [];
|
let tags = [];
|
||||||
@ -29,7 +29,7 @@
|
|||||||
{#each tags as tag}
|
{#each tags as tag}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Link to="/posts?tags={tag.tagType}:{tag.tagName}">{tag.tagName}</Link>
|
<Link to="/tags/{tag.tagName}">{tag.tagName}</Link>
|
||||||
</td>
|
</td>
|
||||||
<td>{tag.tagType}</td>
|
<td>{tag.tagType}</td>
|
||||||
<td>{tag.postCount}</td>
|
<td>{tag.postCount}</td>
|
26
web/app/src/routes/User/Profile.svelte
Normal file
26
web/app/src/routes/User/Profile.svelte
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { getUserProfile } from "../../api.js";
|
||||||
|
import AuthRequired from "../../AuthRequired.svelte";
|
||||||
|
|
||||||
|
let user;
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
user = await getUserProfile();
|
||||||
|
};
|
||||||
|
onMount(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AuthRequired />
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
{#if user}
|
||||||
|
<h1 class="title">Welcome, {user.username}</h1>
|
||||||
|
<p>Email: {user.email}</p>
|
||||||
|
<p>Username: {user.username}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</section>
|
Loading…
Reference in New Issue
Block a user