diff --git a/frontend/src/api/pub.ts b/frontend/src/api/pub.ts
index 4328f64c..dadf2a68 100644
--- a/frontend/src/api/pub.ts
+++ b/frontend/src/api/pub.ts
@@ -1,11 +1,12 @@
import { fetchURL, removePrefix, createURL } from "./utils";
import { baseURL } from "@/utils/constants";
-
+import { useAuthStore } from "@/stores/auth";
export async function fetch(url: string, password: string = "") {
url = removePrefix(url);
+ const authStore = useAuthStore();
const res = await fetchURL(
- `/api/public/share${url}`,
+ `/api/public/share${url}?s=${authStore.shareConfig.sortBy}&a=${Number(authStore.shareConfig.asc)}`,
{
headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) },
},
@@ -73,3 +74,14 @@ export function getDownloadURL(res: Resource, inline = false) {
return createURL("api/public/dl/" + res.hash + res.path, params, false);
}
+
+export function getPreviewURL(file: ResourceItem, size: string) {
+ const authStore = useAuthStore();
+ const params = {
+ inline: "true",
+ token: authStore.guestJwt,
+ key: Date.parse(file.modified),
+ };
+
+ return createURL("api/public/preview/" + size + file.path, params, false);
+}
diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue
index 51093ce8..11eb1b0f 100644
--- a/frontend/src/components/files/ListingItem.vue
+++ b/frontend/src/components/files/ListingItem.vue
@@ -14,10 +14,7 @@
:aria-selected="isSelected"
>
-
![]()
+
@@ -42,15 +39,16 @@ import { useLayoutStore } from "@/stores/layout";
import { enableThumbs } from "@/utils/constants";
import { filesize } from "@/utils";
import dayjs from "dayjs";
-import { files as api } from "@/api";
+import { files as api, pub as pub_api } from "@/api";
import * as upload from "@/utils/upload";
import { computed, inject, ref } from "vue";
-import { useRouter } from "vue-router";
+import { useRouter, useRoute } from "vue-router";
const touches = ref(0);
const $showError = inject("$showError")!;
const router = useRouter();
+const route = useRoute();
const props = defineProps<{
name: string;
@@ -95,8 +93,13 @@ const thumbnailUrl = computed(() => {
path: props.path,
modified: props.modified,
};
-
- return api.getPreviewURL(file as Resource, "thumb");
+ if (route.name === "Share") {
+ const hash = props.url.split("/")[2];
+ file.path = `/${hash}${props.path}`;
+ return pub_api.getPreviewURL(file as Resource, "thumb");
+ } else {
+ return api.getPreviewURL(file as Resource, "thumb");
+ }
});
const isThumbsEnabled = computed(() => {
diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts
index 459141ad..2f5f653a 100644
--- a/frontend/src/stores/auth.ts
+++ b/frontend/src/stores/auth.ts
@@ -1,41 +1,70 @@
import { defineStore } from "pinia";
import { detectLocale, setLocale } from "@/i18n";
import { cloneDeep } from "lodash-es";
+import { useStorage } from "@vueuse/core";
+import { computed, ref } from "vue";
-export const useAuthStore = defineStore("auth", {
- // convert to a function
- state: (): {
- user: IUser | null;
- jwt: string;
- } => ({
- user: null,
- jwt: "",
- }),
- getters: {
- // user and jwt getter removed, no longer needed
- isLoggedIn: (state) => state.user !== null,
- },
- actions: {
- // no context as first argument, use `this` instead
- setUser(user: IUser) {
- if (user === null) {
- this.user = null;
- return;
+export const useAuthStore = defineStore("auth", () => {
+ const registeredUser = ref(null);
+ const jwt = ref("");
+
+ const guestJwt = ref("");
+ const guestUser = useStorage("guest", {
+ locale: "zh-cn",
+ viewMode: "list",
+ singleClick: false,
+ perm: { create: false },
+ });
+
+ const shareConfig = ref({ sortBy: "name", asc: false });
+ const isLoggedIn = computed(() => registeredUser.value !== null);
+ const user = computed({
+ get: () =>
+ isLoggedIn.value ? registeredUser.value : (guestUser.value as IUser),
+ set: (val) => {
+ if (isLoggedIn.value) {
+ registeredUser.value = val;
+ } else {
+ guestUser.value = val;
}
+ },
+ });
- setLocale(user.locale || detectLocale());
- this.user = user;
- },
- updateUser(user: Partial) {
- if (user.locale) {
- setLocale(user.locale);
- }
+ function setUser(_user: IUser) {
+ if (_user === null) {
+ registeredUser.value = null;
+ return;
+ }
- this.user = { ...this.user, ...cloneDeep(user) } as IUser;
- },
- // easily reset state using `$reset`
- clearUser() {
- this.$reset();
- },
- },
+ setLocale(_user.locale || detectLocale());
+ registeredUser.value = _user;
+ }
+
+ function updateUser(_user: Partial) {
+ if (_user.locale) {
+ setLocale(_user.locale);
+ }
+
+ user.value = {
+ ...user.value,
+ ...cloneDeep(_user),
+ } as IUser;
+ }
+ // easily reset state using `$reset`
+ function clearUser() {
+ registeredUser.value = null;
+ }
+
+ return {
+ jwt,
+ guestJwt,
+ shareConfig,
+
+ isLoggedIn,
+ user,
+
+ setUser,
+ updateUser,
+ clearUser,
+ };
});
diff --git a/frontend/src/views/Share.vue b/frontend/src/views/Share.vue
index 53f0cfb1..1beb6606 100644
--- a/frontend/src/views/Share.vue
+++ b/frontend/src/views/Share.vue
@@ -95,7 +95,8 @@
v-if="!req.isDir"
class="share__box__element share__box__center share__box__icon"
>
- {{ icon }}
+
+ {{ icon }}
{{ $t("prompts.displayName") }} {{ req.name }}
@@ -236,51 +237,9 @@
id="shareList"
v-if="req.isDir && req.items.length > 0"
class="share__box share__box__items"
+ style="background-color: transparent"
>
-
-
-
-
-
-
-
-
+ {{ req.items.length - showLimit }}
-
-
-
-
-
{{ t("files.multipleSelectionEnabled") }}
-
(fileStore.multiple = false)"
- tabindex="0"
- role="button"
- :data-title="t('buttons.clear')"
- :aria-label="t('buttons.clear')"
- class="action"
- >
- clear
-
-
-
+
(null);
const showLimit = ref
(100);
@@ -331,7 +293,9 @@ const { t } = useI18n({});
const route = useRoute();
const fileStore = useFileStore();
+const { reload } = storeToRefs(fileStore);
const layoutStore = useLayoutStore();
+const authStore = useAuthStore();
watch(route, () => {
showLimit.value = 100;
@@ -352,16 +316,27 @@ const icon = computed(() => {
});
const link = computed(() => (req.value ? api.getDownloadURL(req.value) : ""));
+
+// 用于转换 URL 的辅助函数
+function transformUrl(url: string) {
+ return url.replace(/share/, "api/public/dl") + "?token=" + token.value;
+}
const raw = computed(() => {
- return req.value
- ? req.value.items[fileStore.selected[0]].url.replace(
- /share/,
- "api/public/dl"
- ) +
- "?token=" +
- token.value
- : "";
+ // 如果 req.value 不存在,则直接返回空字符串
+ if (!req.value) {
+ return "";
+ }
+
+ // 如果 req.value 是目录
+ if (req.value.isDir) {
+ const selectedItemUrl = req.value.items[fileStore.selected[0]].url;
+ return transformUrl(selectedItemUrl);
+ }
+
+ // 如果 req.value 不是目录
+ return transformUrl(req.value.url);
});
+
const inlineLink = computed(() =>
req.value ? api.getDownloadURL(req.value, true) : ""
);
@@ -382,7 +357,6 @@ const modTime = computed(() =>
);
// Functions
-const base64 = (name: any) => Base64.encodeURI(name);
const play = () => {
if (tag.value) {
audio.value?.pause();
@@ -392,6 +366,10 @@ const play = () => {
tag.value = true;
}
};
+
+watch(reload, (newValue) => {
+ newValue && fetchData();
+});
const fetchData = async () => {
fileStore.reload = false;
fileStore.selected = [];
@@ -408,12 +386,12 @@ const fetchData = async () => {
let url = route.path;
if (url === "") url = "/";
if (url[0] !== "/") url = "/" + url;
-
try {
const file = await api.fetch(url, password.value);
file.hash = hash.value;
token.value = file.token || "";
+ authStore.guestJwt = token.value;
fileStore.updateRequest(file);
document.title = `${file.name} - ${document.title}`;
@@ -504,6 +482,7 @@ onMounted(async () => {
hash.value = route.params.path[0];
window.addEventListener("keydown", keyEvent);
await fetchData();
+ fileStore.selected[0] = 0;
});
onBeforeUnmount(() => {
diff --git a/frontend/src/views/files/FileListing.vue b/frontend/src/views/files/FileListing.vue
index a26ac67e..4d6f41eb 100644
--- a/frontend/src/views/files/FileListing.vue
+++ b/frontend/src/views/files/FileListing.vue
@@ -74,6 +74,7 @@
/>
{
"sorting",
]);
}
+ authStore.shareConfig.sortBy = by;
+ authStore.shareConfig.asc = asc;
} catch (e: any) {
$showError(e);
}
@@ -904,8 +907,10 @@ const switchView = async () => {
viewMode: modes[authStore.user?.viewMode ?? "list"] || "list",
};
- // @ts-ignore
- users.update(data, ["viewMode"]).catch($showError);
+ if (authStore.isLoggedIn) {
+ // @ts-ignore
+ users.update(data, ["viewMode"]).catch($showError);
+ }
// @ts-ignore
authStore.updateUser(data);
diff --git a/http/http.go b/http/http.go
index f91ec426..9ed79b48 100644
--- a/http/http.go
+++ b/http/http.go
@@ -90,6 +90,8 @@ func NewHandler(
public := api.PathPrefix("/public").Subrouter()
public.PathPrefix("/dl").Handler(monkey(publicDlHandler, "/api/public/dl/")).Methods("GET")
public.PathPrefix("/share").Handler(monkey(publicShareHandler, "/api/public/share/")).Methods("GET")
+ public.PathPrefix("/preview/{size}/{path:.*}").
+ Handler(monkey(publicPreviewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/public/preview")).Methods("GET")
return stripPrefix(server.BaseURL, r), nil
}
diff --git a/http/preview.go b/http/preview.go
index 1abc6019..2fbefa9b 100644
--- a/http/preview.go
+++ b/http/preview.go
@@ -111,7 +111,8 @@ func handleImagePreview(
}
func createPreview(imgSvc ImgService, fileCache FileCache,
- file *files.FileInfo, previewSize PreviewSize) ([]byte, error) {
+ file *files.FileInfo, previewSize PreviewSize,
+) ([]byte, error) {
fd, err := file.Fs.Open(file.Path)
if err != nil {
return nil, err
diff --git a/http/public.go b/http/public.go
index 5e9e01ba..964a9c03 100644
--- a/http/public.go
+++ b/http/public.go
@@ -2,12 +2,14 @@ package http
import (
"errors"
+ "fmt"
"net/http"
"net/url"
"path"
"path/filepath"
"strings"
+ "github.com/gorilla/mux"
"github.com/spf13/afero"
"golang.org/x/crypto/bcrypt"
@@ -98,7 +100,16 @@ var publicShareHandler = withHashFile(func(w http.ResponseWriter, r *http.Reques
file := d.raw.(*files.FileInfo)
if file.IsDir {
- file.Listing.Sorting = files.Sorting{By: "name", Asc: false}
+ sortBy := r.URL.Query().Get("s")
+ ascStr := r.URL.Query().Get("a")
+ asc := false
+ if sortBy == "" {
+ sortBy = "name"
+ }
+ if ascStr == "1" {
+ asc = true
+ }
+ file.Listing.Sorting = files.Sorting{By: sortBy, Asc: asc}
file.Listing.ApplySort()
return renderJSON(w, r, file)
}
@@ -115,6 +126,28 @@ var publicDlHandler = withHashFile(func(w http.ResponseWriter, r *http.Request,
return rawDirHandler(w, r, d, file)
})
+func publicPreviewHandler(imgSvc ImgService, fileCache FileCache, enableThumbnails, resizePreview bool) handleFunc {
+ return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
+ vars := mux.Vars(r)
+ previewSize, err := ParsePreviewSize(vars["size"])
+ if err != nil {
+ return http.StatusBadRequest, err
+ }
+ r.URL.Path = vars["path"]
+ return withHashFile(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
+ file := d.raw.(*files.FileInfo)
+ setContentDisposition(w, r, file)
+
+ switch file.Type {
+ case "image":
+ return handleImagePreview(w, r, imgSvc, fileCache, file, previewSize, enableThumbnails, resizePreview)
+ default:
+ return http.StatusNotImplemented, fmt.Errorf("can't create preview for %s type", file.Type)
+ }
+ })(w, r, d)
+ }
+}
+
func authenticateShareRequest(r *http.Request, l *share.Link) (int, error) {
if l.PasswordHash == "" {
return 0, nil