diff options
author | John Molakvoæ <skjnldsv@protonmail.com> | 2023-08-09 14:59:35 +0200 |
---|---|---|
committer | John Molakvoæ <skjnldsv@protonmail.com> | 2023-08-17 18:56:37 +0200 |
commit | 410f58e43e8db767eaf0b272ab6ffde1841cd6a2 (patch) | |
tree | 5f0d2812ce7cce75b6b72c3652c81f7ae3c26bfd | |
parent | 998b3a2581fb873b03bcf4dc02eafb19390b3cd6 (diff) | |
download | nextcloud-server-410f58e43e8db767eaf0b272ab6ffde1841cd6a2.tar.gz nextcloud-server-410f58e43e8db767eaf0b272ab6ffde1841cd6a2.zip |
chore(files): add Headers, remove legacy methods and cleanup
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
30 files changed, 435 insertions, 599 deletions
diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index 01f85a7c939..24f236a0893 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -187,8 +187,6 @@ class ViewController extends Controller { } } - $nav = new \OCP\Template('files', 'appnavigation', ''); - // Load the files we need \OCP\Util::addStyle('files', 'merged'); \OCP\Util::addScript('files', 'merged-index', 'files'); @@ -203,15 +201,6 @@ class ViewController extends Controller { $favElements['folders'] = []; } - $navItems = \OCA\Files\App::getNavigationManager()->getAll(); - - // parse every menu and add the expanded user value - foreach ($navItems as $key => $item) { - $navItems[$key]['expanded'] = $this->config->getUserValue($userId, 'files', 'show_' . $item['id'], '0') === '1'; - } - - $nav->assign('navigationItems', $navItems); - $contentItems = []; try { @@ -222,7 +211,6 @@ class ViewController extends Controller { } $this->initialState->provideInitialState('storageStats', $storageInfo); - $this->initialState->provideInitialState('navigation', $navItems); $this->initialState->provideInitialState('config', $this->userConfig->getConfigs()); $this->initialState->provideInitialState('viewConfigs', $this->viewConfig->getConfigs()); $this->initialState->provideInitialState('favoriteFolders', $favElements['folders'] ?? []); @@ -231,34 +219,9 @@ class ViewController extends Controller { $filesSortingConfig = json_decode($this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}'), true); $this->initialState->provideInitialState('filesSortingConfig', $filesSortingConfig); - // render the container content for every navigation item - foreach ($navItems as $item) { - $content = ''; - if (isset($item['script'])) { - $content = $this->renderScript($item['appname'], $item['script']); - } - // parse submenus - if (isset($item['sublist'])) { - foreach ($item['sublist'] as $subitem) { - $subcontent = ''; - if (isset($subitem['script'])) { - $subcontent = $this->renderScript($subitem['appname'], $subitem['script']); - } - $contentItems[$subitem['id']] = [ - 'id' => $subitem['id'], - 'content' => $subcontent - ]; - } - } - $contentItems[$item['id']] = [ - 'id' => $item['id'], - 'content' => $content - ]; - } - - $this->eventDispatcher->dispatchTyped(new ResourcesLoadAdditionalScriptsEvent()); $event = new LoadAdditionalScriptsEvent(); $this->eventDispatcher->dispatchTyped($event); + $this->eventDispatcher->dispatchTyped(new ResourcesLoadAdditionalScriptsEvent()); $this->eventDispatcher->dispatchTyped(new LoadSidebar()); // Load Viewer scripts if (class_exists(LoadViewer::class)) { @@ -268,23 +231,9 @@ class ViewController extends Controller { $this->initialState->provideInitialState('templates_path', $this->templateManager->hasTemplateDirectory() ? $this->templateManager->getTemplatePath() : false); $this->initialState->provideInitialState('templates', $this->templateManager->listCreators()); - $params = []; - $params['usedSpacePercent'] = (int) $storageInfo['relative']; - $params['owner'] = $storageInfo['owner'] ?? ''; - $params['ownerDisplayName'] = $storageInfo['ownerDisplayName'] ?? ''; - $params['isPublic'] = false; - $params['allowShareWithLink'] = $this->shareManager->shareApiAllowLinks() ? 'yes' : 'no'; - $params['defaultFileSorting'] = $filesSortingConfig['files']['mode'] ?? 'basename'; - $params['defaultFileSortingDirection'] = $filesSortingConfig['files']['direction'] ?? 'asc'; - $params['showgridview'] = $this->config->getUserValue($userId, 'files', 'show_grid', false); - $showHidden = (bool) $this->config->getUserValue($userId, 'files', 'show_hidden', false); - $params['showHiddenFiles'] = $showHidden ? 1 : 0; - $cropImagePreviews = (bool) $this->config->getUserValue($userId, 'files', 'crop_image_previews', true); - $params['cropImagePreviews'] = $cropImagePreviews ? 1 : 0; - $params['fileNotFound'] = $fileNotFound ? 1 : 0; - $params['appNavigation'] = $nav; - $params['appContents'] = $contentItems; - $params['hiddenFields'] = $event->getHiddenFields(); + $params = [ + 'fileNotFound' => $fileNotFound ? 1 : 0 + ]; $response = new TemplateResponse( Application::APP_ID, diff --git a/apps/files/lib/Event/LoadAdditionalScriptsEvent.php b/apps/files/lib/Event/LoadAdditionalScriptsEvent.php index 5291a776e81..1e2080622f4 100644 --- a/apps/files/lib/Event/LoadAdditionalScriptsEvent.php +++ b/apps/files/lib/Event/LoadAdditionalScriptsEvent.php @@ -31,18 +31,7 @@ use OCP\EventDispatcher\Event; /** * This event is triggered when the files app is rendered. - * It can be used to add additional scripts to the files app. * * @since 17.0.0 */ -class LoadAdditionalScriptsEvent extends Event { - private $hiddenFields = []; - - public function addHiddenField(string $name, string $value): void { - $this->hiddenFields[$name] = $value; - } - - public function getHiddenFields(): array { - return $this->hiddenFields; - } -} +class LoadAdditionalScriptsEvent extends Event {}
\ No newline at end of file diff --git a/apps/files/src/components/FilesListHeader.vue b/apps/files/src/components/FilesListHeader.vue index d36c9dd46a6..74dc224a39b 100644 --- a/apps/files/src/components/FilesListHeader.vue +++ b/apps/files/src/components/FilesListHeader.vue @@ -20,194 +20,51 @@ - --> <template> - <tr> - <th class="files-list__column files-list__row-checkbox"> - <NcCheckboxRadioSwitch v-bind="selectAllBind" @update:checked="onToggleAll" /> - </th> - - <!-- Actions multiple if some are selected --> - <FilesListHeaderActions v-if="!isNoneSelected" - :current-view="currentView" - :selected-nodes="selectedNodes" /> - - <!-- Columns display --> - <template v-else> - <!-- Link to file --> - <th class="files-list__column files-list__row-name files-list__column--sortable" - @click.stop.prevent="toggleSortBy('basename')"> - <!-- Icon or preview --> - <span class="files-list__row-icon" /> - - <!-- Name --> - <FilesListHeaderButton :name="t('files', 'Name')" mode="basename" /> - </th> - - <!-- Actions --> - <th class="files-list__row-actions" /> - - <!-- Size --> - <th v-if="isSizeAvailable" - :class="{'files-list__column--sortable': isSizeAvailable}" - class="files-list__column files-list__row-size"> - <FilesListHeaderButton :name="t('files', 'Size')" mode="size" /> - </th> - - <!-- Mtime --> - <th v-if="isMtimeAvailable" - :class="{'files-list__column--sortable': isMtimeAvailable}" - class="files-list__column files-list__row-mtime"> - <FilesListHeaderButton :name="t('files', 'Modified')" mode="mtime" /> - </th> - - <!-- Custom views columns --> - <th v-for="column in columns" - :key="column.id" - :class="classForColumn(column)"> - <FilesListHeaderButton v-if="!!column.sort" :name="column.title" :mode="column.id" /> - <span v-else> - {{ column.title }} - </span> - </th> - </template> - </tr> + <div v-show="enabled" :class="`files-list__header-${header.id}`"> + <span ref="mount" /> + </div> </template> <script lang="ts"> -import { translate } from '@nextcloud/l10n' -import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' -import Vue from 'vue' - -import { useFilesStore } from '../store/files.ts' -import { useSelectionStore } from '../store/selection.ts' -import FilesListHeaderActions from './FilesListHeaderActions.vue' -import FilesListHeaderButton from './FilesListHeaderButton.vue' -import filesSortingMixin from '../mixins/filesSorting.ts' -import logger from '../logger.js' - -export default Vue.extend({ +/** + * This component is used to render custom + * elements provided by an API. Vue doesn't allow + * to directly render an HTMLElement, so we can do + * this magic here. + */ +export default { name: 'FilesListHeader', - - components: { - FilesListHeaderButton, - NcCheckboxRadioSwitch, - FilesListHeaderActions, - }, - - mixins: [ - filesSortingMixin, - ], - props: { - isMtimeAvailable: { - type: Boolean, - default: false, - }, - isSizeAvailable: { - type: Boolean, - default: false, + header: { + type: Object, + required: true, }, - nodes: { - type: Array, + currentFolder: { + type: Object, required: true, }, - filesListWidth: { - type: Number, - default: 0, + currentView: { + type: Object, + required: true, }, }, - - setup() { - const filesStore = useFilesStore() - const selectionStore = useSelectionStore() - return { - filesStore, - selectionStore, - } - }, - computed: { - currentView() { - return this.$navigation.active - }, - - columns() { - // Hide columns if the list is too small - if (this.filesListWidth < 512) { - return [] - } - return this.currentView?.columns || [] - }, - - dir() { - // Remove any trailing slash but leave root slash - return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1') - }, - - selectAllBind() { - const label = this.isNoneSelected || this.isSomeSelected - ? this.t('files', 'Select all') - : this.t('files', 'Unselect all') - return { - 'aria-label': label, - checked: this.isAllSelected, - indeterminate: this.isSomeSelected, - title: label, - } - }, - - selectedNodes() { - return this.selectionStore.selected - }, - - isAllSelected() { - return this.selectedNodes.length === this.nodes.length - }, - - isNoneSelected() { - return this.selectedNodes.length === 0 - }, - - isSomeSelected() { - return !this.isAllSelected && !this.isNoneSelected + enabled() { + console.debug('Enabled', this.header.id) + return this.header.enabled(this.currentFolder, this.currentView) }, }, - - methods: { - classForColumn(column) { - return { - 'files-list__column': true, - 'files-list__column--sortable': !!column.sort, - 'files-list__row-column-custom': true, - [`files-list__row-${this.currentView.id}-${column.id}`]: true, - } - }, - - onToggleAll(selected) { - if (selected) { - const selection = this.nodes.map(node => node.fileid.toString()) - logger.debug('Added all nodes to selection', { selection }) - this.selectionStore.setLastIndex(null) - this.selectionStore.set(selection) - } else { - logger.debug('Cleared selection') - this.selectionStore.reset() + watch: { + enabled(enabled) { + if (!enabled) { + return } + this.header.updated(this.currentFolder, this.currentView) }, - - t: translate, }, -}) -</script> - -<style scoped lang="scss"> -.files-list__column { - user-select: none; - // Make sure the cell colors don't apply to column headers - color: var(--color-text-maxcontrast) !important; - - &--sortable { - cursor: pointer; - } + mounted() { + console.debug('Mounted', this.header.id) + this.header.render(this.$refs.mount, this.currentFolder, this.currentView) + }, } - -</style> +</script> diff --git a/apps/files/src/components/FilesListFooter.vue b/apps/files/src/components/FilesListTableFooter.vue index b4a2d7eda30..4bda140770d 100644 --- a/apps/files/src/components/FilesListFooter.vue +++ b/apps/files/src/components/FilesListTableFooter.vue @@ -20,7 +20,7 @@ - --> <template> - <tr> + <tr class="files-list__row-footer"> <th class="files-list__row-checkbox"> <span class="hidden-visually">{{ t('files', 'Total rows summary') }}</span> </th> @@ -65,7 +65,7 @@ import { useFilesStore } from '../store/files.ts' import { usePathsStore } from '../store/paths.ts' export default Vue.extend({ - name: 'FilesListFooter', + name: 'FilesListTableFooter', components: { }, diff --git a/apps/files/src/components/FilesListTableHeader.vue b/apps/files/src/components/FilesListTableHeader.vue new file mode 100644 index 00000000000..52060d2589e --- /dev/null +++ b/apps/files/src/components/FilesListTableHeader.vue @@ -0,0 +1,213 @@ +<!-- + - @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> + - + - @author John Molakvoæ <skjnldsv@protonmail.com> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> +<template> + <tr class="files-list__row-head"> + <th class="files-list__column files-list__row-checkbox"> + <NcCheckboxRadioSwitch v-bind="selectAllBind" @update:checked="onToggleAll" /> + </th> + + <!-- Actions multiple if some are selected --> + <FilesListTableHeaderActions v-if="!isNoneSelected" + :current-view="currentView" + :selected-nodes="selectedNodes" /> + + <!-- Columns display --> + <template v-else> + <!-- Link to file --> + <th class="files-list__column files-list__row-name files-list__column--sortable" + @click.stop.prevent="toggleSortBy('basename')"> + <!-- Icon or preview --> + <span class="files-list__row-icon" /> + + <!-- Name --> + <FilesListTableHeaderButton :name="t('files', 'Name')" mode="basename" /> + </th> + + <!-- Actions --> + <th class="files-list__row-actions" /> + + <!-- Size --> + <th v-if="isSizeAvailable" + :class="{'files-list__column--sortable': isSizeAvailable}" + class="files-list__column files-list__row-size"> + <FilesListTableHeaderButton :name="t('files', 'Size')" mode="size" /> + </th> + + <!-- Mtime --> + <th v-if="isMtimeAvailable" + :class="{'files-list__column--sortable': isMtimeAvailable}" + class="files-list__column files-list__row-mtime"> + <FilesListTableHeaderButton :name="t('files', 'Modified')" mode="mtime" /> + </th> + + <!-- Custom views columns --> + <th v-for="column in columns" + :key="column.id" + :class="classForColumn(column)"> + <FilesListTableHeaderButton v-if="!!column.sort" :name="column.title" :mode="column.id" /> + <span v-else> + {{ column.title }} + </span> + </th> + </template> + </tr> +</template> + +<script lang="ts"> +import { translate } from '@nextcloud/l10n' +import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' +import Vue from 'vue' + +import { useFilesStore } from '../store/files.ts' +import { useSelectionStore } from '../store/selection.ts' +import FilesListTableHeaderActions from './FilesListTableHeaderActions.vue' +import FilesListTableHeaderButton from './FilesListTableHeaderButton.vue' +import filesSortingMixin from '../mixins/filesSorting.ts' +import logger from '../logger.js' + +export default Vue.extend({ + name: 'FilesListTableHeader', + + components: { + FilesListTableHeaderButton, + NcCheckboxRadioSwitch, + FilesListTableHeaderActions, + }, + + mixins: [ + filesSortingMixin, + ], + + props: { + isMtimeAvailable: { + type: Boolean, + default: false, + }, + isSizeAvailable: { + type: Boolean, + default: false, + }, + nodes: { + type: Array, + required: true, + }, + filesListWidth: { + type: Number, + default: 0, + }, + }, + + setup() { + const filesStore = useFilesStore() + const selectionStore = useSelectionStore() + return { + filesStore, + selectionStore, + } + }, + + computed: { + currentView() { + return this.$navigation.active + }, + + columns() { + // Hide columns if the list is too small + if (this.filesListWidth < 512) { + return [] + } + return this.currentView?.columns || [] + }, + + dir() { + // Remove any trailing slash but leave root slash + return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1') + }, + + selectAllBind() { + const label = this.isNoneSelected || this.isSomeSelected + ? this.t('files', 'Select all') + : this.t('files', 'Unselect all') + return { + 'aria-label': label, + checked: this.isAllSelected, + indeterminate: this.isSomeSelected, + title: label, + } + }, + + selectedNodes() { + return this.selectionStore.selected + }, + + isAllSelected() { + return this.selectedNodes.length === this.nodes.length + }, + + isNoneSelected() { + return this.selectedNodes.length === 0 + }, + + isSomeSelected() { + return !this.isAllSelected && !this.isNoneSelected + }, + }, + + methods: { + classForColumn(column) { + return { + 'files-list__column': true, + 'files-list__column--sortable': !!column.sort, + 'files-list__row-column-custom': true, + [`files-list__row-${this.currentView.id}-${column.id}`]: true, + } + }, + + onToggleAll(selected) { + if (selected) { + const selection = this.nodes.map(node => node.fileid.toString()) + logger.debug('Added all nodes to selection', { selection }) + this.selectionStore.setLastIndex(null) + this.selectionStore.set(selection) + } else { + logger.debug('Cleared selection') + this.selectionStore.reset() + } + }, + + t: translate, + }, +}) +</script> + +<style scoped lang="scss"> +.files-list__column { + user-select: none; + // Make sure the cell colors don't apply to column headers + color: var(--color-text-maxcontrast) !important; + + &--sortable { + cursor: pointer; + } +} + +</style> diff --git a/apps/files/src/components/FilesListHeaderActions.vue b/apps/files/src/components/FilesListTableHeaderActions.vue index e419c8e5abd..f55487d183b 100644 --- a/apps/files/src/components/FilesListHeaderActions.vue +++ b/apps/files/src/components/FilesListTableHeaderActions.vue @@ -61,7 +61,7 @@ import logger from '../logger.js' const actions = getFileActions() export default Vue.extend({ - name: 'FilesListHeaderActions', + name: 'FilesListTableHeaderActions', components: { CustomSvgIconRender, diff --git a/apps/files/src/components/FilesListHeaderButton.vue b/apps/files/src/components/FilesListTableHeaderButton.vue index 9aac83a185d..ebd1abb4314 100644 --- a/apps/files/src/components/FilesListHeaderButton.vue +++ b/apps/files/src/components/FilesListTableHeaderButton.vue @@ -42,7 +42,7 @@ import Vue from 'vue' import filesSortingMixin from '../mixins/filesSorting.ts' export default Vue.extend({ - name: 'FilesListHeaderButton', + name: 'FilesListTableHeaderButton', components: { MenuDown, diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue index 014f0a89f00..69cab260963 100644 --- a/apps/files/src/components/FilesListVirtual.vue +++ b/apps/files/src/components/FilesListVirtual.vue @@ -20,28 +20,18 @@ - --> <template> - <RecycleScroller ref="recycleScroller" - class="files-list" - key-field="source" - :items="nodes" - :item-size="55" - :table-mode="true" - item-class="files-list__row" - item-tag="tr" - list-class="files-list__body" - list-tag="tbody" - role="table"> - <template #default="{ item, active, index }"> - <!-- File row --> - <FileEntry :active="active" - :index="index" - :is-mtime-available="isMtimeAvailable" - :is-size-available="isSizeAvailable" - :files-list-width="filesListWidth" - :nodes="nodes" - :source="item" /> - </template> - + <VirtualList :data-component="FileEntry" + :data-key="'source'" + :data-sources="nodes" + :item-height="56" + :extra-props="{ + isMtimeAvailable, + isSizeAvailable, + nodes, + filesListWidth, + }" + :scroll-to-index="scrollToIndex"> + <!-- Accessibility description and headers --> <template #before> <!-- Accessibility description --> <caption class="hidden-visually"> @@ -49,42 +39,54 @@ {{ t('files', 'This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list.') }} </caption> - <!-- Thead--> - <FilesListHeader :files-list-width="filesListWidth" + <!-- Headers --> + <FilesListHeader v-for="header in sortedHeaders" + :key="header.id" + :current-folder="currentFolder" + :current-view="currentView" + :header="header" /> + </template> + + <!-- Thead--> + <template #header> + <FilesListTableHeader :files-list-width="filesListWidth" :is-mtime-available="isMtimeAvailable" :is-size-available="isSizeAvailable" :nodes="nodes" /> </template> - <template #after> - <!-- Tfoot--> - <FilesListFooter :files-list-width="filesListWidth" + <!-- Tfoot--> + <template #footer> + <FilesListTableFooter :files-list-width="filesListWidth" :is-mtime-available="isMtimeAvailable" :is-size-available="isSizeAvailable" :nodes="nodes" :summary="summary" /> </template> - </RecycleScroller> + </VirtualList> </template> <script lang="ts"> -import { RecycleScroller } from 'vue-virtual-scroller' import { translate, translatePlural } from '@nextcloud/l10n' +import { getFileListHeaders } from '@nextcloud/files' import Vue from 'vue' +import VirtualList from './VirtualList.vue' import FileEntry from './FileEntry.vue' -import FilesListFooter from './FilesListFooter.vue' import FilesListHeader from './FilesListHeader.vue' +import FilesListTableFooter from './FilesListTableFooter.vue' +import FilesListTableHeader from './FilesListTableHeader.vue' import filesListWidthMixin from '../mixins/filesListWidth.ts' +import { showError } from '@nextcloud/dialogs' export default Vue.extend({ name: 'FilesListVirtual', components: { - RecycleScroller, - FileEntry, FilesListHeader, - FilesListFooter, + FilesListTableHeader, + FilesListTableFooter, + VirtualList, }, mixins: [ @@ -96,6 +98,10 @@ export default Vue.extend({ type: Object, required: true, }, + currentFolder: { + type: Object, + required: true, + }, nodes: { type: Array, required: true, @@ -105,6 +111,7 @@ export default Vue.extend({ data() { return { FileEntry, + headers: getFileListHeaders(), } }, @@ -113,6 +120,21 @@ export default Vue.extend({ return this.nodes.filter(node => node.type === 'file') }, + fileId() { + return parseInt(this.$route.params.fileid || this.$route.query.fileid) || null + }, + + scrollToIndex() { + if (!this.fileId) { + return + } + const index = this.nodes.findIndex(node => node.fileid === this.fileId) + if (index === -1) { + showError(this.t('files', 'File not found')) + } + return Math.max(0, index) + }, + summaryFile() { const count = this.files.length return translatePlural('files', '{count} file', '{count} files', count, { count }) @@ -138,13 +160,14 @@ export default Vue.extend({ } return this.nodes.some(node => node.attributes.size !== undefined) }, - }, - mounted() { - // Make the root recycle scroller a table for proper semantics - const slots = this.$el.querySelectorAll('.vue-recycle-scroller__slot') - slots[0].setAttribute('role', 'thead') - slots[1].setAttribute('role', 'tfoot') + sortedHeaders() { + if (!this.currentFolder || !this.currentView) { + return [] + } + + return [...this.headers].sort((a, b) => a.order - b.order) + }, }, methods: { @@ -173,7 +196,7 @@ export default Vue.extend({ &::v-deep { // Table head, body and footer - tbody, .vue-recycle-scroller__slot { + tbody { display: flex; flex-direction: column; width: 100%; @@ -181,23 +204,35 @@ export default Vue.extend({ position: relative; } + // Before table and thead + .files-list__before { + display: flex; + flex-direction: column; + } + // Table header - .vue-recycle-scroller__slot[role='thead'] { + .files-list__thead { // Pinned on top when scrolling position: sticky; z-index: 10; top: 0; - height: var(--row-height); + } + + .files-list__thead, + .files-list__tfoot { + display: flex; + width: 100%; background-color: var(--color-main-background); + } tr { - position: absolute; + position: relative; display: flex; align-items: center; width: 100%; - border-bottom: 1px solid var(--color-border); user-select: none; + border-bottom: 1px solid var(--color-border); } td, th { diff --git a/apps/files/src/components/NavigationQuota.vue b/apps/files/src/components/NavigationQuota.vue index d38d4d2fd9e..02d473e7def 100644 --- a/apps/files/src/components/NavigationQuota.vue +++ b/apps/files/src/components/NavigationQuota.vue @@ -134,7 +134,7 @@ export default { // User storage stats display .app-navigation-entry__settings-quota { // Align title with progress and icon - &--not-unlimited::v-deep .app-navigation-entry__title { + &--not-unlimited::v-deep .app-navigation-entry__name { margin-top: -4px; } diff --git a/apps/files/src/legacy/navigationMapper.js b/apps/files/src/legacy/navigationMapper.js deleted file mode 100644 index 764a7cb6cd9..00000000000 --- a/apps/files/src/legacy/navigationMapper.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -import { loadState } from '@nextcloud/initial-state' -import logger from '../logger.js' - -/** - * Fetch and register the legacy files views - */ -export default function() { - const legacyViews = Object.values(loadState('files', 'navigation', {})) - - if (legacyViews.length > 0) { - logger.debug('Legacy files views detected. Processing...', legacyViews) - legacyViews.forEach(view => { - registerLegacyView(view) - if (view.sublist) { - view.sublist.forEach(subview => registerLegacyView({ ...subview, parent: view.id })) - } - }) - } -} - -const registerLegacyView = function({ id, name, order, icon, parent, classes = '', expanded, params }) { - OCP.Files.Navigation.register({ - id, - name, - order, - params, - parent, - expanded: expanded === true, - iconClass: icon ? `icon-${icon}` : 'nav-icon-' + id, - legacy: true, - sticky: classes.includes('pinned'), - }) -} diff --git a/apps/files/src/main.ts b/apps/files/src/main.ts index 2fbae13693a..d0fb3922229 100644 --- a/apps/files/src/main.ts +++ b/apps/files/src/main.ts @@ -15,14 +15,13 @@ import Vue from 'vue' import { createPinia, PiniaVuePlugin } from 'pinia' import FilesListView from './views/FilesList.vue' -import NavigationService from './services/Navigation' +import { NavigationService } from './services/Navigation' import NavigationView from './views/Navigation.vue' -import processLegacyFilesViews from './legacy/navigationMapper.js' import registerFavoritesView from './views/favorites' import registerRecentView from './views/recent' import registerFilesView from './views/files' import registerPreviewServiceWorker from './services/ServiceWorker.js' -import router from './router/router.js' +import router from './router/router' import RouterService from './services/RouterService' import SettingsModel from './models/Setting.js' import SettingsService from './services/Settings.js' @@ -79,7 +78,6 @@ const FilesList = new ListView({ FilesList.$mount('#app-content-vue') // Init legacy and new files views -processLegacyFilesViews() registerFavoritesView() registerFilesView() registerRecentView() diff --git a/apps/files/src/mixins/filesSorting.ts b/apps/files/src/mixins/filesSorting.ts index 2f79a3eb171..e766ea631f3 100644 --- a/apps/files/src/mixins/filesSorting.ts +++ b/apps/files/src/mixins/filesSorting.ts @@ -23,14 +23,14 @@ import Vue from 'vue' import { mapState } from 'pinia' import { useViewConfigStore } from '../store/viewConfig' -import type { Navigation } from '../services/Navigation' +import type { NavigationService, Navigation } from '../services/Navigation' export default Vue.extend({ computed: { ...mapState(useViewConfigStore, ['getConfig', 'setSortingBy', 'toggleSortingDirection']), currentView(): Navigation { - return this.$navigation.active + return (this.$navigation as NavigationService).active as Navigation }, /** diff --git a/apps/files/src/router/router.js b/apps/files/src/router/router.ts index 0d833cd6464..6ba8bec92fb 100644 --- a/apps/files/src/router/router.js +++ b/apps/files/src/router/router.ts @@ -19,10 +19,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -import Vue from 'vue' -import Router from 'vue-router' import { generateUrl } from '@nextcloud/router' import queryString from 'query-string' +import Router from 'vue-router' +import Vue from 'vue' Vue.use(Router) @@ -31,7 +31,7 @@ const router = new Router({ // if index.php is in the url AND we got this far, then it's working: // let's keep using index.php in the url - base: generateUrl('/apps/files', ''), + base: generateUrl('/apps/files'), linkActiveClass: 'active', routes: [ diff --git a/apps/files/src/services/Navigation.ts b/apps/files/src/services/Navigation.ts index 56d3ba0b97d..8f8212783ca 100644 --- a/apps/files/src/services/Navigation.ts +++ b/apps/files/src/services/Navigation.ts @@ -96,22 +96,9 @@ export interface Navigation { * haven't customized their sorting column */ defaultSortKey?: string - - /** - * This view is sticky a legacy view. - * Here until all the views are migrated to Vue. - * @deprecated It will be removed in a near future - */ - legacy?: boolean - - /** - * An icon class. - * @deprecated It will be removed in a near future - */ - iconClass?: string } -export default class { +export class NavigationService { private _views: Navigation[] = [] private _currentView: Navigation | null = null @@ -131,14 +118,6 @@ export default class { throw e } - if (view.legacy) { - logger.warn('Legacy view detected, please migrate to Vue') - } - - if (view.iconClass) { - view.legacy = true - } - this._views.push(view) } @@ -192,18 +171,12 @@ const isValidNavigation = function(view: Navigation): boolean { throw new Error('Navigation caption is required for top-level views and must be a string') } - /** - * Legacy handle their content and icon differently - * TODO: remove when support for legacy views is removed - */ - if (!view.legacy) { - if (!view.getContents || typeof view.getContents !== 'function') { - throw new Error('Navigation getContents is required and must be a function') - } + if (!view.getContents || typeof view.getContents !== 'function') { + throw new Error('Navigation getContents is required and must be a function') + } - if (!view.icon || typeof view.icon !== 'string' || !isSvg(view.icon)) { - throw new Error('Navigation icon is required and must be a valid svg string') - } + if (!view.icon || typeof view.icon !== 'string' || !isSvg(view.icon)) { + throw new Error('Navigation icon is required and must be a valid svg string') } if (!('order' in view) || typeof view.order !== 'number') { diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 1a7ca5769aa..ae93642f985 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -20,9 +20,7 @@ - --> <template> - <NcAppContent v-show="!currentView?.legacy" - :class="{'app-content--hidden': currentView?.legacy}" - data-cy-files-content> + <NcAppContent data-cy-files-content> <div class="files-list__header"> <!-- Current folder breadcrumbs --> <BreadCrumbs :path="dir" @reload="fetchContent" /> @@ -58,19 +56,25 @@ <!-- File list --> <FilesListVirtual v-else ref="filesListVirtual" + :current-folder="currentFolder" :current-view="currentView" :nodes="dirContents" /> </NcAppContent> </template> <script lang="ts"> -import { Folder, File, Node } from '@nextcloud/files' +import type { Route } from 'vue-router' +import type { Navigation, ContentsWithRoot } from '../services/Navigation.ts' +import type { UserConfig } from '../types.ts' + +import { Folder, Node } from '@nextcloud/files' import { join } from 'path' import { orderBy } from 'natural-orderby' import { translate } from '@nextcloud/l10n' import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' +import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' import Vue from 'vue' @@ -83,8 +87,6 @@ import BreadCrumbs from '../components/BreadCrumbs.vue' import FilesListVirtual from '../components/FilesListVirtual.vue' import filesSortingMixin from '../mixins/filesSorting.ts' import logger from '../logger.js' -import Navigation, { ContentsWithRoot } from '../services/Navigation.ts' -import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' export default Vue.extend({ name: 'FilesList', @@ -126,32 +128,27 @@ export default Vue.extend({ }, computed: { - userConfig() { + userConfig(): UserConfig { return this.userConfigStore.userConfig }, - /** @return {Navigation} */ - currentView() { - return this.$navigation.active - || this.$navigation.views.find(view => view.id === 'files') + currentView(): Navigation { + return (this.$navigation.active + || this.$navigation.views.find(view => view.id === 'files')) as Navigation }, /** * The current directory query. - * - * @return {string} */ - dir() { + dir(): string { // Remove any trailing slash but leave root slash return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1') }, /** * The current folder. - * - * @return {Folder|undefined} */ - currentFolder() { + currentFolder(): Folder|undefined { if (!this.currentView?.id) { return } @@ -165,10 +162,8 @@ export default Vue.extend({ /** * The current directory contents. - * - * @return {Node[]} */ - dirContents() { + dirContents(): Node[] { if (!this.currentView) { return [] } @@ -207,7 +202,7 @@ export default Vue.extend({ /** * The current directory is empty. */ - isEmptyDir() { + isEmptyDir(): boolean { return this.dirContents.length === 0 }, @@ -216,7 +211,7 @@ export default Vue.extend({ * But we already have a cached version of it * that is not empty. */ - isRefreshing() { + isRefreshing(): boolean { return this.currentFolder !== undefined && !this.isEmptyDir && this.loading @@ -225,7 +220,7 @@ export default Vue.extend({ /** * Route to the previous directory. */ - toPreviousDir() { + toPreviousDir(): Route { const dir = this.dir.split('/').slice(0, -1).join('/') || '/' return { ...this.$route, query: { dir } } }, @@ -257,10 +252,6 @@ export default Vue.extend({ methods: { async fetchContent() { - if (this.currentView?.legacy) { - return - } - this.loading = true const dir = this.dir const currentView = this.currentView @@ -272,8 +263,7 @@ export default Vue.extend({ } // Fetch the current dir contents - /** @type {Promise<ContentsWithRoot>} */ - this.promise = currentView.getContents(dir) + this.promise = currentView.getContents(dir) as Promise<ContentsWithRoot> try { const { folder, contents } = await this.promise logger.debug('Fetched contents', { dir, folder, contents }) @@ -333,12 +323,6 @@ export default Vue.extend({ overflow: hidden; flex-direction: column; max-height: 100%; - - // TODO: remove after all legacy views are migrated - // Hides the legacy app-content if shown view is not legacy - &:not(&--hidden)::v-deep + #app-content { - display: none; - } } $margin: 4px; diff --git a/apps/files/src/views/Navigation.cy.ts b/apps/files/src/views/Navigation.cy.ts index 8678465841a..a65016da297 100644 --- a/apps/files/src/views/Navigation.cy.ts +++ b/apps/files/src/views/Navigation.cy.ts @@ -2,9 +2,9 @@ import FolderSvg from '@mdi/svg/svg/folder.svg' import ShareSvg from '@mdi/svg/svg/share-variant.svg' import { createTestingPinia } from '@pinia/testing' -import NavigationService from '../services/Navigation' +import { NavigationService } from '../services/Navigation' import NavigationView from './Navigation.vue' -import router from '../router/router.js' +import router from '../router/router' import { useViewConfigStore } from '../store/viewConfig' describe('Navigation renders', () => { diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue index 81ceac80a7f..068db016ddb 100644 --- a/apps/files/src/views/Navigation.vue +++ b/apps/files/src/views/Navigation.vue @@ -72,7 +72,7 @@ </NcAppNavigation> </template> -<script> +<script lang="ts"> import { emit, subscribe } from '@nextcloud/event-bus' import { translate } from '@nextcloud/l10n' import Cog from 'vue-material-design-icons/Cog.vue' @@ -83,7 +83,7 @@ import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js import { setPageHeading } from '../../../../core/src/OCP/accessibility.js' import { useViewConfigStore } from '../store/viewConfig.ts' import logger from '../logger.js' -import Navigation from '../services/Navigation.ts' +import type { NavigationService, Navigation } from '../services/Navigation.ts' import NavigationQuota from '../components/NavigationQuota.vue' import SettingsModal from './Settings.vue' @@ -102,7 +102,7 @@ export default { props: { // eslint-disable-next-line vue/prop-name-casing Navigation: { - type: Navigation, + type: Object as Navigation, required: true, }, }, @@ -125,18 +125,15 @@ export default { return this.$route?.params?.view || 'files' }, - /** @return {Navigation} */ - currentView() { + currentView(): Navigation { return this.views.find(view => view.id === this.currentViewId) }, - /** @return {Navigation[]} */ - views() { + views(): Navigation[] { return this.Navigation.views }, - /** @return {Navigation[]} */ - parentViews() { + parentViews(): Navigation[] { return this.views // filter child views .filter(view => !view.parent) @@ -146,8 +143,7 @@ export default { }) }, - /** @return {Navigation[]} */ - childViews() { + childViews(): Navigation[] { return this.views // filter parent views .filter(view => !!view.parent) @@ -165,13 +161,6 @@ export default { watch: { currentView(view, oldView) { - // If undefined, it means we're initializing the view - // This is handled by the legacy-view:initialized event - // TODO: remove when legacy views are dropped - if (view?.id === oldView?.id) { - return - } - this.Navigation.setActive(view) logger.debug('Navigation changed', { id: view.id, view }) @@ -184,70 +173,22 @@ export default { logger.debug('Navigation mounted. Showing requested view', { view: this.currentView }) this.showView(this.currentView) } - - subscribe('files:legacy-navigation:changed', this.onLegacyNavigationChanged) - - // TODO: remove this once the legacy navigation is gone - subscribe('files:legacy-view:initialized', () => { - logger.debug('Legacy view initialized', { ...this.currentView }) - this.showView(this.currentView) - }) }, methods: { - /** - * @param {Navigation} view the new active view - * @param {Navigation} oldView the old active view - */ - showView(view, oldView) { + showView(view: Navigation) { // Closing any opened sidebar window?.OCA?.Files?.Sidebar?.close?.() - - if (view?.legacy) { - const newAppContent = document.querySelector('#app-content #app-content-' + this.currentView.id + '.viewcontainer') - document.querySelectorAll('#app-content .viewcontainer').forEach(el => { - el.classList.add('hidden') - }) - newAppContent.classList.remove('hidden') - - // Triggering legacy navigation events - const { dir = '/' } = OC.Util.History.parseUrlQuery() - const params = { itemId: view.id, dir } - - logger.debug('Triggering legacy navigation event', params) - window.jQuery(newAppContent).trigger(new window.jQuery.Event('show', params)) - window.jQuery(newAppContent).trigger(new window.jQuery.Event('urlChanged', params)) - } - this.Navigation.setActive(view) setPageHeading(view.name) emit('files:navigation:changed', view) }, /** - * Coming from the legacy files app. - * TODO: remove when all views are migrated. - * - * @param {Navigation} view the new active view - */ - onLegacyNavigationChanged({ id } = { id: 'files' }) { - const view = this.Navigation.views.find(view => view.id === id) - if (view && view.legacy && view.id !== this.currentView.id) { - // Force update the current route as the request comes - // from the legacy files app router - this.$router.replace({ ...this.$route, params: { view: view.id } }) - this.Navigation.setActive(view) - this.showView(view) - } - }, - - /** * Expand/collapse a a view with children and permanently * save this setting in the server. - * - * @param {Navigation} view the view to toggle */ - onToggleExpand(view) { + onToggleExpand(view: Navigation) { // Invert state const isExpanded = this.isExpanded(view) // Update the view expanded state, might not be necessary @@ -258,10 +199,8 @@ export default { /** * Check if a view is expanded by user config * or fallback to the default value. - * - * @param {Navigation} view the view to check */ - isExpanded(view) { + isExpanded(view: Navigation): boolean { return typeof this.viewConfigStore.getConfig(view.id)?.expanded === 'boolean' ? this.viewConfigStore.getConfig(view.id).expanded === true : view.expanded === true @@ -269,10 +208,8 @@ export default { /** * Generate the route to a view - * - * @param {Navigation} view the view to toggle */ - generateToNavigation(view) { + generateToNavigation(view: Navigation) { if (view.params) { const { dir, fileid } = view.params return { name: 'filelist', params: view.params, query: { dir, fileid } } diff --git a/apps/files/src/views/Sidebar.vue b/apps/files/src/views/Sidebar.vue index 12581222f8e..b17aa93e63e 100644 --- a/apps/files/src/views/Sidebar.vue +++ b/apps/files/src/views/Sidebar.vue @@ -396,13 +396,6 @@ export default { ${state ? '</d:set>' : '</d:remove>'} </d:propertyupdate>`, }) - - // TODO: Obliterate as soon as possible and use events with new files app - // Terrible fallback for legacy files: toggle filelist as well - if (OCA.Files && OCA.Files.App && OCA.Files.App.fileList && OCA.Files.App.fileList.fileActions) { - OCA.Files.App.fileList.fileActions.triggerAction('Favorite', OCA.Files.App.fileList.getModelForFile(this.fileInfo.name), OCA.Files.App.fileList) - } - } catch (error) { OC.Notification.showTemporary(t('files', 'Unable to change the favourite state of the file')) console.error('Unable to change favourite state', error) diff --git a/apps/files/src/views/favorites.spec.ts b/apps/files/src/views/favorites.spec.ts index d39def283a9..b1340187244 100644 --- a/apps/files/src/views/favorites.spec.ts +++ b/apps/files/src/views/favorites.spec.ts @@ -27,7 +27,7 @@ import * as eventBus from '@nextcloud/event-bus' import { action } from '../actions/favoriteAction' import * as favoritesService from '../services/Favorites' -import NavigationService from '../services/Navigation' +import { NavigationService } from '../services/Navigation' import registerFavoritesView from './favorites' jest.mock('webdav/dist/node/request.js', () => ({ diff --git a/apps/files/src/views/favorites.ts b/apps/files/src/views/favorites.ts index 20baa2c582d..7485340a2fe 100644 --- a/apps/files/src/views/favorites.ts +++ b/apps/files/src/views/favorites.ts @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -import type { Navigation } from '../services/Navigation' -import type NavigationService from '../services/Navigation' +import type { Navigation, NavigationService } from '../services/Navigation' import { getLanguage, translate as t } from '@nextcloud/l10n' import FolderSvg from '@mdi/svg/svg/folder.svg?raw' import StarSvg from '@mdi/svg/svg/star.svg?raw' diff --git a/apps/files/src/views/files.ts b/apps/files/src/views/files.ts index 1a174f54e42..baafc8572c2 100644 --- a/apps/files/src/views/files.ts +++ b/apps/files/src/views/files.ts @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -import type NavigationService from '../services/Navigation' -import type { Navigation } from '../services/Navigation' +import type { NavigationService, Navigation } from '../services/Navigation' import { translate as t } from '@nextcloud/l10n' import FolderSvg from '@mdi/svg/svg/folder.svg?raw' diff --git a/apps/files/src/views/recent.ts b/apps/files/src/views/recent.ts index 5a0dbd91860..3e0c51184e4 100644 --- a/apps/files/src/views/recent.ts +++ b/apps/files/src/views/recent.ts @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -import type NavigationService from '../services/Navigation' -import type { Navigation } from '../services/Navigation' +import type { NavigationService, Navigation } from '../services/Navigation' import { translate as t } from '@nextcloud/l10n' import HistorySvg from '@mdi/svg/svg/history.svg?raw' diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index c6f145bfe40..c974a37aa5b 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -1,41 +1,9 @@ -<?php /** @var \OCP\IL10N $l */ ?> -<?php $_['appNavigation']->printPage(); ?> +<!-- File navigation --> +<div id="app-navigation-files" role="navigation"></div> -<!-- New files vue container --> +<!-- File list vue container --> <div id="app-content-vue" class="hidden"></div> -<div id="app-content" tabindex="0"> - - <input type="checkbox" class="hidden-visually" id="showgridview" - aria-label="<?php p($l->t('Toggle grid view'))?>" - <?php if ($_['showgridview']) { ?>checked="checked" <?php } ?>/> - <label id="view-toggle" for="showgridview" tabindex="0" class="button <?php p($_['showgridview'] ? 'icon-toggle-filelist' : 'icon-toggle-pictures') ?>" - title="<?php p($_['showgridview'] ? $l->t('Show list view') : $l->t('Show grid view'))?>"></label> - - - <!-- Legacy views --> - <?php foreach ($_['appContents'] as $content) { ?> - <div id="app-content-<?php p($content['id']) ?>" class="hidden viewcontainer"> - <?php print_unescaped($content['content']) ?> - </div> - <?php } ?> - <div id="searchresults" class="hidden"></div> -</div><!-- closing app-content --> - <!-- config hints for javascript --> <input type="hidden" name="filesApp" id="filesApp" value="1" /> -<input type="hidden" name="usedSpacePercent" id="usedSpacePercent" value="<?php p($_['usedSpacePercent']); ?>" /> -<input type="hidden" name="owner" id="owner" value="<?php p($_['owner']); ?>" /> -<input type="hidden" name="ownerDisplayName" id="ownerDisplayName" value="<?php p($_['ownerDisplayName']); ?>" /> <input type="hidden" name="fileNotFound" id="fileNotFound" value="<?php p($_['fileNotFound']); ?>" /> -<?php if (!$_['isPublic']) :?> -<input type="hidden" name="allowShareWithLink" id="allowShareWithLink" value="<?php p($_['allowShareWithLink']) ?>" /> -<input type="hidden" name="defaultFileSorting" id="defaultFileSorting" value="<?php p($_['defaultFileSorting']) ?>" /> -<input type="hidden" name="defaultFileSortingDirection" id="defaultFileSortingDirection" value="<?php p($_['defaultFileSortingDirection']) ?>" /> -<input type="hidden" name="showHiddenFiles" id="showHiddenFiles" value="<?php p($_['showHiddenFiles']); ?>" /> -<input type="hidden" name="cropImagePreviews" id="cropImagePreviews" value="<?php p($_['cropImagePreviews']); ?>" /> -<?php endif; - -foreach ($_['hiddenFields'] as $name => $value) {?> -<input type="hidden" name="<?php p($name) ?>" id="<?php p($name) ?>" value="<?php p($value) ?>" /> -<?php } diff --git a/apps/files_external/src/main.ts b/apps/files_external/src/main.ts index e72cb8673d0..250ad51e38f 100644 --- a/apps/files_external/src/main.ts +++ b/apps/files_external/src/main.ts @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -import type NavigationService from '../../files/src/services/Navigation' -import type { Navigation } from '../../files/src/services/Navigation' +import type { NavigationService, Navigation } from '../../files/src/services/Navigation' import { translate as t } from '@nextcloud/l10n' import { loadState } from '@nextcloud/initial-state' diff --git a/apps/files_sharing/src/views/shares.spec.ts b/apps/files_sharing/src/views/shares.spec.ts index e5c7e6853c6..424d3680411 100644 --- a/apps/files_sharing/src/views/shares.spec.ts +++ b/apps/files_sharing/src/views/shares.spec.ts @@ -25,7 +25,7 @@ import axios from '@nextcloud/axios' import { type Navigation } from '../../../files/src/services/Navigation' import { type OCSResponse } from '../services/SharingService' -import NavigationService from '../../../files/src/services/Navigation' +import { NavigationService } from '../../../files/src/services/Navigation' import registerSharingViews from './shares' import '../main' diff --git a/apps/files_sharing/src/views/shares.ts b/apps/files_sharing/src/views/shares.ts index ff37983813e..74be9e7a503 100644 --- a/apps/files_sharing/src/views/shares.ts +++ b/apps/files_sharing/src/views/shares.ts @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -import type NavigationService from '../../../files/src/services/Navigation' -import type { Navigation } from '../../../files/src/services/Navigation' +import type { NavigationService, Navigation } from '../../../files/src/services/Navigation' import { translate as t } from '@nextcloud/l10n' import AccountClockSvg from '@mdi/svg/svg/account-clock.svg?raw' diff --git a/apps/files_trashbin/src/main.ts b/apps/files_trashbin/src/main.ts index eb235f34c8d..cf5a95bb1d8 100644 --- a/apps/files_trashbin/src/main.ts +++ b/apps/files_trashbin/src/main.ts @@ -19,8 +19,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -import type NavigationService from '../../files/src/services/Navigation' -import type { Navigation } from '../../files/src/services/Navigation' +import type { NavigationService, Navigation } from '../../files/src/services/Navigation' import { translate as t, translate } from '@nextcloud/l10n' import DeleteSvg from '@mdi/svg/svg/delete.svg?raw' diff --git a/core/src/systemtags/systemtagmodel.js b/core/src/systemtags/systemtagmodel.js index 72f2d6f0915..261ba02c905 100644 --- a/core/src/systemtags/systemtagmodel.js +++ b/core/src/systemtags/systemtagmodel.js @@ -24,52 +24,53 @@ */ (function(OC) { + if (OC?.Files?.Client) { + _.extend(OC.Files.Client, { + PROPERTY_FILEID: '{' + OC.Files.Client.NS_OWNCLOUD + '}id', + PROPERTY_CAN_ASSIGN: '{' + OC.Files.Client.NS_OWNCLOUD + '}can-assign', + PROPERTY_DISPLAYNAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}display-name', + PROPERTY_USERVISIBLE: '{' + OC.Files.Client.NS_OWNCLOUD + '}user-visible', + PROPERTY_USERASSIGNABLE: '{' + OC.Files.Client.NS_OWNCLOUD + '}user-assignable', + }) - _.extend(OC.Files.Client, { - PROPERTY_FILEID: '{' + OC.Files.Client.NS_OWNCLOUD + '}id', - PROPERTY_CAN_ASSIGN: '{' + OC.Files.Client.NS_OWNCLOUD + '}can-assign', - PROPERTY_DISPLAYNAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}display-name', - PROPERTY_USERVISIBLE: '{' + OC.Files.Client.NS_OWNCLOUD + '}user-visible', - PROPERTY_USERASSIGNABLE: '{' + OC.Files.Client.NS_OWNCLOUD + '}user-assignable', - }) - - /** - * @class OCA.SystemTags.SystemTagsCollection - * @classdesc - * - * System tag - * - */ - const SystemTagModel = OC.Backbone.Model.extend( - /** @lends OCA.SystemTags.SystemTagModel.prototype */ { - sync: OC.Backbone.davSync, + /** + * @class OCA.SystemTags.SystemTagsCollection + * @classdesc + * + * System tag + * + */ + const SystemTagModel = OC.Backbone.Model.extend( + /** @lends OCA.SystemTags.SystemTagModel.prototype */ { + sync: OC.Backbone.davSync, - defaults: { - userVisible: true, - userAssignable: true, - canAssign: true, - }, + defaults: { + userVisible: true, + userAssignable: true, + canAssign: true, + }, - davProperties: { - id: OC.Files.Client.PROPERTY_FILEID, - name: OC.Files.Client.PROPERTY_DISPLAYNAME, - userVisible: OC.Files.Client.PROPERTY_USERVISIBLE, - userAssignable: OC.Files.Client.PROPERTY_USERASSIGNABLE, - // read-only, effective permissions computed by the server, - canAssign: OC.Files.Client.PROPERTY_CAN_ASSIGN, - }, + davProperties: { + id: OC.Files.Client.PROPERTY_FILEID, + name: OC.Files.Client.PROPERTY_DISPLAYNAME, + userVisible: OC.Files.Client.PROPERTY_USERVISIBLE, + userAssignable: OC.Files.Client.PROPERTY_USERASSIGNABLE, + // read-only, effective permissions computed by the server, + canAssign: OC.Files.Client.PROPERTY_CAN_ASSIGN, + }, - parse(data) { - return { - id: data.id, - name: data.name, - userVisible: data.userVisible === true || data.userVisible === 'true', - userAssignable: data.userAssignable === true || data.userAssignable === 'true', - canAssign: data.canAssign === true || data.canAssign === 'true', - } - }, - }) + parse(data) { + return { + id: data.id, + name: data.name, + userVisible: data.userVisible === true || data.userVisible === 'true', + userAssignable: data.userAssignable === true || data.userAssignable === 'true', + canAssign: data.canAssign === true || data.canAssign === 'true', + } + }, + }) - OC.SystemTags = OC.SystemTags || {} - OC.SystemTags.SystemTagModel = SystemTagModel + OC.SystemTags = OC.SystemTags || {} + OC.SystemTags.SystemTagModel = SystemTagModel + } })(OC) diff --git a/package-lock.json b/package-lock.json index 7740fbc67ed..6cc2c9e17c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@nextcloud/capabilities": "^1.0.4", "@nextcloud/dialogs": "^4.1.0", "@nextcloud/event-bus": "^3.1.0", - "@nextcloud/files": "^3.0.0-beta.13", + "@nextcloud/files": "^3.0.0-beta.14", "@nextcloud/initial-state": "^2.0.0", "@nextcloud/l10n": "^2.1.0", "@nextcloud/logger": "^2.5.0", diff --git a/package.json b/package.json index 626ea8da367..f0b7d870e1e 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@nextcloud/capabilities": "^1.0.4", "@nextcloud/dialogs": "^4.1.0", "@nextcloud/event-bus": "^3.1.0", - "@nextcloud/files": "^3.0.0-beta.13", + "@nextcloud/files": "^3.0.0-beta.14", "@nextcloud/initial-state": "^2.0.0", "@nextcloud/l10n": "^2.1.0", "@nextcloud/logger": "^2.5.0", |