summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2023-08-09 14:59:35 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-08-17 18:56:37 +0200
commit410f58e43e8db767eaf0b272ab6ffde1841cd6a2 (patch)
tree5f0d2812ce7cce75b6b72c3652c81f7ae3c26bfd
parent998b3a2581fb873b03bcf4dc02eafb19390b3cd6 (diff)
downloadnextcloud-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>
-rw-r--r--apps/files/lib/Controller/ViewController.php59
-rw-r--r--apps/files/lib/Event/LoadAdditionalScriptsEvent.php13
-rw-r--r--apps/files/src/components/FilesListHeader.vue205
-rw-r--r--apps/files/src/components/FilesListTableFooter.vue (renamed from apps/files/src/components/FilesListFooter.vue)4
-rw-r--r--apps/files/src/components/FilesListTableHeader.vue213
-rw-r--r--apps/files/src/components/FilesListTableHeaderActions.vue (renamed from apps/files/src/components/FilesListHeaderActions.vue)2
-rw-r--r--apps/files/src/components/FilesListTableHeaderButton.vue (renamed from apps/files/src/components/FilesListHeaderButton.vue)2
-rw-r--r--apps/files/src/components/FilesListVirtual.vue123
-rw-r--r--apps/files/src/components/NavigationQuota.vue2
-rw-r--r--apps/files/src/legacy/navigationMapper.js55
-rw-r--r--apps/files/src/main.ts6
-rw-r--r--apps/files/src/mixins/filesSorting.ts4
-rw-r--r--apps/files/src/router/router.ts (renamed from apps/files/src/router/router.js)6
-rw-r--r--apps/files/src/services/Navigation.ts39
-rw-r--r--apps/files/src/views/FilesList.vue54
-rw-r--r--apps/files/src/views/Navigation.cy.ts4
-rw-r--r--apps/files/src/views/Navigation.vue85
-rw-r--r--apps/files/src/views/Sidebar.vue7
-rw-r--r--apps/files/src/views/favorites.spec.ts2
-rw-r--r--apps/files/src/views/favorites.ts3
-rw-r--r--apps/files/src/views/files.ts3
-rw-r--r--apps/files/src/views/recent.ts3
-rw-r--r--apps/files/templates/index.php38
-rw-r--r--apps/files_external/src/main.ts3
-rw-r--r--apps/files_sharing/src/views/shares.spec.ts2
-rw-r--r--apps/files_sharing/src/views/shares.ts3
-rw-r--r--apps/files_trashbin/src/main.ts3
-rw-r--r--core/src/systemtags/systemtagmodel.js87
-rw-r--r--package-lock.json2
-rw-r--r--package.json2
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",