mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-11-22 04:17:33 +00:00
feat: functioning upload frontend
This commit is contained in:
parent
dbff5649d7
commit
f04575bc5c
@ -14,7 +14,7 @@ func InitializeFrontendRoutes(g *gin.Engine) {
|
|||||||
func frontendHome(c *gin.Context) {
|
func frontendHome(c *gin.Context) {
|
||||||
baseURL := config.CurrentConfig.BaseURL
|
baseURL := config.CurrentConfig.BaseURL
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||||
"title": "Home",
|
"title": "Shioriko",
|
||||||
"base_url": baseURL,
|
"base_url": baseURL,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -14,24 +14,35 @@ func GetTagAll() []database.Tag {
|
|||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
|
func CreateOrUpdateTagGeneric(tagName string) (*database.Tag, error) {
|
||||||
tagFields := strings.Split(tagSyntax, ":")
|
var tag database.Tag
|
||||||
var tagName string
|
result := database.DB.Where("name = ?", tagName).First(&tag)
|
||||||
|
if result.Error != nil {
|
||||||
var tagType database.TagType
|
var tagType database.TagType
|
||||||
if len(tagFields) == 1 {
|
|
||||||
tagName = tagFields[0]
|
|
||||||
database.DB.Where("name = ?", "general").First(&tagType)
|
database.DB.Where("name = ?", "general").First(&tagType)
|
||||||
} else if len(tagFields) == 2 {
|
|
||||||
tagName = tagFields[1]
|
tag = database.Tag{
|
||||||
result := database.DB.Where("name = ?", tagFields[0]).First(&tagType)
|
ID: uuid.NewString(),
|
||||||
|
Name: tagName,
|
||||||
|
TagTypeID: tagType.ID,
|
||||||
|
}
|
||||||
|
result = database.DB.Create(&tag)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return nil, errors.New("Malformed tag syntax")
|
|
||||||
}
|
}
|
||||||
|
return &tag, nil
|
||||||
|
}
|
||||||
|
func CreateOrUpdateTagComplex(tagName string, tagTypeString string) (*database.Tag, error) {
|
||||||
var tag database.Tag
|
var tag database.Tag
|
||||||
result := database.DB.Where("name = ? AND tag_type_id = ? ", tagName, tagType.ID).First(&tag)
|
var tagType database.TagType
|
||||||
|
result := database.DB.Where("name = ?", tagTypeString).First(&tagType)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
result = database.DB.Where("name = ? AND tag_type_id = ? ", tagName, tagType.ID).First(&tag)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
tag = database.Tag{
|
tag = database.Tag{
|
||||||
@ -47,6 +58,21 @@ func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
|
|||||||
}
|
}
|
||||||
return &tag, nil
|
return &tag, nil
|
||||||
}
|
}
|
||||||
|
func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
|
||||||
|
tagFields := strings.Split(tagSyntax, ":")
|
||||||
|
var tagName string
|
||||||
|
var tagType string
|
||||||
|
if len(tagFields) == 1 {
|
||||||
|
tagName = tagFields[0]
|
||||||
|
return CreateOrUpdateTagGeneric(tagName)
|
||||||
|
} else if len(tagFields) == 2 {
|
||||||
|
tagType = tagFields[0]
|
||||||
|
tagName = tagFields[1]
|
||||||
|
return CreateOrUpdateTagComplex(tagName, tagType)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Malformed tag syntax")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetTag(tagSyntax string) (*database.Tag, error) {
|
func GetTag(tagSyntax string) (*database.Tag, error) {
|
||||||
tagFields := strings.Split(tagSyntax, ":")
|
tagFields := strings.Split(tagSyntax, ":")
|
||||||
|
@ -18,8 +18,14 @@
|
|||||||
"svelte": "^3.0.0"
|
"svelte": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"bulma": "^0.9.2",
|
||||||
|
"node-sass": "^6.0.0",
|
||||||
|
"postcss": "^8.2.14",
|
||||||
"query-string": "^7.0.0",
|
"query-string": "^7.0.0",
|
||||||
"sirv-cli": "^1.0.0",
|
"sirv-cli": "^1.0.0",
|
||||||
"svelte-routing": "^1.6.0"
|
"svelte-preprocess": "^4.7.3",
|
||||||
|
"svelte-routing": "^1.6.0",
|
||||||
|
"svelte-tags-input": "^2.7.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import resolve from '@rollup/plugin-node-resolve';
|
|||||||
import livereload from 'rollup-plugin-livereload';
|
import livereload from 'rollup-plugin-livereload';
|
||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from 'rollup-plugin-terser';
|
||||||
import css from 'rollup-plugin-css-only';
|
import css from 'rollup-plugin-css-only';
|
||||||
|
import sveltePreprocess from 'svelte-preprocess'
|
||||||
|
|
||||||
const production = !process.env.ROLLUP_WATCH;
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
@ -34,18 +35,30 @@ export default {
|
|||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
format: 'iife',
|
format: 'iife',
|
||||||
name: 'app',
|
name: 'app',
|
||||||
file: '../static/bundle.js'
|
dir: "../static",
|
||||||
|
assetFileNames: 'bundle.css',
|
||||||
|
entryFileNames: 'bundle.js'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
svelte({
|
svelte({
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
// enable run-time checks when not in production
|
// enable run-time checks when not in production
|
||||||
dev: !production
|
dev: !production
|
||||||
}
|
},
|
||||||
|
|
||||||
|
preprocess: sveltePreprocess({
|
||||||
|
sourceMap: !production,
|
||||||
|
scss: {
|
||||||
|
includePaths: [
|
||||||
|
'node_modules',
|
||||||
|
'src'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
// we'll extract any component CSS out into
|
// we'll extract any component CSS out into
|
||||||
// a separate file - better for performance
|
// a separate file - better for performance
|
||||||
css({ output: '../static/bundle.css' }),
|
css({ name: "bundle" }),
|
||||||
|
|
||||||
// If you have external dependencies installed from
|
// If you have external dependencies installed from
|
||||||
// npm, you'll most likely need these plugins. In
|
// npm, you'll most likely need these plugins. In
|
||||||
|
@ -9,12 +9,10 @@
|
|||||||
import Login from "./routes/Login.svelte";
|
import Login from "./routes/Login.svelte";
|
||||||
import Logout from "./routes/Logout.svelte";
|
import Logout from "./routes/Logout.svelte";
|
||||||
import Tag from "./routes/Tag.svelte";
|
import Tag from "./routes/Tag.svelte";
|
||||||
|
import Upload from "./routes/Upload.svelte";
|
||||||
|
|
||||||
|
|
||||||
export let url = "";
|
export let url = "";
|
||||||
let baseURL = window.BASE_URL;
|
let baseURL = window.BASE_URL;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Router {url}>
|
<Router {url}>
|
||||||
@ -26,5 +24,25 @@
|
|||||||
<Route path="/post/:id" component={Post} />
|
<Route path="/post/:id" component={Post} />
|
||||||
<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} />
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
|
<style global lang="scss">
|
||||||
|
@import "./main.scss";
|
||||||
|
|
||||||
|
#tags .svelte-tags-input-tag {
|
||||||
|
background: $primary;
|
||||||
|
color: $text-invert;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tags .svelte-tags-input-layout {
|
||||||
|
@extend .input;
|
||||||
|
height: inherit;
|
||||||
|
|
||||||
|
& .svelte-tags-input {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
font-size: 13.3333px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
106
web/app/src/PostPaginator.svelte
Normal file
106
web/app/src/PostPaginator.svelte
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<script>
|
||||||
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
|
export let posts = [];
|
||||||
|
export let page = 1;
|
||||||
|
export let totalPages = 1;
|
||||||
|
export let handlePage = (i) => { };
|
||||||
|
export let url = "/posts";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||||
|
{#if page > 1}
|
||||||
|
<Link
|
||||||
|
on:click={handlePage(page - 1)}
|
||||||
|
to="{url}?page={page - 1}"
|
||||||
|
class="pagination-previous"
|
||||||
|
aria-label="Previous">Previous</Link
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{#if page < totalPages}
|
||||||
|
<Link
|
||||||
|
on:click={handlePage(page + 1)}
|
||||||
|
to="{url}?page={page + 1}"
|
||||||
|
class="pagination-next"
|
||||||
|
aria-label="Next">Next</Link
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
<ul class="pagination-list">
|
||||||
|
{#if page > 3}
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
on:click={handlePage(1)}
|
||||||
|
to="{url}?page={1}"
|
||||||
|
class="pagination-link"
|
||||||
|
aria-label="Goto page 1">1</Link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="pagination-ellipsis">…</span>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
|
||||||
|
{#if i >= 1 && i <= totalPages}
|
||||||
|
{#if i == page}
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
on:click={handlePage(i)}
|
||||||
|
to="{url}?page={i}"
|
||||||
|
class="pagination-link is-current"
|
||||||
|
aria-label="Goto page {i}">{i}</Link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{:else}
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
on:click={handlePage(i)}
|
||||||
|
to="{url}?page={i}"
|
||||||
|
class="pagination-link"
|
||||||
|
aria-label="Goto page {i}">{i}</Link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{#if totalPages - page > 2}
|
||||||
|
<li>
|
||||||
|
<span class="pagination-ellipsis">…</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
on:click={handlePage(totalPages)}
|
||||||
|
to="{url}?page={totalPages}"
|
||||||
|
class="pagination-link"
|
||||||
|
aria-label="Goto page {totalPages}"
|
||||||
|
>{totalPages}</Link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
{#each posts as post (post.id)}
|
||||||
|
<div class="column is-one-quarter card">
|
||||||
|
<div class="card-image">
|
||||||
|
<figure class="image">
|
||||||
|
<Link to="/post/{post.id}">
|
||||||
|
<img alt={post.id} src={post.image_path} />
|
||||||
|
</Link>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
{#each post.tags as tag (tag)}
|
||||||
|
<p>
|
||||||
|
<Link to="/tag/{tag}">{tag}</Link>
|
||||||
|
</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -1,41 +1,78 @@
|
|||||||
import { token } from "./stores.js"
|
import { token } from "./stores.js"
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
let url = window.BASE_URL;
|
let url = window.BASE_URL;
|
||||||
let current_token;
|
let current_token;
|
||||||
const unsub_token = token.subscribe(value => {
|
token.subscribe(value => {
|
||||||
current_token = token;
|
current_token = value;
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function login({ username, password }) {
|
export async function login({ username, password }) {
|
||||||
const endpoint = url + "/api/auth/login";
|
const endpoint = url + "/api/auth/login";
|
||||||
const response = await fetch(endpoint, {
|
const response = await axios({
|
||||||
|
url: endpoint,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
data: JSON.stringify({
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const data = await response.json();
|
token.set(response.data.token);
|
||||||
token.set(data.token);
|
return response.data;
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPosts({ page }) {
|
export async function getPosts({ page }) {
|
||||||
const endpoint = url + "/api/post?page=" + page;
|
const endpoint = url + "/api/post?page=" + page;
|
||||||
const response = await fetch(endpoint);
|
const response = await axios.get(endpoint);
|
||||||
const data = await response.json();
|
return response.data;
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPostsTag({ page, tag }) {
|
export async function getPostsTag({ page, tag }) {
|
||||||
const endpoint = url + "/api/post/tag/" + tag + "?page=" + page;
|
const endpoint = url + "/api/post/tag/" + tag + "?page=" + page;
|
||||||
const response = await fetch(endpoint);
|
const response = await axios(endpoint);
|
||||||
const data = await response.json();
|
return response.data;
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPost({ id }) {
|
export async function getPost({ id }) {
|
||||||
const endpoint = url + "/api/post/" + id;
|
const endpoint = url + "/api/post/" + id;
|
||||||
const response = await fetch(endpoint);
|
const response = await axios(endpoint);
|
||||||
const data = await response.json();
|
return response.data;
|
||||||
return data;
|
}
|
||||||
|
|
||||||
|
export async function uploadBlob({ file, onProgress }) {
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
const endpoint = url + "/api/blob/upload";
|
||||||
|
const response = await axios({
|
||||||
|
url: endpoint,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + current_token,
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
data: formData,
|
||||||
|
onUploadProgress: e => {
|
||||||
|
if (onProgress) {
|
||||||
|
onProgress(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postCreate({ blob_id, source_url, tags }) {
|
||||||
|
const endpoint = url + "/api/post/create";
|
||||||
|
const response = await axios({
|
||||||
|
url: endpoint,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + current_token,
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
data: {
|
||||||
|
blob_id, source_url, tags
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return response.data;
|
||||||
}
|
}
|
9
web/app/src/main.scss
Normal file
9
web/app/src/main.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
@import "../node_modules/bulma/sass/utilities/_all";
|
||||||
|
@import "../node_modules/bulma/sass/base/_all";
|
||||||
|
@import "../node_modules/bulma/sass/elements/_all";
|
||||||
|
@import "../node_modules/bulma/sass/form/_all";
|
||||||
|
@import "../node_modules/bulma/sass/components/_all";
|
||||||
|
@import "../node_modules/bulma/sass/grid/_all";
|
||||||
|
@import "../node_modules/bulma/sass/helpers/_all";
|
||||||
|
@import "../node_modules/bulma/sass/layout/_all";
|
@ -3,6 +3,7 @@
|
|||||||
import { getPosts } from "../api.js";
|
import { getPosts } from "../api.js";
|
||||||
import { Link } from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
import PostPaginator from "../PostPaginator.svelte";
|
||||||
|
|
||||||
export let location;
|
export let location;
|
||||||
|
|
||||||
@ -39,99 +40,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section">
|
<PostPaginator url="/posts" posts={posts} page={page} totalPages={totalPages} handlePage={handlePage} />
|
||||||
<div class="container">
|
|
||||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
|
||||||
{#if page > 1}
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(page - 1)}
|
|
||||||
to="/posts?page={page - 1}"
|
|
||||||
class="pagination-previous"
|
|
||||||
aria-label="Previous">Previous</Link
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{#if page < totalPages}
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(page + 1)}
|
|
||||||
to="/posts?page={page + 1}"
|
|
||||||
class="pagination-next"
|
|
||||||
aria-label="Next">Next</Link
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
<ul class="pagination-list">
|
|
||||||
{#if page > 3}
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(1)}
|
|
||||||
to="/posts?page={1}"
|
|
||||||
class="pagination-link"
|
|
||||||
aria-label="Goto page 1">1</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span class="pagination-ellipsis">…</span>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
|
|
||||||
{#if i >= 1 && i <= totalPages}
|
|
||||||
{#if i == page}
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(i)}
|
|
||||||
to="/posts?page={i}"
|
|
||||||
class="pagination-link is-current"
|
|
||||||
aria-label="Goto page {i}">{i}</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{:else}
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(i)}
|
|
||||||
to="/posts?page={i}"
|
|
||||||
class="pagination-link"
|
|
||||||
aria-label="Goto page {i}">{i}</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
{#if totalPages - page > 2}
|
|
||||||
<li>
|
|
||||||
<span class="pagination-ellipsis">…</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(totalPages)}
|
|
||||||
to="/posts?page={totalPages}"
|
|
||||||
class="pagination-link"
|
|
||||||
aria-label="Goto page {totalPages}"
|
|
||||||
>{totalPages}</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<div class="columns is-multiline">
|
|
||||||
{#each posts as post (post.id)}
|
|
||||||
<div class="column is-one-quarter card">
|
|
||||||
<div class="card-image">
|
|
||||||
<figure class="image">
|
|
||||||
<Link to="/post/{post.id}">
|
|
||||||
<img alt={post.id} src={post.image_path} />
|
|
||||||
</Link>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
{#each post.tags as tag (tag)}
|
|
||||||
<p>
|
|
||||||
<Link to="/tag/{tag}">{tag}</Link>
|
|
||||||
</p>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { getPostsTag } from "../api.js";
|
import { getPostsTag } from "../api.js";
|
||||||
import { Link } from "svelte-routing";
|
import PostPaginator from "../PostPaginator.svelte";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
export let location;
|
export let location;
|
||||||
@ -44,99 +44,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section">
|
<PostPaginator url="/tag/{id}" posts={posts} page={page} totalPages={totalPages} handlePage={handlePage} />
|
||||||
<div class="container">
|
|
||||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
|
||||||
{#if page > 1}
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(page - 1)}
|
|
||||||
to="/tag/{id}?page={page - 1}"
|
|
||||||
class="pagination-previous"
|
|
||||||
aria-label="Previous">Previous</Link
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{#if page < totalPages}
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(page + 1)}
|
|
||||||
to="/tag/{id}?page={page + 1}"
|
|
||||||
class="pagination-next"
|
|
||||||
aria-label="Next">Next</Link
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
<ul class="pagination-list">
|
|
||||||
{#if page > 3}
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(1)}
|
|
||||||
to="/tag/{id}?page={1}"
|
|
||||||
class="pagination-link"
|
|
||||||
aria-label="Goto page 1">1</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span class="pagination-ellipsis">…</span>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
|
|
||||||
{#if i >= 1 && i <= totalPages}
|
|
||||||
{#if i == page}
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(i)}
|
|
||||||
to="/tag/{id}?page={i}"
|
|
||||||
class="pagination-link is-current"
|
|
||||||
aria-label="Goto page {i}">{i}</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{:else}
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(i)}
|
|
||||||
to="/tag/{id}?page={i}"
|
|
||||||
class="pagination-link"
|
|
||||||
aria-label="Goto page {i}">{i}</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
{#if totalPages - page > 2}
|
|
||||||
<li>
|
|
||||||
<span class="pagination-ellipsis">…</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
on:click={handlePage(totalPages)}
|
|
||||||
to="/tag/{id}?page={totalPages}"
|
|
||||||
class="pagination-link"
|
|
||||||
aria-label="Goto page {totalPages}"
|
|
||||||
>{totalPages}</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<div class="columns is-multiline">
|
|
||||||
{#each posts as post (post.id)}
|
|
||||||
<div class="column is-one-quarter card">
|
|
||||||
<div class="card-image">
|
|
||||||
<figure class="image">
|
|
||||||
<Link to="/post/{post.id}">
|
|
||||||
<img alt={post.id} src={post.image_path} />
|
|
||||||
</Link>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="content">
|
|
||||||
{#each post.tags as tag (tag)}
|
|
||||||
<p>
|
|
||||||
<Link to="/tag/{tag}">{tag}</Link>
|
|
||||||
</p>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
99
web/app/src/routes/Upload.svelte
Normal file
99
web/app/src/routes/Upload.svelte
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<script>
|
||||||
|
import { uploadBlob, postCreate } from "../api.js";
|
||||||
|
import { navigate } from "svelte-routing";
|
||||||
|
import Tags from "svelte-tags-input";
|
||||||
|
|
||||||
|
let currentProgress = 0;
|
||||||
|
|
||||||
|
let fileName = "";
|
||||||
|
|
||||||
|
let form = {
|
||||||
|
blob_id: "",
|
||||||
|
source_url: "",
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const onProgress = (e) => {
|
||||||
|
var percentCompleted = Math.round((e.loaded * 100) / e.total);
|
||||||
|
currentProgress = percentCompleted;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFileChange = async (e) => {
|
||||||
|
fileName = "";
|
||||||
|
var file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
var response = await uploadBlob({ file, onProgress });
|
||||||
|
form.blob_id = response.id;
|
||||||
|
fileName = file.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTagChange = (value) => {
|
||||||
|
form.tags = value.detail.tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
const response = await postCreate(form);
|
||||||
|
navigate(`/post/${response.id}`);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<p class="title">Upload</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<form on:submit|preventDefault={onSubmit}>
|
||||||
|
<div class="field">
|
||||||
|
<label for="file" class="label">Image File</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="file">
|
||||||
|
<label class="file-label">
|
||||||
|
<input
|
||||||
|
id="file"
|
||||||
|
class="file-input"
|
||||||
|
type="file"
|
||||||
|
name="resume"
|
||||||
|
on:change={onFileChange}
|
||||||
|
/>
|
||||||
|
<span class="file-cta">
|
||||||
|
<span class="file-icon" />
|
||||||
|
<span class="file-label"> Choose a file… </span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if currentProgress > 0 && currentProgress < 100}
|
||||||
|
<p class="help">{currentProgress}%</p>
|
||||||
|
{/if}
|
||||||
|
{#if fileName !== ""}
|
||||||
|
<p class="help">{fileName} uploaded</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<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 on:tags={onTagChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button type="submit" class="button is-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
1275
web/app/yarn.lock
1275
web/app/yarn.lock
File diff suppressed because it is too large
Load Diff
5620
web/static/bundle.js
5620
web/static/bundle.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -5,8 +5,8 @@
|
|||||||
<title>{{ .title }}</title>
|
<title>{{ .title }}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.2/css/bulma.min.css">
|
|
||||||
<script defer src="/static/bundle.js"></script>
|
<script defer src="/static/bundle.js"></script>
|
||||||
|
<link rel="stylesheet" href="/static/bundle.css">
|
||||||
<script>
|
<script>
|
||||||
window.BASE_URL = {{ .base_url }}
|
window.BASE_URL = {{ .base_url }}
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user