From 7797a4ef18038a877df31bd34f2ebf70d18823f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:36:17 +0100 Subject: [PATCH 1/8] build(deps): bump google.golang.org/protobuf in /tools (#3044) Bumps google.golang.org/protobuf from 1.28.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index b28e935d..8b39ed28 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -173,7 +173,7 @@ require ( golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.9.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tools/go.sum b/tools/go.sum index bc293ba5..6f24f994 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -934,8 +934,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 4c233c3db39ea5a00d6e602ec0ecbddecb590877 Mon Sep 17 00:00:00 2001 From: niubility000 <76441520+niubility000@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:53:33 +0800 Subject: [PATCH 2/8] feat: enable preview in shared folder (#3055) --- frontend/src/views/Share.vue | 115 ++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 10 deletions(-) diff --git a/frontend/src/views/Share.vue b/frontend/src/views/Share.vue index cdcfdf60..0bbf6436 100644 --- a/frontend/src/views/Share.vue +++ b/frontend/src/views/Share.vue @@ -29,7 +29,7 @@
-

+

@@ -73,28 +73,34 @@

@@ -49,18 +49,18 @@ - diff --git a/frontend/src/views/Share.vue b/frontend/src/views/Share.vue index 0bbf6436..53f0cfb1 100644 --- a/frontend/src/views/Share.vue +++ b/frontend/src/views/Share.vue @@ -4,55 +4,56 @@ <action - v-if="selectedCount" + v-if="fileStore.selectedCount" icon="file_download" - :label="$t('buttons.download')" + :label="t('buttons.download')" @action="download" - :counter="selectedCount" + :counter="fileStore.selectedCount" /> <button v-if="isSingleFile()" class="action copy-clipboard" - :data-clipboard-text="linkSelected()" - :aria-label="$t('buttons.copyDownloadLinkToClipboard')" - :title="$t('buttons.copyDownloadLinkToClipboard')" + :aria-label="t('buttons.copyDownloadLinkToClipboard')" + :data-title="t('buttons.copyDownloadLinkToClipboard')" + @click="copyToClipboard(linkSelected())" > <i class="material-icons">content_paste</i> </button> <action icon="check_circle" - :label="$t('buttons.selectMultiple')" + :label="t('buttons.selectMultiple')" @action="toggleMultipleSelection" /> </header-bar> <breadcrumbs :base="'/share/' + hash" /> - <div v-if="loading"> - <h2 class="message delayed" style="padding-top: 3em !important;"> + <div v-if="layoutStore.loading"> + <h2 class="message delayed" style="padding-top: 3em !important"> <div class="spinner"> <div class="bounce1"></div> <div class="bounce2"></div> <div class="bounce3"></div> </div> - <span>{{ $t("files.loading") }}</span> + <span>{{ t("files.loading") }}</span> </h2> </div> <div v-else-if="error"> <div v-if="error.status === 401"> - <div class="card floating" id="password"> + <div class="card floating" id="password" style="z-index: 9999999"> <div v-if="attemptedPasswordLogin" class="share__wrong__password"> - {{ $t("login.wrongCredentials") }} + {{ t("login.wrongCredentials") }} </div> <div class="card-title"> - <h2>{{ $t("login.password") }}</h2> + <h2>{{ t("login.password") }}</h2> </div> <div class="card-content"> <input v-focus + class="input input--block" type="password" - :placeholder="$t('login.password')" + :placeholder="t('login.password')" v-model="password" @keyup.enter="fetchData" /> @@ -61,49 +62,60 @@ <button class="button button--flat" @click="fetchData" - :aria-label="$t('buttons.submit')" - :title="$t('buttons.submit')" + :aria-label="t('buttons.submit')" + :data-title="t('buttons.submit')" > - {{ $t("buttons.submit") }} + {{ t("buttons.submit") }} </button> </div> </div> + <div class="overlay" /> </div> <errors v-else :errorCode="error.status" /> </div> - <div v-else> + <div v-else-if="req !== null"> <div class="share"> - <div class="share__box share__box__info" + <div + class="share__box share__box__info" style=" position: -webkit-sticky; position: sticky; - top:-20.6em; - z-index:999;" + top: -20.6em; + z-index: 999; + " > - <div class="share__box__header" style="height:3em"> + <div class="share__box__header" style="height: 3em"> {{ req.isDir - ? $t("download.downloadFolder") - : $t("download.downloadFile") + ? t("download.downloadFolder") + : t("download.downloadFile") }} </div> - <div v-if="!this.req.isDir" class="share__box__element share__box__center share__box__icon"> + <div + v-if="!req.isDir" + class="share__box__element share__box__center share__box__icon" + > <i class="material-icons">{{ icon }}</i> </div> - <div class="share__box__element" style="height:3em"> + <div class="share__box__element" style="height: 3em"> <strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }} </div> - <div v-if="!this.req.isDir" class="share__box__element" :title="modTime"> + <div v-if="!req.isDir" class="share__box__element" :title="modTime"> <strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }} </div> - <div class="share__box__element" style="height:3em"> + <div class="share__box__element" style="height: 3em"> <strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }} </div> <div class="share__box__element share__box__center"> - <a target="_blank" :href="link" class="button button--flat" style="height:4em"> + <a + target="_blank" + :href="link" + class="button button--flat" + style="height: 4em" + > <div> <i class="material-icons">file_download</i - >{{ $t("buttons.download") }} + >{{ t("buttons.download") }} </div> </a> <a @@ -114,85 +126,123 @@ > <div> <i class="material-icons">open_in_new</i - >{{ $t("buttons.openFile") }} + >{{ t("buttons.openFile") }} </div> </a> - <qrcode-vue v-if="this.req.isDir" :value="fullLink" size="100" level="M"></qrcode-vue> + <qrcode-vue + v-if="req.isDir" + :value="link" + :size="100" + level="M" + ></qrcode-vue> </div> - <div v-if="!this.req.isDir" class="share__box__element share__box__center"> - <qrcode-vue :value="link" size="200" level="M"></qrcode-vue> + <div v-if="!req.isDir" class="share__box__element share__box__center"> + <qrcode-vue :value="link" :size="200" level="M"></qrcode-vue> </div> - <div v-if="this.req.isDir" class="share__box__element share__box__header" style="height:3em"> + <div + v-if="req.isDir" + class="share__box__element share__box__header" + style="height: 3em" + > {{ $t("sidebar.preview") }} </div> <div - v-if="this.req.isDir" - class="share__box__element share__box__center share__box__icon" - style="padding:0em !important;height:12em !important;" - > - <a + v-if="req.isDir" + class="share__box__element share__box__center share__box__icon" + style="padding: 0em !important; height: 12em !important" + > + <a target="_blank" :href="raw" class="button button--flat" - v-if= "!this.$store.state.multiple && - selectedCount === 1 && - req.items[this.selected[0]].type === 'image'" - style="height: 12em; padding:0; margin:0;" + v-if=" + !fileStore.multiple && + fileStore.selectedCount === 1 && + req.items[fileStore.selected[0]].type === 'image' + " + style="height: 12em; padding: 0; margin: 0" > - <img - style="height: 12em;" - :src="raw" - > + <img style="height: 12em" :src="raw" /> </a> <div - v-else-if= " - !this.$store.state.multiple && - selectedCount === 1 && - req.items[this.selected[0]].type === 'audio'" - style="height: 12em; paddingTop:1em; margin:0;" - > - <button @click="play" v-if="!this.tag" style="fontSize:6em !important; border:0px;outline:none; background: white;" class="material-icons">play_circle_filled</button> - <button @click="play" v-if="this.tag" style="fontSize:6em !important; border:0px;outline:none; background: white;" class="material-icons">pause_circle_filled</button> - <audio id="myaudio" - :src="raw" - controls="controls" + v-else-if=" + fileStore.multiple && + fileStore.selectedCount === 1 && + req.items[fileStore.selected[0]].type === 'audio' + " + style="height: 12em; padding-top: 1em; margin: 0" + > + <button + @click="play" + v-if="!tag" + style=" + font-size: 6em !important; + border: 0px; + outline: none; + background: white; + " + class="material-icons" + > + play_circle_filled + </button> + <button + @click="play" + v-if="tag" + style=" + font-size: 6em !important; + border: 0px; + outline: none; + background: white; + " + class="material-icons" + > + pause_circle_filled + </button> + <audio + id="myaudio" + ref="audio" + :src="raw" + controls :autoplay="tag" - > - </audio> + ></audio> </div> - <video - v-else-if= " - !this.$store.state.multiple && - selectedCount === 1 && - req.items[this.selected[0]].type === 'video'" - style="height: 12em; padding:0; margin:0;" - :src="raw" - controls="controls" - > - Sorry, your browser doesn't support embedded videos, but don't worry, - you can <a :href="raw">download it</a> - and watch it with your favorite video player! - </video> - <i - v-else-if= " - !this.$store.state.multiple && - selectedCount === 1 && - req.items[this.selected[0]].isDir" - class="material-icons">folder + <video + v-else-if=" + !fileStore.multiple && + fileStore.selectedCount === 1 && + req.items[fileStore.selected[0]].type === 'video' + " + style="height: 12em; padding: 0; margin: 0" + :src="raw" + controls + > + Sorry, your browser doesn't support embedded videos, but don't + worry, you can <a :href="raw">download it</a> + and watch it with your favorite video player! + </video> + <i + v-else-if=" + !fileStore.multiple && + fileStore.selectedCount === 1 && + req.items[fileStore.selected[0]].isDir + " + class="material-icons" + >folder </i> <i v-else class="material-icons">call_to_action</i> </div> </div> - <div id="shareList" + <div + id="shareList" v-if="req.isDir && req.items.length > 0" class="share__box share__box__items" > <div class="share__box__header" v-if="req.isDir"> - {{ $t("files.files") }} + {{ t("files.files") }} </div> <div id="listing" class="list file-icons"> <item - v-for="item in req.items.slice(0, this.showLimit)" + v-for="item in req.items.slice(0, showLimit)" :key="base64(item.name)" v-bind:index="item.index" v-bind:name="item.name" @@ -215,16 +265,16 @@ </div> <div - :class="{ active: $store.state.multiple }" + :class="{ active: fileStore.multiple }" id="multiple-selection" > - <p>{{ $t("files.multipleSelectionEnabled") }}</p> + <p>{{ t("files.multipleSelectionEnabled") }}</p> <div - @click="$store.commit('multiple', false)" + @click="() => (fileStore.multiple = false)" tabindex="0" role="button" - :title="$t('files.clear')" - :aria-label="$t('files.clear')" + :data-title="t('buttons.clear')" + :aria-label="t('buttons.clear')" class="action" > <i class="material-icons">clear</i> @@ -238,7 +288,7 @@ > <h2 class="message"> <i class="material-icons">sentiment_dissatisfied</i> - <span>{{ $t("files.lonely") }}</span> + <span>{{ t("files.lonely") }}</span> </h2> </div> </div> @@ -246,11 +296,11 @@ </div> </template> -<script> -import { mapState, mapMutations, mapGetters } from "vuex"; +<script setup lang="ts"> import { pub as api } from "@/api"; import { filesize } from "@/utils"; -import moment from "moment/min/moment-with-locales"; +import dayjs from "dayjs"; +import { Base64 } from "js-base64"; import HeaderBar from "@/components/header/HeaderBar.vue"; import Action from "@/components/header/Action.vue"; @@ -258,198 +308,223 @@ import Breadcrumbs from "@/components/Breadcrumbs.vue"; import Errors from "@/views/Errors.vue"; import QrcodeVue from "qrcode.vue"; import Item from "@/components/files/ListingItem.vue"; -import Clipboard from "clipboard"; +import { useFileStore } from "@/stores/file"; +import { useLayoutStore } from "@/stores/layout"; +import { computed, inject, onMounted, onBeforeUnmount, ref, watch } from "vue"; +import { useRoute } from "vue-router"; +import { useI18n } from "vue-i18n"; +import { StatusError } from "@/api/utils"; +import { copy } from "@/utils/clipboard"; -export default { - name: "share", - components: { - HeaderBar, - Action, - Breadcrumbs, - Item, - QrcodeVue, - Errors, - }, - data: () => ({ - error: null, - showLimit: 100, - password: "", - attemptedPasswordLogin: false, - hash: null, - token: null, - clip: null, - tag: false, - }), - watch: { - $route: function () { - this.showLimit = 100; +const error = ref<StatusError | null>(null); +const showLimit = ref<number>(100); +const password = ref<string>(""); +const attemptedPasswordLogin = ref<boolean>(false); +const hash = ref<string>(""); +const token = ref<string>(""); +const audio = ref<HTMLAudioElement>(); +const tag = ref<boolean>(false); - this.fetchData(); - }, - }, - created: async function () { - const hash = this.$route.params.pathMatch.split("/")[0]; - this.hash = hash; - await this.fetchData(); - }, - mounted() { - window.addEventListener("keydown", this.keyEvent); - this.clip = new Clipboard(".copy-clipboard"); - this.clip.on("success", () => { - this.$showSuccess(this.$t("success.linkCopied")); - }); - }, - beforeDestroy() { - window.removeEventListener("keydown", this.keyEvent); - this.clip.destroy(); - }, - computed: { - ...mapState(["req", "loading", "multiple", "selected"]), - ...mapGetters(["selectedCount"]), - icon: function () { - if (this.req.isDir) return "folder"; - if (this.req.type === "image") return "insert_photo"; - if (this.req.type === "audio") return "volume_up"; - if (this.req.type === "video") return "movie"; - return "insert_drive_file"; - }, - link: function () { - return api.getDownloadURL(this.req); - }, - raw: function () { - return this.req.items[this.selected[0]].url.replace(/share/, 'api/public/dl')+'?token='+this.token; - }, - inlineLink: function () { - return api.getDownloadURL(this.req, true); - }, - humanSize: function () { - if (this.req.isDir) { - return this.req.items.length; - } +const $showSuccess = inject<IToastSuccess>("$showSuccess")!; - return filesize(this.req.size); - }, - humanTime: function () { - return moment(this.req.modified).fromNow(); - }, - modTime: function () { - return new Date(Date.parse(this.req.modified)).toLocaleString(); - }, - }, - methods: { - ...mapMutations(["resetSelected", "updateRequest", "setLoading"]), - base64: function (name) { - return window.btoa(unescape(encodeURIComponent(name))); - }, - play() { - var audio = document.getElementById('myaudio'); - if(this.tag){ - audio.pause(); - this.tag = false; - } else { - audio.play(); - this.tag = true; - } - }, - fetchData: async function () { - // Reset view information. - this.$store.commit("setReload", false); - this.$store.commit("resetSelected"); - this.$store.commit("multiple", false); - this.$store.commit("closeHovers"); +const { t } = useI18n({}); - // Set loading to true and reset the error. - this.setLoading(true); - this.error = null; +const route = useRoute(); +const fileStore = useFileStore(); +const layoutStore = useLayoutStore(); - if (this.password !== "") { - this.attemptedPasswordLogin = true; - } +watch(route, () => { + showLimit.value = 100; + fetchData(); +}); - let url = this.$route.path; - if (url === "") url = "/"; - if (url[0] !== "/") url = "/" + url; +const req = computed(() => fileStore.req); - try { - let file = await api.fetch(url, this.password); - file.hash = this.hash; +// Define computes - this.token = file.token || ""; +const icon = computed(() => { + if (req.value === null) return "insert_drive_file"; + if (req.value.isDir) return "folder"; + if (req.value.type === "image") return "insert_photo"; + if (req.value.type === "audio") return "volume_up"; + if (req.value.type === "video") return "movie"; + return "insert_drive_file"; +}); - this.updateRequest(file); - document.title = `${file.name} - ${document.title}`; - } catch (e) { - this.error = e; - } finally { - this.setLoading(false); - } - }, - keyEvent(event) { - // Esc! - if (event.keyCode === 27) { - // If we're on a listing, unselect all - // files and folders. - if (this.selectedCount > 0) { - this.resetSelected(); - } - } - }, - toggleMultipleSelection() { - this.$store.commit("multiple", !this.multiple); - }, - isSingleFile: function () { - return ( - this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir - ); - }, - download() { - if (this.isSingleFile()) { - api.download( - null, - this.hash, - this.token, - this.req.items[this.selected[0]].path - ); - return; - } +const link = computed(() => (req.value ? api.getDownloadURL(req.value) : "")); +const raw = computed(() => { + return req.value + ? req.value.items[fileStore.selected[0]].url.replace( + /share/, + "api/public/dl" + ) + + "?token=" + + token.value + : ""; +}); +const inlineLink = computed(() => + req.value ? api.getDownloadURL(req.value, true) : "" +); +const humanSize = computed(() => { + if (req.value) { + return req.value.isDir + ? req.value.items.length + : filesize(req.value.size ?? 0); + } else { + return ""; + } +}); +const humanTime = computed(() => dayjs(req.value?.modified).fromNow()); +const modTime = computed(() => + req.value + ? new Date(Date.parse(req.value.modified)).toLocaleString() + : new Date().toLocaleString() +); - this.$store.commit("showHover", { - prompt: "download", - confirm: (format) => { - this.$store.commit("closeHovers"); - - let files = []; - - for (let i of this.selected) { - files.push(this.req.items[i].path); - } - - api.download(format, this.hash, this.token, ...files); - }, - }); - }, - linkSelected: function () { - return this.isSingleFile() - ? api.getDownloadURL({ - hash: this.hash, - path: this.req.items[this.selected[0]].path, - }) - : ""; - }, - }, +// Functions +const base64 = (name: any) => Base64.encodeURI(name); +const play = () => { + if (tag.value) { + audio.value?.pause(); + tag.value = false; + } else { + audio.value?.play(); + tag.value = true; + } }; -</script> -<style scoped> - #listing.list{ - height: auto; +const fetchData = async () => { + fileStore.reload = false; + fileStore.selected = []; + fileStore.multiple = false; + layoutStore.closeHovers(); + + // Set loading to true and reset the error. + layoutStore.loading = true; + error.value = null; + if (password.value !== "") { + attemptedPasswordLogin.value = true; } - #shareList{ - overflow-y: scroll; + + 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 || ""; + + fileStore.updateRequest(file); + document.title = `${file.name} - ${document.title}`; + } catch (err) { + if (err instanceof Error) { + error.value = err; + } + } finally { + layoutStore.loading = false; } - @media (min-width: 930px) { - #shareList{ - height: calc(100vh - 9.8em); - overflow-y: auto; +}; + +const keyEvent = (event: KeyboardEvent) => { + if (event.key === "Escape") { + // If we're on a listing, unselect all + // files and folders. + if (fileStore.selectedCount > 0) { + fileStore.selected = []; } } -</style> \ No newline at end of file +}; + +const toggleMultipleSelection = () => { + fileStore.toggleMultiple(); +}; + +const isSingleFile = () => + fileStore.selectedCount === 1 && + !req.value?.items[fileStore.selected[0]].isDir; + +const download = () => { + if (!req.value) return false; + + if (isSingleFile()) { + api.download( + null, + hash.value, + token.value, + req.value.items[fileStore.selected[0]].path + ); + return true; + } + + layoutStore.showHover({ + prompt: "download", + confirm: (format: DownloadFormat) => { + if (req.value === null) return false; + layoutStore.closeHovers(); + + let files: string[] = []; + + for (let i of fileStore.selected) { + files.push(req.value.items[i].path); + } + + api.download(format, hash.value, token.value, ...files); + return true; + }, + }); + + return true; +}; + +const linkSelected = () => { + return isSingleFile() && req.value + ? api.getDownloadURL({ + ...req.value, + hash: hash.value, + path: req.value.items[fileStore.selected[0]].path, + }) + : ""; +}; + +const copyToClipboard = (text: string) => { + copy(text).then( + () => { + // clipboard successfully set + $showSuccess(t("success.linkCopied")); + }, + () => { + // clipboard write failed + } + ); +}; + +onMounted(async () => { + // Created + hash.value = route.params.path[0]; + window.addEventListener("keydown", keyEvent); + await fetchData(); +}); + +onBeforeUnmount(() => { + // Destroyed + window.removeEventListener("keydown", keyEvent); +}); +</script> + +<style scoped> +#listing.list { + height: auto; +} + +#shareList { + overflow-y: scroll; +} + +@media (min-width: 930px) { + #shareList { + height: calc(100vh - 9.8em); + overflow-y: auto; + } +} +</style> diff --git a/frontend/src/views/files/Editor.vue b/frontend/src/views/files/Editor.vue index f4871615..1654937e 100644 --- a/frontend/src/views/files/Editor.vue +++ b/frontend/src/views/files/Editor.vue @@ -1,156 +1,129 @@ <template> - <div - id="editor-container" - @touchmove.prevent.stop - @wheel.prevent.stop - > + <div id="editor-container" @touchmove.prevent.stop @wheel.prevent.stop> <header-bar> - <action icon="close" :label="$t('buttons.close')" @action="close()" /> - <title>{{ req.name }} + + {{ fileStore.req?.name ?? "" }} - +
- diff --git a/frontend/src/views/files/FileListing.vue b/frontend/src/views/files/FileListing.vue new file mode 100644 index 00000000..a26ac67e --- /dev/null +++ b/frontend/src/views/files/FileListing.vue @@ -0,0 +1,960 @@ +