Skip to content

Commit c50c5a9

Browse files
authored
Merge pull request #57277 from nextcloud/refactor/files-sidebar-nodeapi
refactor!: migrate files sidebar to Node API
2 parents eb03697 + 98a4b95 commit c50c5a9

File tree

565 files changed

+3934
-6102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

565 files changed

+3934
-6102
lines changed

apps/comments/src/actions/inlineUnreadCommentsAction.spec.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,12 @@ describe('Inline unread comments action enabled tests', () => {
180180
describe('Inline unread comments action execute tests', () => {
181181
test('Action opens sidebar', async () => {
182182
const openMock = vi.fn()
183-
const setActiveTabMock = vi.fn()
184183
window.OCA = {
185184
Files: {
186185
// @ts-expect-error Mocking for testing
187-
Sidebar: {
186+
_sidebar: () => ({
188187
open: openMock,
189-
setActiveTab: setActiveTabMock,
190-
},
188+
}),
191189
},
192190
}
193191

@@ -211,22 +209,19 @@ describe('Inline unread comments action execute tests', () => {
211209
})
212210

213211
expect(result).toBe(null)
214-
expect(setActiveTabMock).toBeCalledWith('comments')
215-
expect(openMock).toBeCalledWith('/foobar.txt')
212+
expect(openMock).toBeCalledWith(file, 'comments')
216213
})
217214

218215
test('Action handles sidebar open failure', async () => {
219216
const openMock = vi.fn(() => {
220217
throw new Error('Mock error')
221218
})
222-
const setActiveTabMock = vi.fn()
223219
window.OCA = {
224220
Files: {
225221
// @ts-expect-error Mocking for testing
226-
Sidebar: {
222+
_sidebar: () => ({
227223
open: openMock,
228-
setActiveTab: setActiveTabMock,
229-
},
224+
}),
230225
},
231226
}
232227
vi.spyOn(logger, 'error').mockImplementation(() => vi.fn())
@@ -251,8 +246,7 @@ describe('Inline unread comments action execute tests', () => {
251246
})
252247

253248
expect(result).toBe(false)
254-
expect(setActiveTabMock).toBeCalledWith('comments')
255-
expect(openMock).toBeCalledWith('/foobar.txt')
249+
expect(openMock).toBeCalledWith(file, 'comments')
256250
expect(logger.error).toBeCalledTimes(1)
257251
})
258252
})

apps/comments/src/actions/inlineUnreadCommentsAction.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
/**
1+
/*!
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5+
56
import CommentProcessingSvg from '@mdi/svg/svg/comment-processing.svg?raw'
6-
import { FileAction } from '@nextcloud/files'
7+
import { FileAction, getSidebar } from '@nextcloud/files'
78
import { n, t } from '@nextcloud/l10n'
89
import logger from '../logger.js'
910

@@ -34,8 +35,8 @@ export const action = new FileAction({
3435
}
3536

3637
try {
37-
window.OCA.Files.Sidebar.setActiveTab('comments')
38-
await window.OCA.Files.Sidebar.open(nodes[0].path)
38+
const sidebar = getSidebar()
39+
sidebar.open(nodes[0], 'comments')
3940
return null
4041
} catch (error) {
4142
logger.error('Error while opening sidebar', { error })

apps/comments/src/comments-tab.js

Lines changed: 0 additions & 59 deletions
This file was deleted.

apps/comments/src/files-sidebar.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import MessageReplyText from '@mdi/svg/svg/message-reply-text.svg?raw'
7+
import { getCSPNonce } from '@nextcloud/auth'
8+
import { registerSidebarTab } from '@nextcloud/files'
9+
import { loadState } from '@nextcloud/initial-state'
10+
import { t } from '@nextcloud/l10n'
11+
import wrap from '@vue/web-component-wrapper'
12+
import { createPinia, PiniaVuePlugin } from 'pinia'
13+
import Vue from 'vue'
14+
import FilesSidebarTab from './views/FilesSidebarTab.vue'
15+
import { registerCommentsPlugins } from './comments-activity-tab.ts'
16+
17+
__webpack_nonce__ = getCSPNonce()
18+
19+
const tagName = 'comments_files-sidebar-tab'
20+
21+
if (loadState('comments', 'activityEnabled', false) && OCA?.Activity?.registerSidebarAction !== undefined) {
22+
// Do not mount own tab but mount into activity
23+
window.addEventListener('DOMContentLoaded', function() {
24+
registerCommentsPlugins()
25+
})
26+
} else {
27+
registerSidebarTab({
28+
id: 'comments',
29+
displayName: t('comments', 'Comments'),
30+
iconSvgInline: MessageReplyText,
31+
order: 50,
32+
tagName,
33+
enabled() {
34+
if (!window.customElements.get(tagName)) {
35+
setupSidebarTab()
36+
}
37+
return true
38+
},
39+
})
40+
}
41+
42+
/**
43+
* Setup the sidebar tab as a web component
44+
*/
45+
function setupSidebarTab() {
46+
Vue.use(PiniaVuePlugin)
47+
Vue.mixin({ pinia: createPinia() })
48+
const webComponent = wrap(Vue, FilesSidebarTab)
49+
// In Vue 2, wrap doesn't support disabling shadow. Disable with a hack
50+
Object.defineProperty(webComponent.prototype, 'attachShadow', {
51+
value() { return this },
52+
})
53+
Object.defineProperty(webComponent.prototype, 'shadowRoot', {
54+
get() { return this },
55+
})
56+
window.customElements.define(tagName, webComponent)
57+
}

apps/comments/src/mixins/CommentView.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { getCurrentUser } from '@nextcloud/auth'
2-
/**
1+
/*!
32
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
43
* SPDX-License-Identifier: AGPL-3.0-or-later
54
*/
5+
6+
import { getCurrentUser } from '@nextcloud/auth'
67
import axios from '@nextcloud/axios'
78
import { loadState } from '@nextcloud/initial-state'
89
import { generateOcsUrl } from '@nextcloud/router'
@@ -32,7 +33,7 @@ export default defineComponent({
3233
},
3334
methods: {
3435
/**
35-
* Autocomplete @mentions
36+
* Autocomplete `@mentions`
3637
*
3738
* @param search the query
3839
* @param callback the callback to process the results with
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import type { IFolder, INode, IView } from '@nextcloud/files'
8+
9+
import { computed } from 'vue'
10+
import Comments from './Comments.vue'
11+
12+
const props = defineProps<{
13+
node?: INode
14+
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface
15+
folder?: IFolder
16+
// eslint-disable-next-line vue/no-unused-properties -- Required on the web component interface
17+
view?: IView
18+
}>()
19+
20+
defineExpose({ setActive })
21+
22+
const resourceId = computed(() => props.node?.fileid)
23+
24+
/**
25+
* Set this tab as active
26+
*
27+
* @param active - The active state
28+
*/
29+
function setActive(active: boolean) {
30+
return active
31+
}
32+
</script>
33+
34+
<template>
35+
<Comments
36+
v-if="resourceId !== undefined"
37+
:key="resourceId"
38+
:resource-id="resourceId"
39+
resource-type="files" />
40+
</template>

apps/files/src/FilesApp.vue

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,19 @@
66
<NcContent app-name="files">
77
<FilesNavigation v-if="!isPublic" />
88
<FilesList :is-public="isPublic" />
9+
<FilesSidebar v-if="!isPublic" />
910
</NcContent>
1011
</template>
1112

12-
<script lang="ts">
13+
<script setup lang="ts">
1314
import { isPublicShare } from '@nextcloud/sharing/public'
14-
import { defineComponent } from 'vue'
1515
import NcContent from '@nextcloud/vue/components/NcContent'
1616
import FilesList from './views/FilesList.vue'
1717
import FilesNavigation from './views/FilesNavigation.vue'
18+
import FilesSidebar from './views/FilesSidebar.vue'
1819
import { useHotKeys } from './composables/useHotKeys.ts'
1920
20-
export default defineComponent({
21-
name: 'FilesApp',
21+
useHotKeys()
2222
23-
components: {
24-
NcContent,
25-
FilesList,
26-
FilesNavigation,
27-
},
28-
29-
setup() {
30-
// Register global hotkeys
31-
useHotKeys()
32-
33-
const isPublic = isPublicShare()
34-
35-
return {
36-
isPublic,
37-
}
38-
},
39-
})
23+
const isPublic = isPublicShare()
4024
</script>

apps/files/src/actions/favoriteAction.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ describe('Favorite action execute tests', () => {
217217

218218
// Check node change propagation
219219
expect(file.attributes.favorite).toBe(1)
220-
expect(eventBus.emit).toBeCalledTimes(1)
220+
expect(eventBus.emit).toHaveBeenCalled()
221221
expect(eventBus.emit).toBeCalledWith('files:favorites:added', file)
222222
})
223223

@@ -251,7 +251,7 @@ describe('Favorite action execute tests', () => {
251251

252252
// Check node change propagation
253253
expect(file.attributes.favorite).toBe(0)
254-
expect(eventBus.emit).toBeCalledTimes(1)
254+
expect(eventBus.emit).toHaveBeenCalled()
255255
expect(eventBus.emit).toBeCalledWith('files:favorites:removed', file)
256256
})
257257

@@ -285,9 +285,9 @@ describe('Favorite action execute tests', () => {
285285

286286
// Check node change propagation
287287
expect(file.attributes.favorite).toBe(0)
288-
expect(eventBus.emit).toBeCalledTimes(2)
289-
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file)
290-
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:favorites:removed', file)
288+
expect(eventBus.emit).toHaveBeenCalled()
289+
expect(eventBus.emit).toHaveBeenCalledWith('files:node:deleted', file)
290+
expect(eventBus.emit).toHaveBeenCalledWith('files:favorites:removed', file)
291291
})
292292

293293
test('Favorite does NOT triggers node removal if favorite view but NOT root dir', async () => {
@@ -320,7 +320,7 @@ describe('Favorite action execute tests', () => {
320320

321321
// Check node change propagation
322322
expect(file.attributes.favorite).toBe(0)
323-
expect(eventBus.emit).toBeCalledTimes(1)
323+
expect(eventBus.emit).toHaveBeenCalled()
324324
expect(eventBus.emit).toBeCalledWith('files:favorites:removed', file)
325325
})
326326

apps/files/src/actions/favoriteAction.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
/**
1+
/*
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5-
import type { Node, View } from '@nextcloud/files'
5+
6+
import type { INode, IView } from '@nextcloud/files'
67

78
import StarOutlineSvg from '@mdi/svg/svg/star-outline.svg?raw'
89
import StarSvg from '@mdi/svg/svg/star.svg?raw'
@@ -26,17 +27,18 @@ const queue = new PQueue({ concurrency: 5 })
2627
*
2728
* @param nodes - The nodes to check
2829
*/
29-
function shouldFavorite(nodes: Node[]): boolean {
30+
function shouldFavorite(nodes: INode[]): boolean {
3031
return nodes.some((node) => node.attributes.favorite !== 1)
3132
}
3233

3334
/**
35+
* Favorite or unfavorite a node
3436
*
35-
* @param node
36-
* @param view
37-
* @param willFavorite
37+
* @param node - The node to favorite/unfavorite
38+
* @param view - The current view
39+
* @param willFavorite - Whether to favorite or unfavorite the node
3840
*/
39-
export async function favoriteNode(node: Node, view: View, willFavorite: boolean): Promise<boolean> {
41+
export async function favoriteNode(node: INode, view: IView, willFavorite: boolean): Promise<boolean> {
4042
try {
4143
// TODO: migrate to webdav tags plugin
4244
const url = generateUrl('/apps/files/api/v1/files') + encodePath(node.path)
@@ -55,6 +57,7 @@ export async function favoriteNode(node: Node, view: View, willFavorite: boolean
5557

5658
// Update the node webdav attribute
5759
Vue.set(node.attributes, 'favorite', willFavorite ? 1 : 0)
60+
emit('files:node:updated', node)
5861

5962
// Dispatch event to whoever is interested
6063
if (willFavorite) {

0 commit comments

Comments
 (0)