Attachments

This commit is contained in:
LittleSheep 2024-02-04 18:40:20 +08:00
parent 5e4d5f77c5
commit 86783316a1
17 changed files with 396 additions and 101 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/uploads

View File

@ -15,6 +15,7 @@ func RunMigration(source *gorm.DB) error {
&models.Post{}, &models.Post{},
&models.PostLike{}, &models.PostLike{},
&models.PostDislike{}, &models.PostDislike{},
&models.Attachment{},
); err != nil { ); err != nil {
return err return err
} }

View File

@ -14,6 +14,7 @@ type Account struct {
EmailAddress string `json:"email_address"` EmailAddress string `json:"email_address"`
PowerLevel int `json:"power_level"` PowerLevel int `json:"power_level"`
Posts []Post `json:"posts" gorm:"foreignKey:AuthorID"` Posts []Post `json:"posts" gorm:"foreignKey:AuthorID"`
Attachments []Attachment `json:"attachments" gorm:"foreignKey:AuthorID"`
LikedPosts []PostLike `json:"liked_posts"` LikedPosts []PostLike `json:"liked_posts"`
DislikedPosts []PostDislike `json:"disliked_posts"` DislikedPosts []PostDislike `json:"disliked_posts"`
Realms []Realm `json:"realms"` Realms []Realm `json:"realms"`

29
pkg/models/attachments.go Normal file
View File

@ -0,0 +1,29 @@
package models
import (
"fmt"
"github.com/spf13/viper"
"path/filepath"
)
type Attachment struct {
BaseModel
FileID string `json:"file_id"`
Filesize int64 `json:"filesize"`
Filename string `json:"filename"`
Mimetype string `json:"mimetype"`
Post *Post `json:"post"`
Author Account `json:"author"`
PostID *uint `json:"post_id"`
AuthorID uint `json:"author_id"`
}
func (v Attachment) GetStoragePath() string {
basepath := viper.GetString("content")
return filepath.Join(basepath, v.FileID)
}
func (v Attachment) GetAccessPath() string {
return fmt.Sprintf("/api/attachments/o/%s", v.FileID)
}

View File

@ -10,6 +10,7 @@ type Post struct {
Content string `json:"content"` Content string `json:"content"`
Tags []Tag `json:"tags" gorm:"many2many:post_tags"` Tags []Tag `json:"tags" gorm:"many2many:post_tags"`
Categories []Category `json:"categories" gorm:"many2many:post_categories"` Categories []Category `json:"categories" gorm:"many2many:post_categories"`
Attachments []Attachment `json:"attachments"`
LikedAccounts []PostLike `json:"liked_accounts"` LikedAccounts []PostLike `json:"liked_accounts"`
DislikedAccounts []PostDislike `json:"disliked_accounts"` DislikedAccounts []PostDislike `json:"disliked_accounts"`
RepostTo *Post `json:"repost_to" gorm:"foreignKey:RepostID"` RepostTo *Post `json:"repost_to" gorm:"foreignKey:RepostID"`

View File

@ -0,0 +1,38 @@
package server
import (
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
"code.smartsheep.studio/hydrogen/interactive/pkg/services"
"github.com/gofiber/fiber/v2"
"github.com/spf13/viper"
"path/filepath"
)
func openAttachment(c *fiber.Ctx) error {
id := c.Params("fileId")
basepath := viper.GetString("content")
return c.SendFile(filepath.Join(basepath, id))
}
func uploadAttachment(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
file, err := c.FormFile("attachment")
if err != nil {
return err
}
attachment, err := services.NewAttachment(user, file)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if err := c.SaveFile(file, attachment.GetStoragePath()); err != nil {
return err
}
return c.JSON(fiber.Map{
"info": attachment,
"url": attachment.GetAccessPath(),
})
}

View File

@ -36,7 +36,7 @@ func doLogin(c *fiber.Ctx) error {
}) })
} }
func doPostLogin(c *fiber.Ctx) error { func postLogin(c *fiber.Ctx) error {
buildOauth2Config() buildOauth2Config()
code := c.Query("code") code := c.Query("code")

View File

@ -48,14 +48,15 @@ func createPost(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account) user := c.Locals("principal").(models.Account)
var data struct { var data struct {
Alias string `json:"alias"` Alias string `json:"alias"`
Title string `json:"title"` Title string `json:"title"`
Content string `json:"content" validate:"required"` Content string `json:"content" validate:"required"`
Tags []models.Tag `json:"tags"` Tags []models.Tag `json:"tags"`
Categories []models.Category `json:"categories"` Categories []models.Category `json:"categories"`
PublishedAt *time.Time `json:"published_at"` Attachments []models.Attachment `json:"attachments"`
RepostTo uint `json:"repost_to"` PublishedAt *time.Time `json:"published_at"`
ReplyTo uint `json:"reply_to"` RepostTo uint `json:"repost_to"`
ReplyTo uint `json:"reply_to"`
} }
if err := BindAndValidate(c, &data); err != nil { if err := BindAndValidate(c, &data); err != nil {
@ -94,6 +95,7 @@ func createPost(c *fiber.Ctx) error {
data.Alias, data.Alias,
data.Title, data.Title,
data.Content, data.Content,
data.Attachments,
data.Categories, data.Categories,
data.Tags, data.Tags,
data.PublishedAt, data.PublishedAt,

View File

@ -56,14 +56,17 @@ func NewServer() {
api := A.Group("/api").Name("API") api := A.Group("/api").Name("API")
{ {
api.Get("/auth", doLogin)
api.Get("/auth/callback", postLogin)
api.Post("/auth/refresh", doRefreshToken)
api.Get("/users/me", auth, getUserinfo) api.Get("/users/me", auth, getUserinfo)
api.Get("/users/:accountId", getOthersInfo) api.Get("/users/:accountId", getOthersInfo)
api.Get("/users/:accountId/follow", auth, getAccountFollowed) api.Get("/users/:accountId/follow", auth, getAccountFollowed)
api.Post("/users/:accountId/follow", auth, doFollowAccount) api.Post("/users/:accountId/follow", auth, doFollowAccount)
api.Get("/auth", doLogin) api.Get("/attachments/o/:fileId", openAttachment)
api.Get("/auth/callback", doPostLogin) api.Post("/attachments", auth, uploadAttachment)
api.Post("/auth/refresh", doRefreshToken)
api.Get("/posts", listPost) api.Get("/posts", listPost)
api.Post("/posts", auth, createPost) api.Post("/posts", auth, createPost)

View File

@ -0,0 +1,40 @@
package services
import (
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
"github.com/google/uuid"
"mime/multipart"
"net/http"
)
func NewAttachment(user models.Account, header *multipart.FileHeader) (models.Attachment, error) {
attachment := models.Attachment{
FileID: uuid.NewString(),
Filesize: header.Size,
Filename: header.Filename,
Mimetype: "unknown/unknown",
PostID: nil,
AuthorID: user.ID,
}
// Open file
file, err := header.Open()
if err != nil {
return attachment, err
}
defer file.Close()
// Detect mimetype
fileHeader := make([]byte, 512)
_, err = file.Read(fileHeader)
if err != nil {
return attachment, err
}
attachment.Mimetype = http.DetectContentType(fileHeader)
// Save into database
err = database.C.Save(&attachment).Error
return attachment, err
}

View File

@ -18,10 +18,13 @@ func ListPost(tx *gorm.DB, take int, offset int) ([]*models.Post, error) {
Limit(take). Limit(take).
Offset(offset). Offset(offset).
Preload("Author"). Preload("Author").
Preload("Attachments").
Preload("RepostTo"). Preload("RepostTo").
Preload("ReplyTo"). Preload("ReplyTo").
Preload("RepostTo.Author"). Preload("RepostTo.Author").
Preload("ReplyTo.Author"). Preload("ReplyTo.Author").
Preload("RepostTo.Attachments").
Preload("ReplyTo.Attachments").
Find(&posts).Error; err != nil { Find(&posts).Error; err != nil {
return posts, err return posts, err
} }
@ -66,6 +69,7 @@ WHERE t.id IN (?)`, prefix, prefix, prefix), postIds).Scan(&reactInfo)
func NewPost( func NewPost(
user models.Account, user models.Account,
alias, title, content string, alias, title, content string,
attachments []models.Attachment,
categories []models.Category, categories []models.Category,
tags []models.Tag, tags []models.Tag,
publishedAt *time.Time, publishedAt *time.Time,
@ -77,6 +81,7 @@ func NewPost(
alias, alias,
title, title,
content, content,
attachments,
categories, categories,
tags, tags,
publishedAt, publishedAt,
@ -89,6 +94,7 @@ func NewPostWithRealm(
user models.Account, user models.Account,
realm *models.Realm, realm *models.Realm,
alias, title, content string, alias, title, content string,
attachments []models.Attachment,
categories []models.Category, categories []models.Category,
tags []models.Tag, tags []models.Tag,
publishedAt *time.Time, publishedAt *time.Time,
@ -122,6 +128,7 @@ func NewPostWithRealm(
Alias: alias, Alias: alias,
Title: title, Title: title,
Content: content, Content: content,
Attachments: attachments,
Tags: tags, Tags: tags,
Categories: categories, Categories: categories,
AuthorID: user.ID, AuthorID: user.ID,

View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@solidjs/router": "^0.10.10", "@solidjs/router": "^0.10.10",
"medium-zoom": "^1.1.0",
"solid-js": "^1.8.7", "solid-js": "^1.8.7",
"universal-cookie": "^7.0.2" "universal-cookie": "^7.0.2"
}, },

View File

@ -0,0 +1,3 @@
.attachmentsControl {
background-color: transparent !important;
}

View File

@ -0,0 +1,80 @@
import { createEffect, createMemo, createSignal, Match, Switch } from "solid-js";
import mediumZoom from "medium-zoom";
import styles from "./PostAttachments.module.css";
export default function PostAttachments(props: { attachments: any[] }) {
if (props.attachments.length <= 0) return null;
const [focus, setFocus] = createSignal(0);
const item = createMemo(() => props.attachments[focus()]);
function getRenderType(item: any): string {
return item.mimetype.split("/")[0];
}
function getUrl(item: any): string {
return `/api/attachments/o/${item.file_id}`;
}
createEffect(() => {
mediumZoom(document.querySelectorAll(".attachment-image img"), {
background: "var(--fallback-b1,oklch(var(--b1)/1))"
});
}, [focus()]);
return (
<>
<p class="text-xs mt-3 mb-2">
<i class="fa-solid fa-paperclip me-2"></i>
Attached {props.attachments.length} file{props.attachments.length > 1 ? "s" : null}
</p>
<div class="border border-base-200">
<Switch fallback={
<div class="py-16 flex justify-center items-center">
<div class="text-center">
<i class="fa-solid fa-circle-question text-3xl"></i>
<p class="mt-3">{item().filename}</p>
<div class="flex gap-3 w-full">
<p class="text-sm">{item().filesize} Bytes</p>
<p class="text-sm">{item().mimetype}</p>
</div>
<div class="mt-5">
<a class="link" href={getUrl(item())} target="_blank">Open in browser</a>
</div>
</div>
</div>
}>
<Match when={getRenderType(item()) === "image"}>
<figure class="attachment-image">
<img class="object-cover" src={getUrl(item())} alt={item().filename} />
</figure>
</Match>
</Switch>
<div id="attachments-control" class="flex justify-between border-t border-base-200">
<div class="flex">
<button class={`w-12 h-12 btn btn-ghost ${styles.attachmentsControl}`}
disabled={focus() - 1 < 0}
onClick={() => setFocus(focus() - 1)}>
<i class="fa-solid fa-caret-left"></i>
</button>
<button class={`w-12 h-12 btn btn-ghost ${styles.attachmentsControl}`}
disabled={focus() + 1 >= props.attachments.length}
onClick={() => setFocus(focus() + 1)}>
<i class="fa-solid fa-caret-right"></i>
</button>
</div>
<div>
<div class="h-12 px-5 py-3.5 text-sm">
File {focus() + 1}
</div>
</div>
</div>
</div>
</>
);
}

View File

@ -1,5 +1,6 @@
import { createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
import { getAtk, useUserinfo } from "../stores/userinfo.tsx"; import { getAtk, useUserinfo } from "../stores/userinfo.tsx";
import PostAttachments from "./PostAttachments.tsx";
export default function PostItem(props: { export default function PostItem(props: {
post: any, post: any,
@ -58,6 +59,8 @@ export default function PostItem(props: {
<h2 class="card-title">{props.post.title}</h2> <h2 class="card-title">{props.post.title}</h2>
<article class="prose">{props.post.content}</article> <article class="prose">{props.post.content}</article>
<PostAttachments attachments={props.post.attachments ?? []} />
<Show when={props.post.repost_to}> <Show when={props.post.repost_to}>
<p class="text-xs mt-3 mb-2"> <p class="text-xs mt-3 mb-2">
<i class="fa-solid fa-retweet me-2"></i> <i class="fa-solid fa-retweet me-2"></i>

View File

@ -1,4 +1,4 @@
import { createSignal, Show } from "solid-js"; import { createEffect, createSignal, For, Show } from "solid-js";
import { getAtk, useUserinfo } from "../stores/userinfo.tsx"; import { getAtk, useUserinfo } from "../stores/userinfo.tsx";
import styles from "./PostPublish.module.css"; import styles from "./PostPublish.module.css";
@ -15,6 +15,11 @@ export default function PostPublish(props: {
const userinfo = useUserinfo(); const userinfo = useUserinfo();
const [submitting, setSubmitting] = createSignal(false); const [submitting, setSubmitting] = createSignal(false);
const [uploading, setUploading] = createSignal(false);
const [attachments, setAttachments] = createSignal<any[]>([]);
createEffect(() => setAttachments(props.editing?.attachments ?? []), [props.editing]);
async function doPost(evt: SubmitEvent) { async function doPost(evt: SubmitEvent) {
evt.preventDefault(); evt.preventDefault();
@ -34,6 +39,7 @@ export default function PostPublish(props: {
alias: data.alias ?? crypto.randomUUID().replace(/-/g, ""), alias: data.alias ?? crypto.randomUUID().replace(/-/g, ""),
title: data.title, title: data.title,
content: data.content, content: data.content,
attachments: attachments(),
published_at: data.published_at ? new Date(data.published_at as string) : new Date(), published_at: data.published_at ? new Date(data.published_at as string) : new Date(),
repost_to: props.reposting?.id, repost_to: props.reposting?.id,
reply_to: props.replying?.id reply_to: props.replying?.id
@ -55,6 +61,7 @@ export default function PostPublish(props: {
const form = evt.target as HTMLFormElement; const form = evt.target as HTMLFormElement;
const data = Object.fromEntries(new FormData(form)); const data = Object.fromEntries(new FormData(form));
if (!data.content) return; if (!data.content) return;
if (uploading()) return;
setSubmitting(true); setSubmitting(true);
const res = await fetch(`/api/posts/${props.editing?.id}`, { const res = await fetch(`/api/posts/${props.editing?.id}`, {
@ -66,7 +73,9 @@ export default function PostPublish(props: {
body: JSON.stringify({ body: JSON.stringify({
alias: data.alias ?? crypto.randomUUID().replace(/-/g, ""), alias: data.alias ?? crypto.randomUUID().replace(/-/g, ""),
title: data.title, title: data.title,
content: data.content content: data.content,
attachments: attachments(),
published_at: data.published_at ? new Date(data.published_at as string) : new Date()
}) })
}); });
if (res.status !== 200) { if (res.status !== 200) {
@ -79,103 +88,171 @@ export default function PostPublish(props: {
setSubmitting(false); setSubmitting(false);
} }
async function uploadAttachments(evt: SubmitEvent) {
evt.preventDefault();
const data = new FormData(evt.target as HTMLFormElement);
if (!data.get("attachment")) return;
setUploading(true);
const res = await fetch("/api/attachments", {
method: "POST",
headers: { "Authorization": `Bearer ${getAtk()}` },
body: data
});
if (res.status !== 200) {
props.onError(await res.text());
} else {
const data = await res.json();
setAttachments(attachments().concat([data.info]));
props.onError(null);
}
setUploading(false);
}
function resetForm() {
setAttachments([]);
props.onReset();
}
return ( return (
<form id="publish" onSubmit={props.editing ? doEdit : doPost} onReset={props.onReset}> <>
<div id="publish-identity" class="flex border-y border-base-200"> <form id="publish" onSubmit={props.editing ? doEdit : doPost} onReset={() => resetForm()}>
<div class="avatar pl-[20px]"> <div id="publish-identity" class="flex border-y border-base-200">
<div class="w-12"> <div class="avatar pl-[20px]">
<Show when={userinfo?.profiles?.avatar} <div class="w-12">
fallback={<span class="text-3xl">{userinfo?.displayName.substring(0, 1)}</span>}> <Show when={userinfo?.profiles?.avatar}
<img alt="avatar" src={userinfo?.profiles?.avatar} /> fallback={<span class="text-3xl">{userinfo?.displayName.substring(0, 1)}</span>}>
</Show> <img alt="avatar" src={userinfo?.profiles?.avatar} />
</div> </Show>
</div>
<div class="flex flex-grow">
<input name="title" value={props.editing?.title ?? ""}
class={`${styles.publishInput} input w-full`}
placeholder="The describe for a long content (Optional)" />
</div>
</div>
<Show when={props.reposting}>
<div role="alert" class="bg-base-200 flex justify-between">
<div class="px-5 py-3">
<i class="fa-solid fa-circle-info me-3"></i>
You are reposting a post from <b>{props.reposting?.author?.name}</b>
</div>
<button type="reset" class="btn btn-ghost w-12" disabled={submitting()}>
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</Show>
<Show when={props.replying}>
<div role="alert" class="bg-base-200 flex justify-between">
<div class="px-5 py-3">
<i class="fa-solid fa-circle-info me-3"></i>
You are replying a post from <b>{props.replying?.author?.name}</b>
</div>
<button type="reset" class="btn btn-ghost w-12" disabled={submitting()}>
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</Show>
<Show when={props.editing}>
<div role="alert" class="bg-base-200 flex justify-between">
<div class="px-5 py-3">
<i class="fa-solid fa-circle-info me-3"></i>
You are editing a post published at{" "}
<b>{new Date(props.editing?.created_at).toLocaleString()}</b>
</div>
<button type="reset" class="btn btn-ghost w-12" disabled={submitting()}>
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</Show>
<textarea name="content" value={props.editing?.content ?? ""}
class={`${styles.publishInput} textarea w-full`}
placeholder="What's happend?!" />
<div id="publish-actions" class="flex justify-between border-y border-base-200">
<div class="flex">
<button type="button" class="btn btn-ghost w-12">
<i class="fa-solid fa-paperclip"></i>
</button>
<button type="button" class="btn btn-ghost w-12" onClick={() => openModel("#planning-publish")}>
<i class="fa-solid fa-calendar-day"></i>
</button>
</div>
<div>
<button type="submit" class="btn btn-primary" disabled={submitting()}>
<Show when={submitting()} fallback={props.editing ? "Save changes" : "Post a post"}>
<span class="loading"></span>
</Show>
</button>
</div>
</div>
<dialog id="planning-publish" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mx-1">Planning Publish</h3>
<label class="form-control w-full mt-3">
<div class="label">
<span class="label-text">Published At</span>
</div> </div>
<input name="published_at" type="datetime-local" placeholder="Pick a date" </div>
class="input input-bordered w-full" /> <div class="flex flex-grow">
<div class="label"> <input name="title" value={props.editing?.title ?? ""}
class={`${styles.publishInput} input w-full`}
placeholder="The describe for a long content (Optional)" />
</div>
</div>
<Show when={props.reposting}>
<div role="alert" class="bg-base-200 flex justify-between">
<div class="px-5 py-3">
<i class="fa-solid fa-circle-info me-3"></i>
You are reposting a post from <b>{props.reposting?.author?.name}</b>
</div>
<button type="reset" class="btn btn-ghost w-12" disabled={submitting()}>
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</Show>
<Show when={props.replying}>
<div role="alert" class="bg-base-200 flex justify-between">
<div class="px-5 py-3">
<i class="fa-solid fa-circle-info me-3"></i>
You are replying a post from <b>{props.replying?.author?.name}</b>
</div>
<button type="reset" class="btn btn-ghost w-12" disabled={submitting()}>
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</Show>
<Show when={props.editing}>
<div role="alert" class="bg-base-200 flex justify-between">
<div class="px-5 py-3">
<i class="fa-solid fa-circle-info me-3"></i>
You are editing a post published at{" "}
<b>{new Date(props.editing?.created_at).toLocaleString()}</b>
</div>
<button type="reset" class="btn btn-ghost w-12" disabled={submitting()}>
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</Show>
<textarea name="content" value={props.editing?.content ?? ""}
class={`${styles.publishInput} textarea w-full`}
placeholder="What's happend?!" />
<div id="publish-actions" class="flex justify-between border-y border-base-200">
<div class="flex">
<button type="button" class="btn btn-ghost w-12" onClick={() => openModel("#attachments")}>
<i class="fa-solid fa-paperclip"></i>
</button>
<button type="button" class="btn btn-ghost w-12" onClick={() => openModel("#planning-publish")}>
<i class="fa-solid fa-calendar-day"></i>
</button>
</div>
<div>
<button type="submit" class="btn btn-primary" disabled={submitting()}>
<Show when={submitting()} fallback={props.editing ? "Save changes" : "Post a post"}>
<span class="loading"></span>
</Show>
</button>
</div>
</div>
<dialog id="planning-publish" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mx-1">Planning Publish</h3>
<label class="form-control w-full mt-3">
<div class="label">
<span class="label-text">Published At</span>
</div>
<input name="published_at" type="datetime-local" placeholder="Pick a date"
class="input input-bordered w-full" />
<div class="label">
<span class="label-text-alt"> <span class="label-text-alt">
Before this time, your post will not be visible for everyone. Before this time, your post will not be visible for everyone.
You can modify this plan on Creator Hub. You can modify this plan on Creator Hub.
</span> </span>
</div>
</label>
<div class="modal-action">
<button type="button" class="btn" onClick={() => closeModel("#planning-publish")}>Close</button>
</div> </div>
</label> </div>
</dialog>
</form>
<dialog id="attachments" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mx-1">Attachments</h3>
<form class="w-full mt-3" onSubmit={uploadAttachments}>
<label class="form-control">
<div class="label">
<span class="label-text">Pick a file</span>
</div>
<div class="join">
<input required type="file" name="attachment" class="join-item file-input file-input-bordered w-full" />
<button type="submit" class="join-item btn btn-primary" disabled={uploading()}>
<i class="fa-solid fa-upload"></i>
</button>
</div>
<div class="label">
<span class="label-text-alt">Click upload to add this file into list</span>
</div>
</label>
</form>
<Show when={attachments().length > 0}>
<h3 class="font-bold mt-3 mx-1">Attachment list</h3>
<ol class="mt-2 mx-1 text-sm">
<For each={attachments()}>
{item => <li>
<i class="fa-regular fa-file me-2"></i>
{item.filename}
</li>}
</For>
</ol>
</Show>
<div class="modal-action"> <div class="modal-action">
<button type="button" class="btn" onClick={() => closeModel("#planning-publish")}>Close</button> <button type="button" class="btn" onClick={() => closeModel("#attachments")}>Close</button>
</div> </div>
</div> </div>
</dialog> </dialog>
</form> </>
); );
} }

View File

@ -5,4 +5,12 @@
html, body { html, body {
padding: 0; padding: 0;
margin: 0; margin: 0;
}
.medium-zoom-image--opened {
z-index: 15;
}
.medium-zoom-overlay {
z-index: 10;
} }