mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-12-22 16:03:45 +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) {
|
||||
_, 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")
|
||||
|
||||
var model models.PostUpdateModel
|
||||
@ -194,6 +203,15 @@ func postUpdate(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")
|
||||
|
||||
err := services.DeletePost(id)
|
||||
|
@ -3,6 +3,8 @@ package app
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Damillora/Shioriko/pkg/middleware"
|
||||
"github.com/Damillora/Shioriko/pkg/models"
|
||||
"github.com/Damillora/Shioriko/pkg/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -11,8 +13,14 @@ func InitializeTagRoutes(g *gin.Engine) {
|
||||
unprotected := g.Group("/api/tag")
|
||||
{
|
||||
unprotected.GET("/", tagGet)
|
||||
unprotected.GET("/:tag", tagGetOne)
|
||||
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) {
|
||||
@ -20,7 +28,94 @@ func tagGet(c *gin.Context) {
|
||||
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) {
|
||||
tags := services.GetTagAutocomplete()
|
||||
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) {
|
||||
|
||||
unprotected := g.Group(("/api/tagtype"))
|
||||
{
|
||||
unprotected.GET("/", tagTypeGet)
|
||||
}
|
||||
protected := g.Group("/api/tagtype").Use(middleware.AuthMiddleware())
|
||||
{
|
||||
protected.GET("/", tagTypeGet)
|
||||
protected.POST("/create", tagTypeCreate)
|
||||
protected.DELETE("/:id", tagTypeDelete)
|
||||
}
|
||||
|
@ -11,5 +11,6 @@ type Tag struct {
|
||||
UpdatedAt time.Time
|
||||
TagTypeID uint
|
||||
TagType TagType
|
||||
Note string `gorm:"type:text"`
|
||||
Posts []Post `gorm:"many2many:post_tags"`
|
||||
}
|
||||
|
@ -7,9 +7,10 @@ type UserCreateModel struct {
|
||||
}
|
||||
|
||||
type UserUpdateModel struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
OldPassword string `json:"oldPassword"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
type TagTypeCreateModel struct {
|
||||
@ -26,6 +27,9 @@ type TagUpdateModel struct {
|
||||
TagTypeID uint `json:"tagTypeId" validate:"required"`
|
||||
}
|
||||
|
||||
type TagNoteUpdateModel struct {
|
||||
Note string `json:"note" validate:"required"`
|
||||
}
|
||||
type PostCreateModel struct {
|
||||
BlobID string `json:"blob_id" validate:"required"`
|
||||
SourceURL string `json:"source_url"`
|
||||
|
@ -10,3 +10,11 @@ type PostReadModel struct {
|
||||
Height int `json:"height"`
|
||||
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)
|
||||
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 {
|
||||
var tags []string
|
||||
result := database.DB.Model(&database.Tag{}).
|
||||
@ -134,6 +152,7 @@ func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
|
||||
return nil, errors.New("Malformed tag syntax")
|
||||
}
|
||||
}
|
||||
|
||||
func FindTag(tagSyntax string) (*database.Tag, error) {
|
||||
tagFields := strings.Split(tagSyntax, ":")
|
||||
var tagName string
|
||||
@ -173,3 +192,36 @@ func ParseReadTags(tags []string) ([]database.Tag, error) {
|
||||
}
|
||||
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
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3,15 +3,21 @@
|
||||
|
||||
import Navbar from "./Navbar.svelte";
|
||||
|
||||
|
||||
import Home from "./routes/Home.svelte";
|
||||
import Posts from "./routes/Posts.svelte";
|
||||
import Post from "./routes/Post.svelte";
|
||||
import Login from "./routes/Login.svelte";
|
||||
import Logout from "./routes/Logout.svelte";
|
||||
import Upload from "./routes/Upload.svelte";
|
||||
import Edit from "./routes/Edit.svelte";
|
||||
import Tags from "./routes/Tags.svelte";
|
||||
import Register from "./routes/Register.svelte";
|
||||
|
||||
import Login from "./routes/Auth/Login.svelte";
|
||||
import Logout from "./routes/Auth/Logout.svelte";
|
||||
import Register from "./routes/Auth/Register.svelte";
|
||||
|
||||
import Posts from "./routes/Post/Posts.svelte";
|
||||
import Post from "./routes/Post/Post.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 = "";
|
||||
let baseURL = window.BASE_URL;
|
||||
@ -23,12 +29,13 @@
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/posts" component={Posts} />
|
||||
<Route path="/post/:id" component={Post} />
|
||||
<Route path="/post/edit/:id" component={Edit} />
|
||||
<Route path="/auth/login" component={Login} />
|
||||
<Route path="/auth/logout" component={Logout} />
|
||||
<Route path="/upload" component={Upload} />
|
||||
<Route path="/tags" component={Tags} />
|
||||
<Route path="/tags/:tag" component={Tag} />
|
||||
<Route path="/auth/register" component={Register} />
|
||||
<Route path="/user/profile" component={Profile} />
|
||||
</div>
|
||||
</Router>
|
||||
|
||||
|
@ -46,6 +46,9 @@
|
||||
{#if loggedIn}
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<Link to="/user/profile" class="button is-primary">
|
||||
Profile
|
||||
</Link>
|
||||
<Link to="/auth/logout" class="button is-light">
|
||||
Log out
|
||||
</Link>
|
||||
|
@ -41,6 +41,13 @@ export async function getTags() {
|
||||
const response = await axios.get(endpoint);
|
||||
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() {
|
||||
const endpoint = url + "/api/tag/autocomplete";
|
||||
const response = await axios.get(endpoint);
|
||||
@ -123,6 +130,7 @@ export async function postUpdate(id, { source_url, tags }) {
|
||||
})
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function postDelete({ id }) {
|
||||
const endpoint = url + "/api/post/" + id;
|
||||
const response = await axios({
|
||||
@ -134,4 +142,57 @@ export async function postDelete({ id }) {
|
||||
withCredentials: true,
|
||||
})
|
||||
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/helpers/_all';
|
||||
@import '../node_modules/bulma/sass/layout/_all';
|
||||
@import '../node_modules/bytemd/dist/index.css';
|
||||
|
||||
.tile.is-multiline {
|
||||
flex-wrap: wrap;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { login } from "../api.js";
|
||||
import { login } from "../../api.js";
|
||||
import { navigate } from "svelte-routing";
|
||||
|
||||
let username = "";
|
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { token } from "../stores.js";
|
||||
import { token } from "../../stores.js";
|
||||
import { navigate } from "svelte-routing";
|
||||
import { onMount } from "svelte";
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { register } from "../api.js";
|
||||
import { register } from "../../api.js";
|
||||
import { navigate } from "svelte-routing";
|
||||
|
||||
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>
|
||||
import { onMount } from "svelte";
|
||||
import { getPost, postDelete } from "../api.js";
|
||||
import { getPost, postDelete } from "../../api.js";
|
||||
import { navigate } from "svelte-routing";
|
||||
import EditPostPanel from "../EditPostPanel.svelte";
|
||||
import ViewPostPanel from "../ViewPostPanel.svelte";
|
||||
import EditPostPanel from "../../EditPostPanel.svelte";
|
||||
import ViewPostPanel from "../../ViewPostPanel.svelte";
|
||||
export let id;
|
||||
let post;
|
||||
const getData = async () => {
|
@ -1,12 +1,10 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { getPostSearchTag, getTagAutocomplete } from "../api.js";
|
||||
import { getPostSearchTag, getTagAutocomplete } from "../../api.js";
|
||||
import { Link, navigate } from "svelte-routing";
|
||||
import TagLinkNumbered from "../TagLinkNumbered.svelte";
|
||||
import TagLinkNumbered from "../../TagLinkNumbered.svelte";
|
||||
import queryString from "query-string";
|
||||
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;
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script>
|
||||
import { uploadBlob, postCreate, getTagAutocomplete } from "../api.js";
|
||||
import { uploadBlob, postCreate, getTagAutocomplete } from "../../api.js";
|
||||
import { navigate, Link } from "svelte-routing";
|
||||
import Tags from "svelte-tags-input";
|
||||
import AuthRequired from "../AuthRequired.svelte";
|
||||
import AuthRequired from "../../AuthRequired.svelte";
|
||||
|
||||
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>
|
||||
import { getTags } from "../api";
|
||||
import { getTags } from "../../api";
|
||||
import { Link } from "svelte-routing";
|
||||
|
||||
let tags = [];
|
||||
@ -29,7 +29,7 @@
|
||||
{#each tags as tag}
|
||||
<tr>
|
||||
<td>
|
||||
<Link to="/posts?tags={tag.tagType}:{tag.tagName}">{tag.tagName}</Link>
|
||||
<Link to="/tags/{tag.tagName}">{tag.tagName}</Link>
|
||||
</td>
|
||||
<td>{tag.tagType}</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