Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
758 changes: 758 additions & 0 deletions dev/embed-demo.html

Large diffs are not rendered by default.

92 changes: 75 additions & 17 deletions packages/web-app-files/src/components/EmbedActions/EmbedActions.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<section class="files-embed-actions oc-width-1-1 oc-flex oc-flex-middle oc-flex-between oc-my-s">
<section v-if="!isInlineAttach" class="files-embed-actions oc-width-1-1 oc-flex oc-flex-middle oc-flex-between oc-my-s">
<oc-text-input
v-if="chooseFileName"
v-model="fileName"
Expand Down Expand Up @@ -36,26 +36,29 @@
data-testid="button-select"
variation="inverse"
appearance="filled"
:disabled="areSelectActionsDisabled"
:disabled="areSelectActionsDisabled || signing"
@click="emitSelect"
>{{ selectLabel }}
>{{ signing ? $gettext('Preparing…') : selectLabel }}
</oc-button>
</div>
</section>
</template>

<script lang="ts">
import { computed, defineComponent, ref, unref } from 'vue'
import { computed, defineComponent, onMounted, onUnmounted, ref, unref } from 'vue'
import {
embedModeLocationPickMessageData,
FileAction,
routeToContextQuery,
useAbility,
useCapabilityStore,
useClientService,
useEmbedMode,
useFileActionsCreateLink,
useResourcesStore,
useRouter,
useSpacesStore
useSpacesStore,
useUserStore
} from '@ownclouders/web-pkg'
import { Resource } from '@ownclouders/web-client'
import { useGettext } from 'vue3-gettext'
Expand All @@ -68,6 +71,8 @@ export default defineComponent({
const {
isLocationPicker,
isFilePicker,
isInlineAttach,
messagesTargetOrigin,
postMessage,
chooseFileName,
chooseFileNameSuggestion
Expand All @@ -84,9 +89,18 @@ export default defineComponent({
return [unref(currentFolder)]
}

return unref(selectedResources)
const resources = unref(selectedResources)
if (isInlineAttach.value) {
return resources.filter((r) => !r.isFolder)
}
return resources
})

const capabilityStore = useCapabilityStore()
const clientService = useClientService()
const userStore = useUserStore()
const signing = ref(false)

const { actions: createLinkActions } = useFileActionsCreateLink({ enforceModal: true })
const createLinkAction = computed<FileAction>(() => unref(createLinkActions)[0])

Expand All @@ -102,26 +116,68 @@ export default defineComponent({
return [0, unref(fileName).split('.')[0].length] as [number, number]
})

const emitSelect = (): void => {
if (unref(chooseFileName)) {
postMessage<embedModeLocationPickMessageData>('owncloud-embed:select', {
resources: JSON.parse(JSON.stringify(selectedFiles.value)),
fileName: unref(fileName),
locationQuery: JSON.parse(JSON.stringify(routeToContextQuery(unref(router.currentRoute))))
})
const withSignedUrls = async (resources: Resource[]): Promise<Resource[]> => {
if (!capabilityStore.supportUrlSigning || !unref(space)) {
return resources
}

// TODO: adjust type to embedModeLocationPickMessageData later (breaking)
postMessage<Resource[]>(
'owncloud-embed:select',
JSON.parse(JSON.stringify(selectedFiles.value))
return Promise.all(
resources.map(async (resource) => {
if (resource.type === 'folder') {
return resource
}
try {
const signedUrl = await clientService.webdav.getFileUrl(unref(space), resource, {
isUrlSigningEnabled: true,
username: userStore.user?.onPremisesSamAccountName
})
return { ...resource, downloadURL: signedUrl }
} catch {
return resource
}
})
)
}

const emitSelect = async (): Promise<void> => {
signing.value = true
try {
const resources = await withSignedUrls(JSON.parse(JSON.stringify(selectedFiles.value)))

if (unref(chooseFileName)) {
postMessage<embedModeLocationPickMessageData>('owncloud-embed:select', {
resources,
fileName: unref(fileName),
locationQuery: JSON.parse(
JSON.stringify(routeToContextQuery(unref(router.currentRoute)))
)
})
} else {
// TODO: adjust type to embedModeLocationPickMessageData later (breaking)
postMessage<Resource[]>('owncloud-embed:select', resources)
}
} finally {
signing.value = false
}
}

const emitCancel = (): void => {
postMessage<null>('owncloud-embed:cancel', null)
}

const handleRequestSelection = (event: MessageEvent): void => {
if (unref(messagesTargetOrigin) && event.origin !== unref(messagesTargetOrigin)) {
return
}
if (event.data?.name !== 'owncloud-embed:request-selection') {
return
}
emitSelect()
}

onMounted(() => window.addEventListener('message', handleRequestSelection))
onUnmounted(() => window.removeEventListener('message', handleRequestSelection))

return {
chooseFileName,
chooseFileNameSuggestion,
Expand All @@ -130,9 +186,11 @@ export default defineComponent({
canCreatePublicLinks,
isLocationPicker,
isFilePicker,
isInlineAttach,
selectLabel,
emitCancel,
emitSelect,
signing,
space,
createLinkAction,
fileName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FileAction, useEmbedMode, useFileActionsCreateLink } from '@ownclouders
import { mock } from 'vitest-mock-extended'
import { ref } from 'vue'
import { Resource } from '@ownclouders/web-client'
import { flushPromises } from '@vue/test-utils'

vi.mock('@ownclouders/web-pkg', async (importOriginal) => ({
...(await importOriginal<any>()),
Expand Down Expand Up @@ -46,8 +47,10 @@ describe('EmbedActions', () => {
const { wrapper, mocks } = getWrapper({ selectedIds: ['1'] })

await wrapper.find(selectors.btnSelect).trigger('click')
await flushPromises()

expect(mocks.postMessageMock).toHaveBeenCalledWith('owncloud-embed:select', [{ id: '1' }])
expect(mocks.postMessageMock).toHaveBeenCalledTimes(1)
})

it('should enable select action when embedTarget is set to location', () => {
Expand All @@ -63,8 +66,10 @@ describe('EmbedActions', () => {
})

await wrapper.find(selectors.btnSelect).trigger('click')
await flushPromises()

expect(mocks.postMessageMock).toHaveBeenCalledWith('owncloud-embed:select', [{ id: '1' }])
expect(mocks.postMessageMock).toHaveBeenCalledTimes(1)
})
it('should display the file name input when chooseFileName is configured', () => {
const { wrapper } = getWrapper({
Expand All @@ -91,6 +96,7 @@ describe('EmbedActions', () => {
})

await wrapper.find(selectors.btnSelect).trigger('click')
await flushPromises()

expect(mocks.postMessageMock).toHaveBeenCalledWith('owncloud-embed:select', {
fileName: 'file.txt',
Expand All @@ -100,6 +106,7 @@ describe('EmbedActions', () => {
contextRouteQuery: {}
}
})
expect(mocks.postMessageMock).toHaveBeenCalledTimes(1)
})
})

Expand Down Expand Up @@ -177,6 +184,7 @@ function getWrapper(
mock<ReturnType<typeof useEmbedMode>>({
isLocationPicker: ref(isLocationPicker),
isFilePicker: ref(isFilePicker),
isInlineAttach: ref(false),
chooseFileName: ref(chooseFileName),
chooseFileNameSuggestion: ref('file.txt'),
postMessage: postMessageMock
Expand Down
19 changes: 18 additions & 1 deletion packages/web-pkg/src/components/CreateLinkModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
isFolder
})
"
>{{ $gettext('Copy link and password') }}
>{{ confirmPasswordButtonText }}
</oc-button>
</li>
</oc-list>
Expand Down Expand Up @@ -193,6 +193,13 @@ export default defineComponent({
return $gettext('Copy link')
})

const confirmPasswordButtonText = computed(() => {
if (unref(isEmbedEnabled)) {
return $gettext('Share link(s) and password(s)')
}
return $gettext('Copy link and password')
})

const passwordInputKey = ref(uuidV4())
const roleRefs = ref<Record<string, RoleRef>>({})

Expand Down Expand Up @@ -265,11 +272,20 @@ export default defineComponent({
const result = await createLinks()

const succeeded = result.filter(({ status }) => status === 'fulfilled')
// **DEPRECATED**: Always emit the share url for backwards compatibility
if (succeeded.length && unref(isEmbedEnabled)) {
postMessage<string[]>(
'owncloud-embed:share',
(succeeded as PromiseFulfilledResult<LinkShare>[]).map(({ value }) => value.webUrl)
)
// Always emit new event with objects, include password only when copyPassword is enabled
postMessage<Array<{ url: string; password?: string }>>(
'owncloud-embed:share-links',
(succeeded as PromiseFulfilledResult<LinkShare>[]).map(({ value }) => ({
url: value.webUrl,
...(options.copyPassword && { password: password.value })
}))
)
}

const userFacingErrors: Error[] = []
Expand Down Expand Up @@ -350,6 +366,7 @@ export default defineComponent({
updatePassword,
getLinkRoleByType,
confirmButtonText,
confirmPasswordButtonText,
isAdvancedMode,
setAdvancedMode,
onExpiryDateChanged,
Expand Down
10 changes: 8 additions & 2 deletions packages/web-pkg/src/components/FilesList/ResourceTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
:label="getResourceCheckboxLabel(item)"
:label-hidden="true"
size="large"
:disabled="isResourceDisabled(item)"
:disabled="isResourceDisabled(item) || (isInlineAttach && item.isFolder)"
:model-value="isResourceSelected(item)"
:outline="isLatestSelectedItem(item)"
@click.stop="toggleSelection(item.id)"
Expand Down Expand Up @@ -547,6 +547,7 @@ export default defineComponent({
const {
isLocationPicker,
isFilePicker,
isInlineAttach,
postMessage,
isEnabled: isEmbedModeEnabled,
fileTypes: embedModeFileTypes
Expand Down Expand Up @@ -683,6 +684,7 @@ export default defineComponent({
...folderLinkUtils,
postMessage,
isFilePicker,
isInlineAttach,
isLocationPicker,
isEmbedModeEnabled,
emitSelect,
Expand Down Expand Up @@ -1142,7 +1144,11 @@ export default defineComponent({
}
this.emitSelect(
this.resources
.filter((resource) => !this.disabledResources.includes(resource.id))
.filter(
(resource) =>
!this.disabledResources.includes(resource.id) &&
!(this.isInlineAttach && resource.isFolder)
)
.map((resource) => resource.id)
)
},
Expand Down
20 changes: 13 additions & 7 deletions packages/web-pkg/src/components/FilesList/ResourceTiles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
:label-hidden="true"
size="large"
class="oc-flex-inline oc-p-s"
:disabled="!isSpaceResource(resource) && isResourceDisabled(resource)"
:disabled="!isSpaceResource(resource) && (isResourceDisabled(resource) || (isInlineAttach && resource.isFolder))"
:model-value="isResourceSelected(resource)"
@click.stop.prevent="toggleTile([resource, $event])"
/>
Expand Down Expand Up @@ -235,6 +235,7 @@ export default defineComponent({
fileTypes: embedModeFileTypes,
isLocationPicker,
isFilePicker,
isInlineAttach,
postMessage
} = useEmbedMode()
const viewSizeMax = useViewSizeMax()
Expand Down Expand Up @@ -298,7 +299,7 @@ export default defineComponent({
return action.route({ space, resources: [resource] })
}
const emitTileClick = (resource: Resource) => {
if (unref(isEmbedModeEnabled) && unref(isFilePicker)) {
if (unref(isEmbedModeEnabled) && unref(isFilePicker) && !resource.isFolder) {
return postMessage<embedModeFilePickMessageData>('owncloud-embed:file-pick', {
resource: JSON.parse(JSON.stringify(resource)),
locationQuery: JSON.parse(JSON.stringify(routeToContextQuery(unref(router.currentRoute))))
Expand Down Expand Up @@ -332,14 +333,14 @@ export default defineComponent({
})

const isResourceClickable = (resource: Resource) => {
if (isResourceDisabled(resource)) {
return false
}

if (resource.isFolder) {
return true
}

if (isResourceDisabled(resource)) {
return false
}

if (!resource.canDownload() && !canBeOpenedWithSecureView(resource)) {
return false
}
Expand Down Expand Up @@ -387,7 +388,11 @@ export default defineComponent({
emit(
'update:selectedIds',
props.resources
.filter((resource) => !unref(disabledResourceIds).includes(resource.id))
.filter(
(resource) =>
!unref(disabledResourceIds).includes(resource.id) &&
!(unref(isInlineAttach) && resource.isFolder)
)
.map((resource) => resource.id)
)
}
Expand Down Expand Up @@ -622,6 +627,7 @@ export default defineComponent({
ghostTilesCount,
getIndicators,
isFilePicker,
isInlineAttach,
isLocationPicker,
isResourceDisabled,
isSpaceResource,
Expand Down
9 changes: 8 additions & 1 deletion packages/web-pkg/src/composables/embedMode/useEmbedMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,17 @@ export const useEmbedMode = () => {
return configStore.options.embed?.target === 'file'
})

const isInlineAttach = computed(() => {
return configStore.options.embed?.target === 'inline-attach'
})

const fileTypes = computed(() => {
return configStore.options.embed?.fileTypes
})

const messagesTargetOrigin = computed(() => configStore.options.embed?.messagesOrigin)
const messagesTargetOrigin = computed(() => {
return configStore.options.embed?.messagesOrigin
})

const isDelegatingAuthentication = computed(
() => unref(isEnabled) && configStore.options.embed?.delegateAuthentication
Expand Down Expand Up @@ -73,6 +79,7 @@ export const useEmbedMode = () => {
chooseFileName,
chooseFileNameSuggestion,
isFilePicker,
isInlineAttach,
messagesTargetOrigin,
isDelegatingAuthentication,
fileTypes,
Expand Down
Loading