mirror of
https://github.com/nextcloud/server.git
synced 2026-02-27 18:37:17 +01:00
feat(files_sharing): show Account menu on public pages
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
This commit is contained in:
@@ -107,13 +107,12 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider
|
||||
Util::addInitScript(Application::APP_ID, 'init');
|
||||
Util::addInitScript(Application::APP_ID, 'init-public');
|
||||
Util::addScript('files', 'main');
|
||||
Util::addScript(Application::APP_ID, 'public-nickname-handler');
|
||||
|
||||
// Add file-request script if needed
|
||||
$attributes = $share->getAttributes();
|
||||
$isFileRequest = $attributes?->getAttribute('fileRequest', 'enabled') === true;
|
||||
if ($isFileRequest) {
|
||||
Util::addScript(Application::APP_ID, 'public-file-request');
|
||||
}
|
||||
$this->initialState->provideInitialState('isFileRequest', $isFileRequest);
|
||||
|
||||
// Load Viewer scripts
|
||||
if (class_exists(LoadViewer::class)) {
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace OCA\Files_Sharing\Listener;
|
||||
use OCA\Files_Sharing\AppInfo\Application;
|
||||
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Share\IManager;
|
||||
@@ -19,6 +20,7 @@ use OCP\Util;
|
||||
class LoadPublicFileRequestAuthListener implements IEventListener {
|
||||
public function __construct(
|
||||
private IManager $shareManager,
|
||||
private IInitialState $initialState,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -51,9 +53,9 @@ class LoadPublicFileRequestAuthListener implements IEventListener {
|
||||
// Ignore, this is not a file request or the share does not exist
|
||||
}
|
||||
|
||||
if ($isFileRequest) {
|
||||
// Add the script to the public page
|
||||
Util::addScript(Application::APP_ID, 'public-file-request');
|
||||
}
|
||||
Util::addScript(Application::APP_ID, 'public-nickname-handler');
|
||||
|
||||
// Add file-request script if needed
|
||||
$this->initialState->provideInitialState('isFileRequest', $isFileRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { getBuilder } from '@nextcloud/browser-storage'
|
||||
import { getGuestNickname, setGuestNickname } from '@nextcloud/auth'
|
||||
import { getUploader } from '@nextcloud/upload'
|
||||
import { spawnDialog } from '@nextcloud/dialogs'
|
||||
|
||||
import logger from './services/logger'
|
||||
|
||||
const storage = getBuilder('files_sharing').build()
|
||||
|
||||
/**
|
||||
* Setup file-request nickname header for the uploader
|
||||
* @param nickname The nickname
|
||||
*/
|
||||
function registerFileRequestHeader(nickname: string) {
|
||||
const uploader = getUploader()
|
||||
uploader.setCustomHeader('X-NC-Nickname', encodeURIComponent(nickname))
|
||||
logger.debug('Nickname header registered for uploader', { headers: uploader.customHeaders })
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when a nickname was chosen
|
||||
* @param nickname The chosen nickname
|
||||
*/
|
||||
function onSetNickname(nickname: string): void {
|
||||
// Set the nickname
|
||||
setGuestNickname(nickname)
|
||||
// Set the dialog as shown
|
||||
storage.setItem('public-auth-prompt-shown', 'true')
|
||||
// Register header for uploader
|
||||
registerFileRequestHeader(nickname)
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const nickname = getGuestNickname() ?? ''
|
||||
const dialogShown = storage.getItem('public-auth-prompt-shown') !== null
|
||||
|
||||
// If we don't have a nickname or the public auth prompt hasn't been shown yet, show it
|
||||
// We still show the prompt if the user has a nickname to double check
|
||||
if (!nickname || !dialogShown) {
|
||||
spawnDialog(
|
||||
defineAsyncComponent(() => import('./views/PublicAuthPrompt.vue')),
|
||||
{
|
||||
nickname,
|
||||
},
|
||||
onSetNickname as (...rest: unknown[]) => void,
|
||||
)
|
||||
} else {
|
||||
logger.debug('Public auth prompt already shown.', { nickname })
|
||||
registerFileRequestHeader(nickname)
|
||||
}
|
||||
})
|
||||
86
apps/files_sharing/src/public-nickname-handler.ts
Normal file
86
apps/files_sharing/src/public-nickname-handler.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getBuilder } from '@nextcloud/browser-storage'
|
||||
import { getGuestNickname, type NextcloudUser } from '@nextcloud/auth'
|
||||
import { getUploader } from '@nextcloud/upload'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { showGuestUserPrompt } from '@nextcloud/dialogs'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
import logger from './services/logger'
|
||||
import { subscribe } from '@nextcloud/event-bus'
|
||||
|
||||
const storage = getBuilder('files_sharing').build()
|
||||
|
||||
// Setup file-request nickname header for the uploader
|
||||
const registerFileRequestHeader = (nickname: string) => {
|
||||
const uploader = getUploader()
|
||||
uploader.setCustomHeader('X-NC-Nickname', encodeURIComponent(nickname))
|
||||
logger.debug('Nickname header registered for uploader', { headers: uploader.customHeaders })
|
||||
}
|
||||
|
||||
// Callback when a nickname was chosen
|
||||
const onUserInfoChanged = (guest: NextcloudUser) => {
|
||||
logger.debug('User info changed', { guest })
|
||||
registerFileRequestHeader(guest.displayName ?? '')
|
||||
}
|
||||
|
||||
// Monitor nickname changes
|
||||
subscribe('user:info:changed', onUserInfoChanged)
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const nickname = getGuestNickname() ?? ''
|
||||
const dialogShown = storage.getItem('public-auth-prompt-shown') !== null
|
||||
|
||||
// Check if a nickname is mandatory
|
||||
const isFileRequest = loadState('files_sharing', 'isFileRequest', false)
|
||||
|
||||
const owner = loadState('files_sharing', 'owner', '')
|
||||
const ownerDisplayName = loadState('files_sharing', 'ownerDisplayName', '')
|
||||
const label = loadState('files_sharing', 'label', '')
|
||||
const filename = loadState('files_sharing', 'filename', '')
|
||||
|
||||
// If the owner provided a custom label, use it instead of the filename
|
||||
const folder = label || filename
|
||||
|
||||
const options = {
|
||||
nickname,
|
||||
notice: t('files_sharing', 'To upload files to {folder}, you need to provide your name first.', { folder }),
|
||||
subtitle: undefined as string | undefined,
|
||||
title: t('files_sharing', 'Upload files to {folder}', { folder }),
|
||||
}
|
||||
|
||||
// If the guest already has a nickname, we just make them double check
|
||||
if (nickname) {
|
||||
options.notice = t('files_sharing', 'Please confirm your name to upload files to {folder}', { folder })
|
||||
}
|
||||
|
||||
// If the account owner set their name as public,
|
||||
// we show it in the subtitle
|
||||
if (owner) {
|
||||
options.subtitle = t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName })
|
||||
}
|
||||
|
||||
// If this is a file request, then we need a nickname
|
||||
if (isFileRequest) {
|
||||
// If we don't have a nickname or the public auth prompt hasn't been shown yet, show it
|
||||
// We still show the prompt if the user has a nickname to double check
|
||||
if (!nickname || !dialogShown) {
|
||||
logger.debug('Showing public auth prompt.', { nickname })
|
||||
showGuestUserPrompt(options)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!dialogShown && !nickname) {
|
||||
logger.debug('Public auth prompt not shown yet but nickname is not mandatory.', { nickname })
|
||||
return
|
||||
}
|
||||
|
||||
// Else, we just register the nickname header if any.
|
||||
logger.debug('Public auth prompt already shown.', { nickname })
|
||||
registerFileRequestHeader(nickname)
|
||||
})
|
||||
@@ -1,138 +0,0 @@
|
||||
<!--
|
||||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcDialog :buttons="dialogButtons"
|
||||
class="public-auth-prompt"
|
||||
data-cy-public-auth-prompt-dialog
|
||||
is-form
|
||||
:can-close="false"
|
||||
:name="dialogName"
|
||||
@submit="$emit('close', name)">
|
||||
<p v-if="owner" class="public-auth-prompt__subtitle">
|
||||
{{ t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName }) }}
|
||||
</p>
|
||||
|
||||
<!-- Header -->
|
||||
<NcNoteCard class="public-auth-prompt__header"
|
||||
:text="t('files_sharing', 'To upload files, you need to provide your name first.')"
|
||||
type="info" />
|
||||
|
||||
<!-- Form -->
|
||||
<NcTextField ref="input"
|
||||
class="public-auth-prompt__input"
|
||||
data-cy-public-auth-prompt-dialog-name
|
||||
:label="t('files_sharing', 'Name')"
|
||||
:placeholder="t('files_sharing', 'Enter your name')"
|
||||
minlength="2"
|
||||
name="name"
|
||||
required
|
||||
:value.sync="name" />
|
||||
</NcDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
import NcDialog from '@nextcloud/vue/components/NcDialog'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
|
||||
import { getGuestNameValidity } from '../services/GuestNameValidity'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PublicAuthPrompt',
|
||||
|
||||
components: {
|
||||
NcDialog,
|
||||
NcNoteCard,
|
||||
NcTextField,
|
||||
},
|
||||
|
||||
props: {
|
||||
/**
|
||||
* Preselected nickname
|
||||
* @default '' No name preselected by default
|
||||
*/
|
||||
nickname: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
t,
|
||||
|
||||
owner: loadState('files_sharing', 'owner', ''),
|
||||
ownerDisplayName: loadState('files_sharing', 'ownerDisplayName', ''),
|
||||
label: loadState('files_sharing', 'label', ''),
|
||||
note: loadState('files_sharing', 'note', ''),
|
||||
filename: loadState('files_sharing', 'filename', ''),
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
dialogName() {
|
||||
return this.t('files_sharing', 'Upload files to {folder}', { folder: this.label || this.filename })
|
||||
},
|
||||
dialogButtons() {
|
||||
return [{
|
||||
label: t('files_sharing', 'Submit name'),
|
||||
type: 'primary',
|
||||
nativeType: 'submit',
|
||||
}]
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
/** Reset name to pre-selected nickname (e.g. Talk / Collabora ) */
|
||||
nickname: {
|
||||
handler() {
|
||||
this.name = this.nickname
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
|
||||
name() {
|
||||
// Check validity of the new name
|
||||
const newName = this.name.trim?.() || ''
|
||||
const input = (this.$refs.input as Vue|undefined)?.$el.querySelector('input')
|
||||
if (!input) {
|
||||
return
|
||||
}
|
||||
|
||||
const validity = getGuestNameValidity(newName)
|
||||
input.setCustomValidity(validity)
|
||||
input.reportValidity()
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.public-auth-prompt {
|
||||
&__subtitle {
|
||||
// Smaller than dialog title
|
||||
font-size: 1.25em;
|
||||
margin-block: 0 calc(3 * var(--default-grid-baseline));
|
||||
}
|
||||
|
||||
&__header {
|
||||
margin-block: 0 calc(3 * var(--default-grid-baseline));
|
||||
}
|
||||
|
||||
&__input {
|
||||
margin-block: calc(4 * var(--default-grid-baseline)) calc(2 * var(--default-grid-baseline));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -336,6 +336,7 @@ class ShareControllerTest extends \Test\TestCase {
|
||||
'fileId' => 111,
|
||||
'owner' => 'ownerUID',
|
||||
'ownerDisplayName' => 'ownerDisplay',
|
||||
'isFileRequest' => false,
|
||||
];
|
||||
|
||||
$response = $this->shareController->showShare();
|
||||
@@ -480,6 +481,7 @@ class ShareControllerTest extends \Test\TestCase {
|
||||
'disclaimer' => 'My disclaimer text',
|
||||
'owner' => 'ownerUID',
|
||||
'ownerDisplayName' => 'ownerDisplay',
|
||||
'isFileRequest' => false,
|
||||
'note' => 'The note',
|
||||
'label' => 'A label',
|
||||
];
|
||||
|
||||
@@ -78,9 +78,14 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick(e) {
|
||||
this.loading = true
|
||||
onClick(e: MouseEvent) {
|
||||
this.$emit('click', e)
|
||||
|
||||
// Allow to not show the loading indicator
|
||||
// in case the click event was already handled
|
||||
if (!e.defaultPrevented) {
|
||||
this.loading = true
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,22 +11,24 @@
|
||||
role="presentation"
|
||||
@click="$emit('click')">
|
||||
<template #icon>
|
||||
<div role="presentation" :class="['icon', icon, 'public-page-menu-entry__icon']" />
|
||||
<slot v-if="$scopedSlots.icon" name="icon" />
|
||||
<div v-else role="presentation" :class="['icon', icon, 'public-page-menu-entry__icon']" />
|
||||
</template>
|
||||
</NcListItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NcListItem from '@nextcloud/vue/components/NcListItem'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
import NcListItem from '@nextcloud/vue/components/NcListItem'
|
||||
|
||||
const props = defineProps<{
|
||||
/** Only emit click event but do not open href */
|
||||
clickOnly?: boolean
|
||||
// menu entry props
|
||||
id: string
|
||||
label: string
|
||||
icon: string
|
||||
icon?: string
|
||||
href: string
|
||||
details?: string
|
||||
}>()
|
||||
|
||||
15
core/src/public-page-user-menu.ts
Normal file
15
core/src/public-page-user-menu.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getCSPNonce } from '@nextcloud/auth'
|
||||
import Vue from 'vue'
|
||||
|
||||
import PublicPageUserMenu from './views/PublicPageUserMenu.vue'
|
||||
|
||||
__webpack_nonce__ = getCSPNonce()
|
||||
|
||||
const View = Vue.extend(PublicPageUserMenu)
|
||||
const instance = new View()
|
||||
instance.$mount('#public-page-user-menu')
|
||||
@@ -211,7 +211,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we do not wast space, as the header menu sets a default width of 350px
|
||||
// Ensure we do not waste space, as the header menu sets a default width of 350px
|
||||
:deep(.header-menu__content) {
|
||||
width: fit-content !important;
|
||||
}
|
||||
|
||||
135
core/src/views/PublicPageUserMenu.vue
Normal file
135
core/src/views/PublicPageUserMenu.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<!--
|
||||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<template>
|
||||
<NcHeaderMenu id="public-page-user-menu"
|
||||
class="public-page-user-menu"
|
||||
is-nav
|
||||
:aria-label="t('core', 'User menu')"
|
||||
:description="avatarDescription">
|
||||
<template #trigger>
|
||||
<NcAvatar class="public-page-user-menu__avatar"
|
||||
disable-menu
|
||||
disable-tooltip
|
||||
is-guest
|
||||
:user="displayName || '?'" />
|
||||
</template>
|
||||
<ul class="public-page-user-menu__list">
|
||||
<!-- Privacy notice -->
|
||||
<NcNoteCard class="public-page-user-menu__list-note"
|
||||
:text="privacyNotice"
|
||||
type="info" />
|
||||
|
||||
<!-- Nickname dialog -->
|
||||
<AccountMenuEntry id="set-nickname"
|
||||
:name="!displayName ? t('core', 'Set public name') : t('core', 'Change public name')"
|
||||
href="#"
|
||||
@click.prevent.stop="setNickname">
|
||||
<template #icon>
|
||||
<IconAccount />
|
||||
</template>
|
||||
</AccountMenuEntry>
|
||||
</ul>
|
||||
</NcHeaderMenu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { NextcloudUser } from '@nextcloud/auth'
|
||||
|
||||
import '@nextcloud/dialogs/style.css'
|
||||
import { defineComponent } from 'vue'
|
||||
import { getGuestUser } from '@nextcloud/auth'
|
||||
import { showGuestUserPrompt } from '@nextcloud/dialogs'
|
||||
import { subscribe } from '@nextcloud/event-bus'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
import NcAvatar from '@nextcloud/vue/components/NcAvatar'
|
||||
import NcHeaderMenu from '@nextcloud/vue/components/NcHeaderMenu'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import IconAccount from 'vue-material-design-icons/Account.vue'
|
||||
|
||||
import AccountMenuEntry from '../components/AccountMenu/AccountMenuEntry.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PublicPageUserMenu',
|
||||
components: {
|
||||
AccountMenuEntry,
|
||||
IconAccount,
|
||||
NcAvatar,
|
||||
NcHeaderMenu,
|
||||
NcNoteCard,
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
t,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
displayName: getGuestUser().displayName,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
avatarDescription(): string {
|
||||
return t('core', 'User menu')
|
||||
},
|
||||
|
||||
privacyNotice(): string {
|
||||
return this.displayName
|
||||
? t('core', 'You will be identified as {user} by the account owner.', { user: this.displayName })
|
||||
: t('core', 'You are currently not identified.')
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
subscribe('user:info:changed', (user: NextcloudUser) => {
|
||||
this.displayName = user.displayName || ''
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
setNickname() {
|
||||
showGuestUserPrompt({
|
||||
nickname: this.displayName,
|
||||
cancellable: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.public-page-user-menu {
|
||||
box-sizing: border-box;
|
||||
|
||||
// Ensure we do not waste space, as the header menu sets a default width of 350px
|
||||
:deep(.header-menu__content) {
|
||||
width: fit-content !important;
|
||||
}
|
||||
|
||||
&__list-note {
|
||||
padding-block: 5px !important;
|
||||
padding-inline: 5px !important;
|
||||
max-width: 300px;
|
||||
margin: 5px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
&__list {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
padding-block: var(--default-grid-baseline) 0;
|
||||
padding-inline: 0 var(--default-grid-baseline);
|
||||
|
||||
> :deep(li) {
|
||||
box-sizing: border-box;
|
||||
// basically "fit-content"
|
||||
flex: 0 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -77,6 +77,7 @@ p($theme->getTitle());
|
||||
|
||||
<div class="header-end">
|
||||
<div id="public-page-menu"></div>
|
||||
<div id="public-page-user-menu"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -96,12 +96,12 @@ function checkExpirationDateState(enforced: boolean, hasDefault: boolean) {
|
||||
* @param shareName The name of the shared folder
|
||||
* @param options The share options
|
||||
*/
|
||||
export function createShare(context: ShareContext, shareName: string, options: ShareOptions | null = null) {
|
||||
export function createLinkShare(context: ShareContext, shareName: string, options: ShareOptions | null = null): Cypress.Chainable<string> {
|
||||
cy.login(context.user)
|
||||
cy.visit('/apps/files')
|
||||
openSharingPanel(shareName)
|
||||
|
||||
cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createShare')
|
||||
cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createLinkShare')
|
||||
cy.findByRole('button', { name: 'Create a new share link' }).click()
|
||||
// Conduct optional checks based on the provided options
|
||||
if (options) {
|
||||
@@ -111,14 +111,14 @@ export function createShare(context: ShareContext, shareName: string, options: S
|
||||
cy.findByRole('button', { name: 'Create share' }).click()
|
||||
}
|
||||
|
||||
return cy.wait('@createShare')
|
||||
return cy.wait('@createLinkShare')
|
||||
.should(({ response }) => {
|
||||
expect(response?.statusCode).to.eq(200)
|
||||
const url = response?.body?.ocs?.data?.url
|
||||
expect(url).to.match(/^https?:\/\//)
|
||||
context.url = url
|
||||
})
|
||||
.then(() => cy.wrap(context.url))
|
||||
.then(() => cy.wrap(context.url as string))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,7 +173,7 @@ export function setupPublicShare(shareName = 'shared'): Cypress.Chainable<string
|
||||
defaultShareContext.user = user
|
||||
})
|
||||
.then(() => setupData(defaultShareContext.user, shareName))
|
||||
.then(() => createShare(defaultShareContext, shareName))
|
||||
.then(() => createLinkShare(defaultShareContext, shareName))
|
||||
.then((url) => {
|
||||
shareData.shareUrl = url
|
||||
})
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { copyFile, getRowForFile, moveFile, navigateToFolder } from '../../files/FilesUtils.ts'
|
||||
import { getShareUrl, setupPublicShare } from './setup-public-share.ts'
|
||||
import { getShareUrl, setupPublicShare } from './PublicShareUtils.ts'
|
||||
|
||||
describe('files_sharing: Public share - copy and move files', { testIsolation: true }, () => {
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
import type { User } from '@nextcloud/cypress'
|
||||
import { getRowForFile } from '../../files/FilesUtils.ts'
|
||||
import { createShare, setupData } from './setup-public-share.ts'
|
||||
import { createLinkShare, setupData } from './PublicShareUtils.ts'
|
||||
|
||||
describe('files_sharing: Public share - setting the default view mode', () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('files_sharing: Public share - setting the default view mode', () => {
|
||||
|
||||
it('is by default in list view', () => {
|
||||
const context = { user }
|
||||
createShare(context, 'shared')
|
||||
createLinkShare(context, 'shared')
|
||||
.then((url) => {
|
||||
cy.logout()
|
||||
cy.visit(url!)
|
||||
@@ -34,7 +34,7 @@ describe('files_sharing: Public share - setting the default view mode', () => {
|
||||
|
||||
it('can be toggled by user', () => {
|
||||
const context = { user }
|
||||
createShare(context, 'shared')
|
||||
createLinkShare(context, 'shared')
|
||||
.then((url) => {
|
||||
cy.logout()
|
||||
cy.visit(url!)
|
||||
@@ -67,7 +67,7 @@ describe('files_sharing: Public share - setting the default view mode', () => {
|
||||
|
||||
it('can be changed to default grid view', () => {
|
||||
const context = { user }
|
||||
createShare(context, 'shared')
|
||||
createLinkShare(context, 'shared')
|
||||
.then((url) => {
|
||||
// Can set the "grid" view checkbox
|
||||
cy.findByRole('list', { name: 'Link shares' })
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
// @ts-expect-error The package is currently broken - but works...
|
||||
import { deleteDownloadsFolderBeforeEach } from 'cypress-delete-downloads-folder'
|
||||
import { createShare, getShareUrl, openLinkShareDetails, setupPublicShare, type ShareContext } from './setup-public-share.ts'
|
||||
import { createLinkShare, getShareUrl, openLinkShareDetails, setupPublicShare, type ShareContext } from './PublicShareUtils.ts'
|
||||
import { getRowForFile, getRowForFileId, triggerActionForFile, triggerActionForFileId } from '../../files/FilesUtils.ts'
|
||||
import { zipFileContains } from '../../../support/utils/assertions.ts'
|
||||
import type { User } from '@nextcloud/cypress'
|
||||
@@ -22,7 +22,7 @@ describe('files_sharing: Public share - downloading files', { testIsolation: tru
|
||||
cy.uploadContent(user, new Blob(['<content>foo</content>']), 'text/plain', '/file.txt')
|
||||
.then(({ headers }) => { fileId = Number.parseInt(headers['oc-fileid']) })
|
||||
cy.login(user)
|
||||
createShare(context, 'file.txt')
|
||||
createLinkShare(context, 'file.txt')
|
||||
.then(() => cy.logout())
|
||||
.then(() => cy.visit(context.url!))
|
||||
})
|
||||
@@ -179,7 +179,7 @@ describe('files_sharing: Public share - downloading files', { testIsolation: tru
|
||||
cy.mkdir(user, '/test')
|
||||
|
||||
context = { user }
|
||||
createShare(context, 'test')
|
||||
createLinkShare(context, 'test')
|
||||
cy.login(context.user)
|
||||
cy.visit('/apps/files')
|
||||
})
|
||||
|
||||
193
cypress/e2e/files_sharing/public-share/header-avatar.cy.ts
Normal file
193
cypress/e2e/files_sharing/public-share/header-avatar.cy.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { ShareContext } from './PublicShareUtils.ts'
|
||||
import { createLinkShare, setupData } from './PublicShareUtils.ts'
|
||||
|
||||
/**
|
||||
* This tests ensures that on public shares the header avatar menu correctly works
|
||||
*/
|
||||
describe('files_sharing: Public share - header avatar menu', { testIsolation: true }, () => {
|
||||
let context: ShareContext
|
||||
let firstPublicShareUrl = ''
|
||||
let secondPublicShareUrl = ''
|
||||
|
||||
before(() => {
|
||||
cy.createRandomUser()
|
||||
.then((user) => {
|
||||
context = {
|
||||
user,
|
||||
url: undefined,
|
||||
}
|
||||
setupData(context.user, 'public1')
|
||||
setupData(context.user, 'public2')
|
||||
createLinkShare(context, 'public1').then((shareUrl) => {
|
||||
firstPublicShareUrl = shareUrl
|
||||
cy.log(`Created first share with URL: ${shareUrl}`)
|
||||
})
|
||||
createLinkShare(context, 'public2').then((shareUrl) => {
|
||||
secondPublicShareUrl = shareUrl
|
||||
cy.log(`Created second share with URL: ${shareUrl}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.logout()
|
||||
cy.visit(firstPublicShareUrl)
|
||||
})
|
||||
|
||||
it('See the undefined avatar menu', () => {
|
||||
cy.get('header')
|
||||
.findByRole('navigation', { name: /User menu/i })
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: /User menu/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.get('#header-menu-public-page-user-menu')
|
||||
.as('headerMenu')
|
||||
|
||||
// Note that current guest user is not identified
|
||||
cy.get('@headerMenu')
|
||||
.should('be.visible')
|
||||
.findByRole('note')
|
||||
.should('be.visible')
|
||||
.should('contain', 'not identified')
|
||||
|
||||
// Button to set guest name
|
||||
cy.get('@headerMenu')
|
||||
.findByRole('link', { name: /Set public name/i })
|
||||
.should('be.visible')
|
||||
})
|
||||
|
||||
it('Can set public name', () => {
|
||||
cy.get('header')
|
||||
.findByRole('navigation', { name: /User menu/i })
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: /User menu/i })
|
||||
.should('be.visible')
|
||||
.as('userMenuButton')
|
||||
|
||||
// Open the user menu
|
||||
cy.get('@userMenuButton').click()
|
||||
cy.get('#header-menu-public-page-user-menu')
|
||||
.as('headerMenu')
|
||||
|
||||
cy.get('@headerMenu')
|
||||
.findByRole('link', { name: /Set public name/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// Check the dialog is visible
|
||||
cy.findByRole('dialog', { name: /Guest identification/i })
|
||||
.should('be.visible')
|
||||
.as('guestIdentificationDialog')
|
||||
|
||||
// Check the note is visible
|
||||
cy.get('@guestIdentificationDialog')
|
||||
.findByRole('note')
|
||||
.should('contain', 'not identified')
|
||||
|
||||
// Check the input is visible
|
||||
cy.get('@guestIdentificationDialog')
|
||||
.findByRole('textbox', { name: /Name/i })
|
||||
.should('be.visible')
|
||||
.type('{selectAll}John Doe{enter}')
|
||||
|
||||
// Check that the dialog is closed
|
||||
cy.get('@guestIdentificationDialog')
|
||||
.should('not.exist')
|
||||
|
||||
// Check that the avatar changed
|
||||
cy.get('@userMenuButton')
|
||||
.find('img')
|
||||
.invoke('attr', 'src')
|
||||
.should('include', 'avatar/guest/John%20Doe')
|
||||
})
|
||||
|
||||
it('Guest name us persistent and can be changed', () => {
|
||||
cy.get('header')
|
||||
.findByRole('navigation', { name: /User menu/i })
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: /User menu/i })
|
||||
.should('be.visible')
|
||||
.as('userMenuButton')
|
||||
|
||||
// Open the user menu
|
||||
cy.get('@userMenuButton').click()
|
||||
cy.get('#header-menu-public-page-user-menu')
|
||||
.as('headerMenu')
|
||||
|
||||
cy.get('@headerMenu')
|
||||
.findByRole('link', { name: /Set public name/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// Check the dialog is visible
|
||||
cy.findByRole('dialog', { name: /Guest identification/i })
|
||||
.should('be.visible')
|
||||
.as('guestIdentificationDialog')
|
||||
|
||||
// Set the name
|
||||
cy.get('@guestIdentificationDialog')
|
||||
.findByRole('textbox', { name: /Name/i })
|
||||
.should('be.visible')
|
||||
.type('{selectAll}Jane Doe{enter}')
|
||||
|
||||
// Check that the dialog is closed
|
||||
cy.get('@guestIdentificationDialog')
|
||||
.should('not.exist')
|
||||
|
||||
// Create another share
|
||||
cy.visit(secondPublicShareUrl)
|
||||
|
||||
cy.get('header')
|
||||
.findByRole('navigation', { name: /User menu/i })
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: /User menu/i })
|
||||
.should('be.visible')
|
||||
.as('userMenuButton')
|
||||
|
||||
// Open the user menu
|
||||
cy.get('@userMenuButton').click()
|
||||
cy.get('#header-menu-public-page-user-menu')
|
||||
.as('headerMenu')
|
||||
|
||||
// See the note with the current name
|
||||
cy.get('@headerMenu')
|
||||
.findByRole('note')
|
||||
.should('contain', 'You will be identified as Jane Doe')
|
||||
|
||||
cy.get('@headerMenu')
|
||||
.findByRole('link', { name: /Change public name/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// Check the dialog is visible
|
||||
cy.findByRole('dialog', { name: /Guest identification/i })
|
||||
.should('be.visible')
|
||||
.as('guestIdentificationDialog')
|
||||
|
||||
// Check that the note states the current name
|
||||
// cy.get('@guestIdentificationDialog')
|
||||
// .findByRole('note')
|
||||
// .should('contain', 'are currently identified as Jane Doe')
|
||||
|
||||
// Change the name
|
||||
cy.get('@guestIdentificationDialog')
|
||||
.findByRole('textbox', { name: /Name/i })
|
||||
.should('be.visible')
|
||||
.type('{selectAll}Foo Bar{enter}')
|
||||
|
||||
// Check that the dialog is closed
|
||||
cy.get('@guestIdentificationDialog')
|
||||
.should('not.exist')
|
||||
|
||||
// Check that the avatar changed with the second name
|
||||
cy.get('@userMenuButton')
|
||||
.find('img')
|
||||
.invoke('attr', 'src')
|
||||
.should('include', 'avatar/guest/Foo%20Bar')
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { haveValidity, zipFileContains } from '../../../support/utils/assertions.ts'
|
||||
import { getShareUrl, setupPublicShare } from './setup-public-share.ts'
|
||||
import { getShareUrl, setupPublicShare } from './PublicShareUtils.ts'
|
||||
|
||||
/**
|
||||
* This tests ensures that on public shares the header actions menu correctly works
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { getRowForFile, haveValidity, triggerActionForFile } from '../../files/FilesUtils.ts'
|
||||
import { getShareUrl, setupPublicShare } from './setup-public-share.ts'
|
||||
import { getShareUrl, setupPublicShare } from './PublicShareUtils.ts'
|
||||
|
||||
describe('files_sharing: Public share - renaming files', { testIsolation: true }, () => {
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { ShareContext } from './setup-public-share.ts'
|
||||
import type { ShareContext } from './PublicShareUtils.ts'
|
||||
import type { ShareOptions } from '../ShareOptionsType.ts'
|
||||
import { defaultShareOptions } from '../ShareOptionsType.ts'
|
||||
import { setupData, createShare } from './setup-public-share.ts'
|
||||
import { setupData, createLinkShare } from './PublicShareUtils.ts'
|
||||
|
||||
describe('files_sharing: Before create checks', () => {
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions(shareOptions)
|
||||
const shareName = 'passwordAndExpireEnforced'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -64,7 +64,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions(shareOptions)
|
||||
const shareName = 'passwordEnforcedDefaultExpire'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -79,7 +79,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions(shareOptions)
|
||||
const shareName = 'defaultPasswordExpireEnforced'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -93,7 +93,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions(shareOptions)
|
||||
const shareName = 'defaultPasswordAndExpire'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -109,7 +109,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions(shareOptions)
|
||||
const shareName = 'passwordEnforcedExpireSetNotEnforced'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -125,7 +125,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions(shareOptions)
|
||||
const shareName = 'defaultPasswordAndExpirationNotEnforced'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -141,7 +141,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions(shareOptions)
|
||||
const shareName = 'noPasswordExpireEnforced'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -157,7 +157,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions(shareOptions)
|
||||
const shareName = 'defaultExpireNoPasswordEnforced'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -173,7 +173,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
|
||||
const shareName = 'noPasswordExpireDefault'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, shareOptions).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
@@ -183,7 +183,7 @@ describe('files_sharing: Before create checks', () => {
|
||||
applyShareOptions()
|
||||
const shareName = 'noPasswordNoExpireNoDefaults'
|
||||
setupData(shareContext.user, shareName)
|
||||
createShare(shareContext, shareName, null).then((shareUrl) => {
|
||||
createLinkShare(shareContext, shareName, null).then((shareUrl) => {
|
||||
shareContext.url = shareUrl
|
||||
cy.log(`Created share with URL: ${shareUrl}`)
|
||||
})
|
||||
|
||||
@@ -44,6 +44,7 @@ class PublicTemplateResponse extends TemplateResponse {
|
||||
) {
|
||||
parent::__construct($appName, $templateName, $params, 'public', $status, $headers);
|
||||
\OCP\Util::addScript('core', 'public-page-menu');
|
||||
\OCP\Util::addScript('core', 'public-page-user-menu');
|
||||
|
||||
$state = \OCP\Server::get(IInitialStateService::class);
|
||||
$state->provideLazyInitialState('core', 'public-page-menu', function () {
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -12,13 +12,13 @@
|
||||
"@chenfengyuan/vue-qrcode": "^1.0.2",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/svg": "^7.4.47",
|
||||
"@nextcloud/auth": "^2.4.0",
|
||||
"@nextcloud/auth": "^2.5.0",
|
||||
"@nextcloud/axios": "^2.5.1",
|
||||
"@nextcloud/browser-storage": "^0.4.0",
|
||||
"@nextcloud/browserslist-config": "^3.0.1",
|
||||
"@nextcloud/calendar-availability-vue": "^2.2.6",
|
||||
"@nextcloud/capabilities": "^1.2.0",
|
||||
"@nextcloud/dialogs": "^6.3.0",
|
||||
"@nextcloud/dialogs": "^6.3.1",
|
||||
"@nextcloud/event-bus": "^3.3.2",
|
||||
"@nextcloud/files": "^3.10.2",
|
||||
"@nextcloud/initial-state": "^2.2.0",
|
||||
@@ -3902,19 +3902,19 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nextcloud/dialogs": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-6.3.0.tgz",
|
||||
"integrity": "sha512-6WbWiBnGKvcj5UCG0raQhhU7fso1bNX1KEH2iN8PKTAGfxtXAD6XQ48HLuPjUtSZgrpm1azc2cAkECA18SXJaA==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-6.3.1.tgz",
|
||||
"integrity": "sha512-lklTssGdphRZKoR07pYU88btqguEKcQjEpKYom342i1eiMPiejgmoPZEignWJvJhpaN9CT5FoGndCrqqS3BswA==",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@nextcloud/auth": "^2.5.0",
|
||||
"@nextcloud/auth": "^2.5.1",
|
||||
"@nextcloud/axios": "^2.5.1",
|
||||
"@nextcloud/browser-storage": "^0.4.0",
|
||||
"@nextcloud/event-bus": "^3.3.2",
|
||||
"@nextcloud/files": "^3.10.2",
|
||||
"@nextcloud/initial-state": "^2.2.0",
|
||||
"@nextcloud/l10n": "^3.2.0",
|
||||
"@nextcloud/l10n": "^3.3.0",
|
||||
"@nextcloud/router": "^3.0.1",
|
||||
"@nextcloud/sharing": "^0.2.4",
|
||||
"@nextcloud/typings": "^1.9.1",
|
||||
|
||||
@@ -43,13 +43,13 @@
|
||||
"@chenfengyuan/vue-qrcode": "^1.0.2",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/svg": "^7.4.47",
|
||||
"@nextcloud/auth": "^2.4.0",
|
||||
"@nextcloud/auth": "^2.5.0",
|
||||
"@nextcloud/axios": "^2.5.1",
|
||||
"@nextcloud/browser-storage": "^0.4.0",
|
||||
"@nextcloud/browserslist-config": "^3.0.1",
|
||||
"@nextcloud/calendar-availability-vue": "^2.2.6",
|
||||
"@nextcloud/capabilities": "^1.2.0",
|
||||
"@nextcloud/dialogs": "^6.3.0",
|
||||
"@nextcloud/dialogs": "^6.3.1",
|
||||
"@nextcloud/event-bus": "^3.3.2",
|
||||
"@nextcloud/files": "^3.10.2",
|
||||
"@nextcloud/initial-state": "^2.2.0",
|
||||
|
||||
@@ -19,6 +19,7 @@ module.exports = {
|
||||
main: path.join(__dirname, 'core/src', 'main.js'),
|
||||
maintenance: path.join(__dirname, 'core/src', 'maintenance.js'),
|
||||
'public-page-menu': path.resolve(__dirname, 'core/src', 'public-page-menu.ts'),
|
||||
'public-page-user-menu': path.resolve(__dirname, 'core/src', 'public-page-user-menu.ts'),
|
||||
recommendedapps: path.join(__dirname, 'core/src', 'recommendedapps.js'),
|
||||
systemtags: path.resolve(__dirname, 'core/src', 'systemtags/merged-systemtags.js'),
|
||||
'unified-search': path.join(__dirname, 'core/src', 'unified-search.ts'),
|
||||
@@ -58,7 +59,7 @@ module.exports = {
|
||||
'init-public': path.join(__dirname, 'apps/files_sharing/src', 'init-public.ts'),
|
||||
main: path.join(__dirname, 'apps/files_sharing/src', 'main.ts'),
|
||||
'personal-settings': path.join(__dirname, 'apps/files_sharing/src', 'personal-settings.js'),
|
||||
'public-file-request': path.join(__dirname, 'apps/files_sharing/src', 'public-file-request.ts'),
|
||||
'public-nickname-handler': path.join(__dirname, 'apps/files_sharing/src', 'public-nickname-handler.ts'),
|
||||
},
|
||||
files_trashbin: {
|
||||
init: path.join(__dirname, 'apps/files_trashbin/src', 'files-init.ts'),
|
||||
|
||||
Reference in New Issue
Block a user