Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shortcut url followup #9908

Merged
merged 25 commits into from
Nov 8, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add search and fix unit tests
AlexAndBear committed Nov 6, 2023
commit b077352a95a9f39c652b111d7542b7ef3dbc0a7f
102 changes: 93 additions & 9 deletions packages/web-pkg/src/components/CreateShortcutModal.vue
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@
:close-on-click="true"
>
<oc-list>
<li class="oc-p-s">
<li class="oc-p-xs">
<oc-button
class="oc-width-1-1"
appearance="raw"
@@ -36,6 +36,29 @@
<span v-text="dropItemUrl" />
</oc-button>
</li>
<li v-if="searchTask.isRunning" class="oc-p-xs oc-flex oc-flex-center">
AlexAndBear marked this conversation as resolved.
Show resolved Hide resolved
<oc-spinner />
</li>
<li v-for="(resource, index) in searchResults" :key="index" class="oc-p-xs">
<oc-button
class="oc-width-1-1"
appearance="raw"
justify-content="left"
@click="dropItemResourceClicked(resource)"
>
<oc-resource
:resource="resource"
:is-resource-clickable="false"
:path-prefix="getPathPrefix(resource)"
:is-path-displayed="true"
:folder-link="getFolderLink(resource)"
:parent-folder-link-icon-additional-attributes="
getParentFolderLinkIconAdditionalAttributes(resource)
"
:parent-folder-name="getParentFolderName(resource)"
/>
</oc-button>
</li>
</oc-list>
</oc-drop>
<div class="oc-flex oc-width-1-1 oc-mt-m">
@@ -58,13 +81,18 @@
<script lang="ts">
import { defineComponent, PropType, ref, unref, computed, watch, nextTick } from 'vue'
import { Resource, SpaceResource } from '@ownclouders/web-client'
import { useClientService, useStore } from '../composables'
import { useClientService, useFolderLink, useStore } from '../composables'
import { urlJoin } from '@ownclouders/web-client/src/utils'
import { useGettext } from 'vue3-gettext'
import DOMPurify from 'dompurify'
import { OcDrop } from '@ownclouders/design-system/src/components'
import { resolveFileNameDuplicate } from '../helpers'
import { DavProperties } from '@ownclouders/web-client/src/webdav'
import { useTask } from 'vue-concurrency'
import { debounce } from 'lodash-es'

