mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-12-22 16:03:45 +00:00
feat: similarity search
This commit is contained in:
parent
03f7de692a
commit
064f61cc89
4
go.mod
4
go.mod
@ -3,14 +3,16 @@ module github.com/Damillora/Shioriko
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/corona10/goimagehash v1.0.3
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gin-contrib/cors v1.3.1
|
||||
github.com/gin-contrib/static v0.0.1
|
||||
github.com/gin-gonic/gin v1.7.1
|
||||
github.com/go-playground/validator/v10 v10.6.0
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/h2non/bimg v1.1.5
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
|
||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e
|
||||
gorm.io/driver/postgres v1.1.0
|
||||
gorm.io/gorm v1.21.9
|
||||
)
|
||||
|
9
go.sum
9
go.sum
@ -40,6 +40,8 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/corona10/goimagehash v1.0.3 h1:NZM518aKLmoNluluhfHGxT3LGOnrojrxhGn63DR/CZA=
|
||||
github.com/corona10/goimagehash v1.0.3/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -47,6 +49,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
@ -273,6 +277,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
@ -392,6 +398,9 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk=
|
||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -1,17 +1,27 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
"image/jpeg"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "golang.org/x/image/webp"
|
||||
|
||||
"github.com/Damillora/Shioriko/pkg/config"
|
||||
"github.com/Damillora/Shioriko/pkg/database"
|
||||
"github.com/Damillora/Shioriko/pkg/middleware"
|
||||
"github.com/Damillora/Shioriko/pkg/models"
|
||||
"github.com/Damillora/Shioriko/pkg/services"
|
||||
"github.com/corona10/goimagehash"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/h2non/bimg"
|
||||
)
|
||||
|
||||
func InitializeBlobRoutes(g *gin.Engine) {
|
||||
@ -67,6 +77,87 @@ func uploadBlob(c *gin.Context) {
|
||||
os.Mkdir(filepath.Join(dataDir, "thumbnail", folder1, folder2), 0755)
|
||||
}
|
||||
|
||||
previewFilename := id + ".jpg"
|
||||
previewFilePath := filepath.Join(dataDir, "preview", folder1, folder2, previewFilename)
|
||||
thumbnailFilePath := filepath.Join(dataDir, "thumbnail", folder1, folder2, previewFilename)
|
||||
|
||||
fileObj, _ := file.Open()
|
||||
originalImage, _, err := image.Decode(fileObj)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
width := originalImage.Bounds().Dx()
|
||||
height := originalImage.Bounds().Dy()
|
||||
|
||||
hash, err := goimagehash.PerceptionHash(originalImage)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
hashInt := hash.GetHash()
|
||||
|
||||
similarPosts, err := services.SimilaritySearch(hashInt)
|
||||
|
||||
hashSlice := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(hashSlice, hashInt)
|
||||
|
||||
previewImage := imaging.Resize(originalImage, 1000, 0, imaging.Lanczos)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
thumbnailImage := imaging.Resize(originalImage, 300, 0, imaging.Lanczos)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
previewFile, err := os.Create(previewFilePath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
thumbnailFile, err := os.Create(thumbnailFilePath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = jpeg.Encode(previewFile, previewImage, nil)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
err = jpeg.Encode(thumbnailFile, thumbnailImage, nil)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
filename := id + filepath.Ext(file.Filename)
|
||||
filePath := filepath.Join(dataDir, folder1, folder2, filename)
|
||||
err = c.SaveUploadedFile(file, filePath)
|
||||
@ -75,60 +166,7 @@ func uploadBlob(c *gin.Context) {
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
previewFilename := id + ".webp"
|
||||
previewFilePath := filepath.Join(dataDir, "preview", folder1, folder2, previewFilename)
|
||||
thumbnailFilePath := filepath.Join(dataDir, "thumbnail", folder1, folder2, previewFilename)
|
||||
|
||||
buffer, err := bimg.Read(filePath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
image := bimg.NewImage(buffer)
|
||||
metadata, err := image.Metadata()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
width := metadata.Size.Width
|
||||
height := metadata.Size.Height
|
||||
|
||||
previewImage, err := image.Resize(1000, 0)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
thumbnailImage, err := image.Resize(300, 0)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
err = bimg.Write(previewFilePath, previewImage)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
err = bimg.Write(thumbnailFilePath, thumbnailImage)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
blob := database.Blob{
|
||||
@ -138,13 +176,29 @@ func uploadBlob(c *gin.Context) {
|
||||
ThumbnailFilePath: filepath.Join("thumbnail", folder1, folder2, previewFilename),
|
||||
Width: width,
|
||||
Height: height,
|
||||
Hash1: hashSlice[0:2],
|
||||
Hash2: hashSlice[2:4],
|
||||
Hash3: hashSlice[4:6],
|
||||
Hash4: hashSlice[6:8],
|
||||
}
|
||||
|
||||
database.DB.Create(&blob)
|
||||
|
||||
c.JSON(http.StatusOK, models.BlobResponse{
|
||||
ID: id,
|
||||
Width: width,
|
||||
Height: height,
|
||||
})
|
||||
if len(similarPosts) > 0 {
|
||||
c.JSON(http.StatusOK,
|
||||
models.BlobSimilarResponse{
|
||||
ID: id,
|
||||
Width: width,
|
||||
Height: height,
|
||||
Similar: similarPosts,
|
||||
})
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusOK, models.BlobResponse{
|
||||
ID: id,
|
||||
Width: width,
|
||||
Height: height,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ type Blob struct {
|
||||
ThumbnailFilePath string
|
||||
Width int
|
||||
Height int
|
||||
Hash1 []byte
|
||||
Hash2 []byte
|
||||
Hash3 []byte
|
||||
Hash4 []byte
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
@ -17,3 +17,8 @@ type PostListItem struct {
|
||||
ImageThumbnailPath string `json:"thumbnail_path"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type PostSimilarityListItem struct {
|
||||
ID string `json:"id"`
|
||||
Distance int `json:"distance"`
|
||||
}
|
||||
|
@ -20,6 +20,12 @@ type BlobResponse struct {
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
type BlobSimilarResponse struct {
|
||||
ID string `json:"id"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Similar []PostSimilarityListItem `json:"similar"`
|
||||
}
|
||||
type PostPaginationResponse struct {
|
||||
CurrentPage int `json:"currentPage"`
|
||||
PostCount int `json:"postCount"`
|
||||
|
@ -2,7 +2,6 @@ package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/Damillora/Shioriko/pkg/config"
|
||||
@ -13,7 +12,7 @@ import (
|
||||
|
||||
func Login(username string, password string) *database.User {
|
||||
user := GetUserFromUsername(username)
|
||||
log.Println(user.Username)
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
||||
if err != nil {
|
||||
return nil
|
||||
|
48
pkg/services/blob.go
Normal file
48
pkg/services/blob.go
Normal file
@ -0,0 +1,48 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/Damillora/Shioriko/pkg/database"
|
||||
"github.com/Damillora/Shioriko/pkg/models"
|
||||
"github.com/corona10/goimagehash"
|
||||
)
|
||||
|
||||
func SimilaritySearch(originalHashInt uint64) ([]models.PostSimilarityListItem, error) {
|
||||
originalHash := goimagehash.NewImageHash(originalHashInt, goimagehash.PHash)
|
||||
|
||||
hashSlice := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(hashSlice, originalHashInt)
|
||||
|
||||
var blobs []database.Blob
|
||||
database.DB.
|
||||
Joins("inner join posts on posts.blob_id = blobs.id").
|
||||
Where("hash1 = ?", hashSlice[0:2]).
|
||||
Or("hash2 = ?", hashSlice[2:4]).
|
||||
Or("hash3 = ?", hashSlice[4:6]).
|
||||
Or("hash4 = ?", hashSlice[6:8]).
|
||||
Find(&blobs)
|
||||
|
||||
posts := make([]models.PostSimilarityListItem, 0)
|
||||
for _, blob := range blobs {
|
||||
hash2 := append(blob.Hash1, blob.Hash2...)
|
||||
hash3 := append(hash2, blob.Hash3...)
|
||||
hash4 := append(hash3, blob.Hash4...)
|
||||
|
||||
hashInt := binary.LittleEndian.Uint64(hash4)
|
||||
|
||||
compareHash := goimagehash.NewImageHash(hashInt, goimagehash.PHash)
|
||||
|
||||
distance, _ := compareHash.Distance(originalHash)
|
||||
if distance < 1 {
|
||||
var post database.Post
|
||||
database.DB.Where("blob_id = ?", blob.ID).Find(&post)
|
||||
posts = append(posts, models.PostSimilarityListItem{
|
||||
ID: post.ID,
|
||||
Distance: distance,
|
||||
})
|
||||
}
|
||||
}
|
||||
return posts, nil
|
||||
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/Damillora/Shioriko/pkg/database"
|
||||
"github.com/Damillora/Shioriko/pkg/models"
|
||||
"github.com/google/uuid"
|
||||
@ -18,7 +16,7 @@ func GetPostAll(page int) []database.Post {
|
||||
|
||||
func GetPostTags(page int, tagSyntax []string) []database.Post {
|
||||
tags, err := ParseReadTags(tagSyntax)
|
||||
log.Println(tags)
|
||||
|
||||
if err != nil {
|
||||
return []database.Post{}
|
||||
}
|
||||
|
@ -102,4 +102,16 @@ 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({
|
||||
url: endpoint,
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + current_token,
|
||||
},
|
||||
withCredentials: true,
|
||||
})
|
||||
return response.status == 200;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import TagLink from "../TagLink.svelte";
|
||||
import { getPost, postCreate } from "../api.js";
|
||||
import { Link } from "svelte-routing";
|
||||
import { getPost, postCreate, postDelete } from "../api.js";
|
||||
import { Link, navigate } from "svelte-routing";
|
||||
export let id;
|
||||
let post;
|
||||
const getData = async () => {
|
||||
@ -20,6 +20,19 @@
|
||||
onMount(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
let modal_shown = false;
|
||||
|
||||
const deletePost = async () => {
|
||||
toggleModal();
|
||||
const success = await postDelete({ id });
|
||||
if (success) {
|
||||
navigate("/posts");
|
||||
}
|
||||
};
|
||||
const toggleModal = () => {
|
||||
modal_shown = !modal_shown;
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="hero is-primary">
|
||||
@ -42,6 +55,10 @@
|
||||
class="button is-primary"
|
||||
to="/post/edit/{post.id}">Edit</Link
|
||||
>
|
||||
<button
|
||||
on:click|preventDefault={toggleModal}
|
||||
class="button is-danger">Delete</button
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
Uploader: {post.uploader}
|
||||
@ -87,4 +104,27 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="modal" class:is-active={modal_shown}>
|
||||
<div class="modal-background" />
|
||||
<div class="modal-content">
|
||||
Are you sure to delete post {post.id}?
|
||||
</div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Delete post?</p>
|
||||
<button class="delete" aria-label="close" />
|
||||
</header>
|
||||
<section class="modal-card-body" />
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
on:click|preventDefault={deletePost}
|
||||
class="button is-danger">Delete</button
|
||||
>
|
||||
<button class="button" on:click|preventDefault={toggleModal}
|
||||
>Cancel</button
|
||||
>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -1,12 +1,13 @@
|
||||
<script>
|
||||
import { uploadBlob, postCreate } from "../api.js";
|
||||
import { navigate } from "svelte-routing";
|
||||
import { navigate, Link } from "svelte-routing";
|
||||
import Tags from "svelte-tags-input";
|
||||
import AuthRequired from "../AuthRequired.svelte";
|
||||
|
||||
let currentProgress = 0;
|
||||
|
||||
let fileName = "";
|
||||
let similar = [];
|
||||
|
||||
let form = {
|
||||
blob_id: "",
|
||||
@ -21,9 +22,13 @@
|
||||
|
||||
const onFileChange = async (e) => {
|
||||
fileName = "";
|
||||
similar = [];
|
||||
var file = e.target.files[0];
|
||||
if (file) {
|
||||
var response = await uploadBlob({ file, onProgress });
|
||||
if (response.similar) {
|
||||
similar = response.similar;
|
||||
}
|
||||
form.blob_id = response.id;
|
||||
fileName = file.name;
|
||||
}
|
||||
@ -31,7 +36,7 @@
|
||||
|
||||
const onTagChange = (value) => {
|
||||
form.tags = value.detail.tags;
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
const response = await postCreate(form);
|
||||
@ -75,6 +80,17 @@
|
||||
{#if fileName !== ""}
|
||||
<p class="help">{fileName} uploaded</p>
|
||||
{/if}
|
||||
{#if similar.length > 0}
|
||||
<p class="help">
|
||||
Similar posts:
|
||||
{#each similar as post, i}
|
||||
<Link to="/post/{post.id}">{post.id}</Link>
|
||||
{#if i < similar.length - 1}
|
||||
,
|
||||
{/if}
|
||||
{/each}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="source" class="label">Source URL</label>
|
||||
@ -91,7 +107,7 @@
|
||||
<div class="field">
|
||||
<label for="tags" class="label">Tags</label>
|
||||
<div class="control" id="tags">
|
||||
<Tags addKeys={[9,32]} on:tags={onTagChange} />
|
||||
<Tags addKeys={[9, 32]} on:tags={onTagChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
|
Loading…
Reference in New Issue
Block a user