Generate link feature
This commit is contained in:
parent
d6a2b056c2
commit
4858c7493b
5 changed files with 241 additions and 20 deletions
43
internal/app/s3manager/generate_presigned_url.go
Normal file
43
internal/app/s3manager/generate_presigned_url.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package s3manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleGenerateUrl(s3 S3) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
bucketName := mux.Vars(r)["bucketName"]
|
||||||
|
objectName := mux.Vars(r)["objectName"]
|
||||||
|
expiry := r.URL.Query().Get("expiry")
|
||||||
|
|
||||||
|
parsedExpiry, err := strconv.ParseInt(expiry, 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
handleHTTPError(w, fmt.Errorf("error when converting expiry: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedExpiry > 7*24*60*60 || parsedExpiry < 1 {
|
||||||
|
handleHTTPError(w, fmt.Errorf("invalid expiry value: %v", parsedExpiry))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expirySecond := time.Duration(parsedExpiry * 1e9)
|
||||||
|
reqParams := make(url.Values)
|
||||||
|
url, err := s3.PresignedGetObject(r.Context(), bucketName, objectName, expirySecond, reqParams)
|
||||||
|
if err != nil {
|
||||||
|
handleHTTPError(w, fmt.Errorf("error when generate url: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
encoder.Encode(map[string]string{"url": url.String()})
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,9 @@ import (
|
||||||
"github.com/cloudlena/s3manager/internal/app/s3manager"
|
"github.com/cloudlena/s3manager/internal/app/s3manager"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure, that S3Mock does implement s3manager.S3.
|
// Ensure, that S3Mock does implement s3manager.S3.
|
||||||
|
@ -33,6 +35,9 @@ var _ s3manager.S3 = &S3Mock{}
|
||||||
// MakeBucketFunc: func(ctx context.Context, bucketName string, opts minio.MakeBucketOptions) error {
|
// MakeBucketFunc: func(ctx context.Context, bucketName string, opts minio.MakeBucketOptions) error {
|
||||||
// panic("mock out the MakeBucket method")
|
// panic("mock out the MakeBucket method")
|
||||||
// },
|
// },
|
||||||
|
// PresignedGetObjectFunc: func(ctx context.Context, bucketName string, objectName string, expiry time.Duration, reqParams url.Values) (*url.URL, error) {
|
||||||
|
// panic("mock out the PresignedGetObject method")
|
||||||
|
// },
|
||||||
// PutObjectFunc: func(ctx context.Context, bucketName string, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (minio.UploadInfo, error) {
|
// PutObjectFunc: func(ctx context.Context, bucketName string, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (minio.UploadInfo, error) {
|
||||||
// panic("mock out the PutObject method")
|
// panic("mock out the PutObject method")
|
||||||
// },
|
// },
|
||||||
|
@ -61,6 +66,9 @@ type S3Mock struct {
|
||||||
// MakeBucketFunc mocks the MakeBucket method.
|
// MakeBucketFunc mocks the MakeBucket method.
|
||||||
MakeBucketFunc func(ctx context.Context, bucketName string, opts minio.MakeBucketOptions) error
|
MakeBucketFunc func(ctx context.Context, bucketName string, opts minio.MakeBucketOptions) error
|
||||||
|
|
||||||
|
// PresignedGetObjectFunc mocks the PresignedGetObject method.
|
||||||
|
PresignedGetObjectFunc func(ctx context.Context, bucketName string, objectName string, expiry time.Duration, reqParams url.Values) (*url.URL, error)
|
||||||
|
|
||||||
// PutObjectFunc mocks the PutObject method.
|
// PutObjectFunc mocks the PutObject method.
|
||||||
PutObjectFunc func(ctx context.Context, bucketName string, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (minio.UploadInfo, error)
|
PutObjectFunc func(ctx context.Context, bucketName string, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (minio.UploadInfo, error)
|
||||||
|
|
||||||
|
@ -106,6 +114,19 @@ type S3Mock struct {
|
||||||
// Opts is the opts argument value.
|
// Opts is the opts argument value.
|
||||||
Opts minio.MakeBucketOptions
|
Opts minio.MakeBucketOptions
|
||||||
}
|
}
|
||||||
|
// PresignedGetObject holds details about calls to the PresignedGetObject method.
|
||||||
|
PresignedGetObject []struct {
|
||||||
|
// Ctx is the ctx argument value.
|
||||||
|
Ctx context.Context
|
||||||
|
// BucketName is the bucketName argument value.
|
||||||
|
BucketName string
|
||||||
|
// ObjectName is the objectName argument value.
|
||||||
|
ObjectName string
|
||||||
|
// Expiry is the expiry argument value.
|
||||||
|
Expiry time.Duration
|
||||||
|
// ReqParams is the reqParams argument value.
|
||||||
|
ReqParams url.Values
|
||||||
|
}
|
||||||
// PutObject holds details about calls to the PutObject method.
|
// PutObject holds details about calls to the PutObject method.
|
||||||
PutObject []struct {
|
PutObject []struct {
|
||||||
// Ctx is the ctx argument value.
|
// Ctx is the ctx argument value.
|
||||||
|
@ -140,13 +161,14 @@ type S3Mock struct {
|
||||||
Opts minio.RemoveObjectOptions
|
Opts minio.RemoveObjectOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lockGetObject sync.RWMutex
|
lockGetObject sync.RWMutex
|
||||||
lockListBuckets sync.RWMutex
|
lockListBuckets sync.RWMutex
|
||||||
lockListObjects sync.RWMutex
|
lockListObjects sync.RWMutex
|
||||||
lockMakeBucket sync.RWMutex
|
lockMakeBucket sync.RWMutex
|
||||||
lockPutObject sync.RWMutex
|
lockPresignedGetObject sync.RWMutex
|
||||||
lockRemoveBucket sync.RWMutex
|
lockPutObject sync.RWMutex
|
||||||
lockRemoveObject sync.RWMutex
|
lockRemoveBucket sync.RWMutex
|
||||||
|
lockRemoveObject sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObject calls GetObjectFunc.
|
// GetObject calls GetObjectFunc.
|
||||||
|
@ -305,6 +327,54 @@ func (mock *S3Mock) MakeBucketCalls() []struct {
|
||||||
return calls
|
return calls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PresignedGetObject calls PresignedGetObjectFunc.
|
||||||
|
func (mock *S3Mock) PresignedGetObject(ctx context.Context, bucketName string, objectName string, expiry time.Duration, reqParams url.Values) (*url.URL, error) {
|
||||||
|
if mock.PresignedGetObjectFunc == nil {
|
||||||
|
panic("S3Mock.PresignedGetObjectFunc: method is nil but S3.PresignedGetObject was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Ctx context.Context
|
||||||
|
BucketName string
|
||||||
|
ObjectName string
|
||||||
|
Expiry time.Duration
|
||||||
|
ReqParams url.Values
|
||||||
|
}{
|
||||||
|
Ctx: ctx,
|
||||||
|
BucketName: bucketName,
|
||||||
|
ObjectName: objectName,
|
||||||
|
Expiry: expiry,
|
||||||
|
ReqParams: reqParams,
|
||||||
|
}
|
||||||
|
mock.lockPresignedGetObject.Lock()
|
||||||
|
mock.calls.PresignedGetObject = append(mock.calls.PresignedGetObject, callInfo)
|
||||||
|
mock.lockPresignedGetObject.Unlock()
|
||||||
|
return mock.PresignedGetObjectFunc(ctx, bucketName, objectName, expiry, reqParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresignedGetObjectCalls gets all the calls that were made to PresignedGetObject.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedS3.PresignedGetObjectCalls())
|
||||||
|
func (mock *S3Mock) PresignedGetObjectCalls() []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
BucketName string
|
||||||
|
ObjectName string
|
||||||
|
Expiry time.Duration
|
||||||
|
ReqParams url.Values
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
BucketName string
|
||||||
|
ObjectName string
|
||||||
|
Expiry time.Duration
|
||||||
|
ReqParams url.Values
|
||||||
|
}
|
||||||
|
mock.lockPresignedGetObject.RLock()
|
||||||
|
calls = mock.calls.PresignedGetObject
|
||||||
|
mock.lockPresignedGetObject.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
// PutObject calls PutObjectFunc.
|
// PutObject calls PutObjectFunc.
|
||||||
func (mock *S3Mock) PutObject(ctx context.Context, bucketName string, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (minio.UploadInfo, error) {
|
func (mock *S3Mock) PutObject(ctx context.Context, bucketName string, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (minio.UploadInfo, error) {
|
||||||
if mock.PutObjectFunc == nil {
|
if mock.PutObjectFunc == nil {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package s3manager
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
)
|
)
|
||||||
|
@ -15,6 +17,7 @@ type S3 interface {
|
||||||
ListBuckets(ctx context.Context) ([]minio.BucketInfo, error)
|
ListBuckets(ctx context.Context) ([]minio.BucketInfo, error)
|
||||||
ListObjects(ctx context.Context, bucketName string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
|
ListObjects(ctx context.Context, bucketName string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
|
||||||
MakeBucket(ctx context.Context, bucketName string, opts minio.MakeBucketOptions) error
|
MakeBucket(ctx context.Context, bucketName string, opts minio.MakeBucketOptions) error
|
||||||
|
PresignedGetObject(ctx context.Context, bucketName, objectName string, expiry time.Duration, reqParams url.Values) (*url.URL, error)
|
||||||
PutObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (minio.UploadInfo, error)
|
PutObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (minio.UploadInfo, error)
|
||||||
RemoveBucket(ctx context.Context, bucketName string) error
|
RemoveBucket(ctx context.Context, bucketName string) error
|
||||||
RemoveObject(ctx context.Context, bucketName, objectName string, opts minio.RemoveObjectOptions) error
|
RemoveObject(ctx context.Context, bucketName, objectName string, opts minio.RemoveObjectOptions) error
|
||||||
|
|
1
main.go
1
main.go
|
@ -161,6 +161,7 @@ func main() {
|
||||||
r.Handle("/api/buckets/{bucketName}", s3manager.HandleDeleteBucket(s3)).Methods(http.MethodDelete)
|
r.Handle("/api/buckets/{bucketName}", s3manager.HandleDeleteBucket(s3)).Methods(http.MethodDelete)
|
||||||
}
|
}
|
||||||
r.Handle("/api/buckets/{bucketName}/objects", s3manager.HandleCreateObject(s3, sseType)).Methods(http.MethodPost)
|
r.Handle("/api/buckets/{bucketName}/objects", s3manager.HandleCreateObject(s3, sseType)).Methods(http.MethodPost)
|
||||||
|
r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}/url", s3manager.HandleGenerateUrl(s3)).Methods(http.MethodGet)
|
||||||
r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleGetObject(s3, configuration.ForceDownload)).Methods(http.MethodGet)
|
r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleGetObject(s3, configuration.ForceDownload)).Methods(http.MethodGet)
|
||||||
if configuration.AllowDelete {
|
if configuration.AllowDelete {
|
||||||
r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleDeleteObject(s3)).Methods(http.MethodDelete)
|
r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleDeleteObject(s3)).Methods(http.MethodDelete)
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
<!-- Dropdown Structure -->
|
<!-- Dropdown Structure -->
|
||||||
<ul id="actions-dropdown-{{ $index }}" class="dropdown-content">
|
<ul id="actions-dropdown-{{ $index }}" class="dropdown-content">
|
||||||
<li><a target="_blank" href="/api/buckets/{{ $.BucketName }}/objects/{{ $object.Key }}">Download</a></li>
|
<li><a target="_blank" href="/api/buckets/{{ $.BucketName }}/objects/{{ $object.Key }}">Download</a></li>
|
||||||
|
<li><a onclick="handleOpenDownloadLinkModal({{ $object.Key }})">Download link</a></li>
|
||||||
{{- if $.AllowDelete }}
|
{{- if $.AllowDelete }}
|
||||||
<li><a href="#" onclick="deleteObject({{ $.BucketName }}, {{ $object.Key }})">Delete</a></li>
|
<li><a href="#" onclick="deleteObject({{ $.BucketName }}, {{ $object.Key }})">Delete</a></li>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -110,7 +111,7 @@
|
||||||
<i class="large material-icons">create_new_folder</i>
|
<i class="large material-icons">create_new_folder</i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" class="btn-floating btn-large red modal-trigger tooltipped" data-target="modal-create-folder" data-position="top" data-tooltip="Change path">
|
<button type="button" class="btn-floating btn-large red modal-trigger tooltipped" data-target="modal-change-path" data-position="top" data-tooltip="Change path">
|
||||||
<i class="large material-icons">create</i>
|
<i class="large material-icons">create</i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,9 +119,8 @@
|
||||||
<input type="file" id="upload-folder-input" webkitdirectory multiple style="display: none;">
|
<input type="file" id="upload-folder-input" webkitdirectory multiple style="display: none;">
|
||||||
<input type="file" id="upload-file-input" name="file" multiple style="display: none;">
|
<input type="file" id="upload-file-input" name="file" multiple style="display: none;">
|
||||||
|
|
||||||
<div id="modal-create-folder" class="modal">
|
<div id="modal-change-path" class="modal">
|
||||||
<form id="create-folder-form" enctype="multipart/form-data">
|
<form id="change-path-form" enctype="multipart/form-data">
|
||||||
|
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Change directory path</h4>
|
<h4>Change directory path</h4>
|
||||||
<br>
|
<br>
|
||||||
|
@ -133,12 +133,58 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="modal-close waves-effect waves-green btn-flat">Cancel</button>
|
<button type="button" class="modal-close waves-effect waves-green btn-flat">Cancel</button>
|
||||||
<button type="submit" class="modal-close waves-effect waves-green btn">Create</button>
|
<button type="submit" class="modal-close waves-effect waves-green btn">Change path</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="modal-create-download-link" class="modal">
|
||||||
|
<form id="download-link-form">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="row">
|
||||||
|
<h4>Create download link for </h4>
|
||||||
|
<input name="objectName" id="objectName" type="text" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s4">
|
||||||
|
<div class="input-field">
|
||||||
|
<input name="day" id="gen-link-day" value="0" type="text" pattern="^[\d]+$" class="validate">
|
||||||
|
<label for="gen-link-day">Day</label>
|
||||||
|
<span class="helper-text" data-error="Invalid day format"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s4">
|
||||||
|
<div class="input-field">
|
||||||
|
<input name="hour" id="gen-link-hour" value="1" type="text" pattern="^([0-9]|1\d|2[0-3])$" class="validate">
|
||||||
|
<label for="gen-link-hour">Hour</label>
|
||||||
|
<span class="helper-text" data-error="Invalid hour format"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s4">
|
||||||
|
<div class="input-field">
|
||||||
|
<input name="minute" id="gen-link-minute" value="0" type="text" pattern="^([0-9]|[1-5]\d)$" class="validate">
|
||||||
|
<label for="gen-link-minute">Minute</label>
|
||||||
|
<span class="helper-text" data-error="Invalid minute format"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s3">
|
||||||
|
<button id="create-link-btn" class="waves-effect waves-green btn">Create link</button>
|
||||||
|
</div>
|
||||||
|
<div class="col s9 red-text text-darken-2" id="gen-url-error"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s11">
|
||||||
|
<div class="input-field">
|
||||||
|
<i class="material-icons prefix" onclick="handleCopyLink()" style="cursor:pointer;">content_copy</i>
|
||||||
|
<input name="generated-link" id="generated-link" type="text" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -150,6 +196,7 @@ function deleteObject(bucketName, objectName) {
|
||||||
success: function () { location.reload(); }
|
success: function () { location.reload(); }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteBucket(bucketName) {
|
function deleteBucket(bucketName) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
|
@ -164,19 +211,24 @@ function handleUploadFiles(event) {
|
||||||
uploadFiles(files, url);
|
uploadFiles(files, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCreateFolder(event) {
|
function handleChangePath(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const form = event.target;
|
const form = event.target;
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
const newPath = formData.get("new-path")
|
|
||||||
|
|
||||||
let newHref = window.location.href + newPath
|
let appendPath = formData.get("new-path")
|
||||||
if(!newHref.endsWith("/")) {
|
if(!appendPath.endsWith("/")) {
|
||||||
newHref = newHref + "/"
|
appendPath = appendPath + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href = newHref
|
let currentPath = window.location.href
|
||||||
|
if(!currentPath.endsWith("/")) {
|
||||||
|
currentPath = currentPath + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
form.reset();
|
||||||
|
window.location.href = currentPath + appendPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFiles(files, url) {
|
function uploadFiles(files, url) {
|
||||||
|
@ -219,8 +271,60 @@ function createNotification(fileName) {
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleOpenDownloadLinkModal(objectName) {
|
||||||
|
const downloadLinkForm = document.forms['download-link-form']
|
||||||
|
downloadLinkForm.elements['objectName'].value = objectName;
|
||||||
|
|
||||||
|
const createLinkModalElement = document.getElementById('modal-create-download-link')
|
||||||
|
document.getElementById('generated-link').setAttribute('value', "");
|
||||||
|
document.getElementById('gen-url-error').setHTML("");
|
||||||
|
const modalInstance = M.Modal.init(createLinkModalElement);
|
||||||
|
modalInstance.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGenerateDownloadLink(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const form = event.target;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const objectName = formData.get('objectName');
|
||||||
|
const genUrlMessage = document.getElementById('gen-url-error');
|
||||||
|
|
||||||
|
const expiryTime = formData.get('day') * 24 * 60 * 60 + formData.get('hour') * 60 * 60 + formData.get('minute') * 60;
|
||||||
|
|
||||||
|
if(expiryTime > 7 * 24 * 60 * 60) {
|
||||||
|
genUrlMessage.setHTML("Expiry time must be less than 7 days");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: '/api/buckets/' + {{ $.BucketName }}+ "/objects/" + objectName + "/url?expiry=" + expiryTime,
|
||||||
|
success: function (result) {
|
||||||
|
genUrlMessage.setHTML("")
|
||||||
|
document.getElementById("generated-link").setAttribute('value', JSON.parse(result).url);
|
||||||
|
},
|
||||||
|
error: function(request, status, error) {
|
||||||
|
genUrlMessage.setHTML("Error when generating url")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopyLink() {
|
||||||
|
const url = document.getElementById("generated-link").value;
|
||||||
|
|
||||||
|
if(!!url) {
|
||||||
|
navigator.clipboard.writeText(url).then(function() {
|
||||||
|
M.toast({html: 'Copied to clipboard!'});
|
||||||
|
}, function(err) {
|
||||||
|
console.error('Could not copy:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.onload = (event) => {
|
window.onload = (event) => {
|
||||||
$('#create-folder-form').submit(handleCreateFolder)
|
$('#change-path-form').submit(handleChangePath)
|
||||||
|
$('#download-link-form').submit(handleGenerateDownloadLink)
|
||||||
|
|
||||||
uploadFolderInput = $('#upload-folder-input');
|
uploadFolderInput = $('#upload-folder-input');
|
||||||
$('#upload-folder-btn').click(event => uploadFolderInput.click());
|
$('#upload-folder-btn').click(event => uploadFolderInput.click());
|
||||||
|
|
Loading…
Reference in a new issue