const SEARCH_LIMIT = 7
const SEARCH_DEBOUNCE_TIME = 200
export default defineComponent({
name: 'CreateShortcutModal',
props: {
@@ -81,9 +109,18 @@ export default defineComponent({
const clientService = useClientService()
const { $gettext } = useGettext()
const store = useStore()
const {
getPathPrefix,
getParentFolderName,
getParentFolderLink,
getParentFolderLinkIconAdditionalAttributes,
getFolderLink
} = useFolderLink()

const dropRef = ref(null)
const inputUrl = ref('')
const inputFilename = ref('')
const searchResults = ref(null)

const dropItemUrl = computed(() => {
let url = unref(inputUrl).trim()
@@ -113,6 +150,22 @@ export default defineComponent({
return ''
})

const searchTask = useTask(function* (signal, searchTerm: string) {
searchTerm = `name:"*${searchTerm}*"`

try {
const { resources } = yield clientService.webdav.search(searchTerm, {
searchLimit: SEARCH_LIMIT,
davProperties: DavProperties.Default
})

searchResults.value = resources
} catch (e) {
console.error(e)
searchResults.value = []
}
})

const isMaybeUrl = (input: string) => {
const urlPrefixes = ['http://', 'https://']
return urlPrefixes.some((prefix) => prefix.startsWith(input) || input.startsWith(prefix))
@@ -121,14 +174,27 @@ export default defineComponent({
const dropItemUrlClicked = () => {
inputUrl.value = unref(dropItemUrl)
try {
let fileName = new URL(unref(dropItemUrl)).host
if (unref(files).some((f) => f.name === `${fileName}.url`)) {
fileName = resolveFileNameDuplicate(`${fileName}.url`, 'url', unref(files)).slice(0, -4)
let filename = new URL(unref(dropItemUrl)).host
if (unref(files).some((f) => f.name === `${filename}.url`)) {
filename = resolveFileNameDuplicate(`${filename}.url`, 'url', unref(files)).slice(0, -4)
}
inputFilename.value = fileName
inputFilename.value = filename
} catch (_) {}
}

const dropItemResourceClicked = (resource: Resource) => {
const webURL = new URL(window.location.href)
let filename = resource.name

inputUrl.value = `${webURL.origin}/f/${resource.id}`

if (unref(files).some((f) => f.name === `${filename}.url`)) {
filename = resolveFileNameDuplicate(`${filename}.url`, 'url', unref(files)).slice(0, -4)
}

inputFilename.value = filename
}

const onKeyDownEnter = () => {
if (!unref(confirmButtonDisabled)) {
createShortcut(unref(inputUrl), unref(inputFilename))
@@ -166,6 +232,12 @@ export default defineComponent({
await nextTick()
if (unref(showDrop) && unref(dropRef)) {
;(unref(dropRef) as InstanceType<typeof OcDrop>).show()

const debouncedSearch = debounce(() => {
searchTask.perform(unref(inputUrl))
}, SEARCH_DEBOUNCE_TIME)

debouncedSearch()
}
})

@@ -175,11 +247,19 @@ export default defineComponent({
showDrop,
dropRef,
dropItemUrl,
dropItemUrlClicked,
createShortcut,
searchResults,
confirmButtonDisabled,
inputFileNameErrorMessage,
onKeyDownEnter
searchTask,
createShortcut,
onKeyDownEnter,
dropItemUrlClicked,
dropItemResourceClicked,
getPathPrefix,
getFolderLink,
getParentFolderLink,
getParentFolderName,
getParentFolderLinkIconAdditionalAttributes
}
}
})
@@ -194,6 +274,10 @@ export default defineComponent({
#create-shortcut-modal-contextmenu {
width: 458px;

.oc-resource-name {
text-decoration: none;
}

li:hover {
background-color: var(--oc-color-background-highlight);
}
Original file line number Diff line number Diff line change
@@ -13,15 +13,13 @@ import { useGettext } from 'vue3-gettext'
import { Store } from 'vuex'
import { useClientService } from '../../clientService'
import DOMPurify from 'dompurify'
import { useConfigurationManager } from '../../configuration'

export const useFileActionsOpenShortcut = ({ store }: { store?: Store<any> } = {}) => {
const router = useRouter()
const { $gettext } = useGettext()
const isFilesAppActive = useIsFilesAppActive()
const isSearchActive = useIsSearchActive()
const clientService = useClientService()
const configurationManger = useConfigurationManager()

const extractUrl = (fileContents: string) => {
const regex = /URL=(.+)/
@@ -35,6 +33,7 @@ export const useFileActionsOpenShortcut = ({ store }: { store?: Store<any> } = {
}
const handler = async ({ resources, space }: FileActionOptions) => {
try {
const webURL = new URL(window.location.href)
const fileContents = (await clientService.webdav.getFileContents(space, resources[0])).body
let url = extractUrl(fileContents)

@@ -44,7 +43,7 @@ export const useFileActionsOpenShortcut = ({ store }: { store?: Store<any> } = {
// Omit possible xss code
url = DOMPurify.sanitize(url, { USE_PROFILES: { html: true } })

if (url.startsWith(configurationManger.serverUrl)) {
if (url.startsWith(webURL.origin)) {
window.location.href = url
return
}
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ jest.mock('../../../../../src/composables/configuration', () => ({
window = Object.create(window)
Object.defineProperty(window, 'location', {
value: {
href: ''
href: 'https://demo.owncloud.com'
},
writable: true
})