Skip to content

Commit 95d0805

Browse files
committed
refactor: adjust for Nextcloud 33 new files sidebar API
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 7fc285d commit 95d0805

9 files changed

Lines changed: 282 additions & 163 deletions

File tree

cypress/e2e/sidebar.cy.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { createFolder, getFileListRow, navigateToFolder, moveFile, renameFile } from './filesUtils'
7-
import { addComment, addTag, createPublicShare, toggleFavorite, showActivityTab, randHash } from './sidebarUtils'
6+
import { createFolder, getFileListRow, moveFile, navigateToFolder, renameFile } from './filesUtils.ts'
7+
import { addComment, addTag, createPublicShare, randHash, showActivityTab, toggleFavorite } from './sidebarUtils.ts'
88

99
describe('Check activity listing in the sidebar', { testIsolation: true }, () => {
1010
beforeEach(function() {
@@ -77,5 +77,4 @@ describe('Check activity listing in the sidebar', { testIsolation: true }, () =>
7777
showActivityTab('welcome.txt')
7878
cy.get('.comments-activity').first().should('contains.text', 'A comment')
7979
})
80-
8180
})

package-lock.json

Lines changed: 189 additions & 93 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"devDependencies": {
6262
"@nextcloud/browserslist-config": "^3.1.2",
6363
"@nextcloud/cypress": "^1.0.0-beta.15",
64+
"@nextcloud/files": "^4.0.0-beta.8",
6465
"@nextcloud/stylelint-config": "^3.1.1",
6566
"@nextcloud/vite-config": "^2.5.2",
6667
"@testing-library/cypress": "^10.1.0",

src/components/ActivitySidebarPlugin.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88
</template>
99

1010
<script setup lang="ts">
11+
import type { INode } from '@nextcloud/files'
1112
import type { IActivitySidebarAction } from '../models/ActivityAPI.ts'
1213
1314
import { getCurrentInstance, onBeforeUnmount, onMounted, ref } from 'vue'
1415
1516
const props = defineProps<{
1617
/** The sidebar plugin */
1718
plugin: IActivitySidebarAction
18-
fileInfo: object | null
19+
/**
20+
* The current node (file or folder)
21+
*/
22+
node: INode
1923
}>()
2024
2125
const emit = defineEmits<{
@@ -25,8 +29,8 @@ const emit = defineEmits<{
2529
const attachTarget = ref<HTMLDivElement>()
2630
2731
onMounted(() => props.plugin.mount(attachTarget.value as HTMLDivElement, {
28-
context: getCurrentInstance()?.proxy,
29-
fileInfo: props.fileInfo,
32+
node: props.node,
33+
context: getCurrentInstance()?.proxy ?? undefined,
3034
reload: () => emit('reload-activities'),
3135
}))
3236
onBeforeUnmount(() => props.plugin.unmount())

src/components/activities/GenericActivity.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
<template>
77
<li class="activity-entry">
88
<NcAvatar
9-
:class="[applyMonochromeIconColor, 'activity-entry__icon', 'activity-icon', 'avatardiv--unknown']"
9+
class="activity-entry__icon activity-icon avatardiv--unknown"
10+
:class="[applyMonochromeIconColor]"
1011
:disable-menu="true"
1112
:disable-tooltip="true"
1213
:url="activity.icon"

src/models/ActivityAPI.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,29 @@
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5+
6+
import type { INode } from '@nextcloud/files'
7+
import type { ComponentPublicInstance } from 'vue'
58
import type ActivityModel from './ActivityModel.js'
69

710
interface MountOptions {
811
/**
912
* Trigger reloading the activities
1013
*/
1114
reload: () => void
12-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13-
fileInfo: any
14-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
15-
context: any
15+
/**
16+
* The current file or folder context
17+
*/
18+
node: INode
19+
/**
20+
* The current Vue component instance context
21+
*/
22+
context?: ComponentPublicInstance
1623
}
1724

1825
export interface ActivityFactoryQueryOptions {
19-
/** File to show entries for */
20-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
21-
fileInfo: any
26+
/** File or folder to show entries for */
27+
node: INode
2228
/** Limit the number of entries */
2329
limit?: number
2430
/** Offset for the entries queried */
@@ -52,7 +58,7 @@ export interface IActivitySidebarEntry {
5258
/**
5359
* The action is called with the HTML element where is should be mounted
5460
*/
55-
mount: (element: HTMLElement, options: Omit<MountOptions, 'fileInfo'>) => void
61+
mount: (element: HTMLElement, options: Omit<MountOptions, 'node'>) => void
5662

5763
/**
5864
* Called just before the sidebar is destroyed to allow plugins to cleanup

src/sidebar.ts

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,23 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { App, Component, ComponentPublicInstance } from 'vue'
7-
import type { ActivityTabType } from './views/ActivityTab.vue'
8-
96
import LightningBolt from '@mdi/svg/svg/lightning-bolt.svg?raw'
10-
import { translate as t } from '@nextcloud/l10n'
11-
import { createApp } from 'vue'
7+
import { registerSidebarTab } from '@nextcloud/files'
8+
import { t } from '@nextcloud/l10n'
9+
import { defineAsyncComponent, defineCustomElement } from 'vue'
1210

13-
// Init Activity tab component
14-
let LazyActivityTab: Component | null = null
15-
let activityTabApp: App<Element> | null = null
16-
let activityTabInstance: ComponentPublicInstance<ActivityTabType> | null = null
11+
window.customElements.define(
12+
'activity-files-sidebar-tab',
13+
defineCustomElement(defineAsyncComponent(() => import('./views/ActivityTab.vue'))),
14+
)
1715

18-
const activityTab = new OCA.Files.Sidebar.Tab({
16+
registerSidebarTab({
1917
id: 'activity',
20-
name: t('activity', 'Activity'),
21-
iconSvg: LightningBolt,
22-
23-
async mount(el, fileInfo) {
24-
// only load if needed
25-
if (LazyActivityTab === null) {
26-
const { default: ActivityTab } = await import('./views/ActivityTab.vue')
27-
LazyActivityTab = ActivityTab
28-
}
29-
// destroy previous instance if available
30-
if (activityTabApp) {
31-
activityTabApp.unmount()
32-
}
33-
activityTabApp = createApp(LazyActivityTab)
34-
// No need to await this, we will show a loading indicator instead
35-
activityTabInstance = activityTabApp.mount(el)
36-
activityTabInstance.update(fileInfo)
37-
},
38-
update(fileInfo) {
39-
activityTabInstance!.update(fileInfo)
18+
order: 50,
19+
displayName: t('activity', 'Activity'),
20+
iconSvgInline: LightningBolt,
21+
tagName: 'activity-files-sidebar-tab',
22+
enabled() {
23+
return true
4024
},
41-
destroy() {
42-
activityTabApp?.unmount()
43-
activityTabApp = null
44-
},
45-
})
46-
47-
window.addEventListener('DOMContentLoaded', async function() {
48-
if (OCA.Files && OCA.Files.Sidebar) {
49-
OCA.Files.Sidebar.registerTab(activityTab)
50-
const { default: ActivityTab } = await import('./views/ActivityTab.vue')
51-
LazyActivityTab = ActivityTab
52-
}
5325
})

src/utils/api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ declare global {
2828
__sidebar_filters: IActivityFilter[]
2929
}
3030
Viewer?: {
31-
open(options: { path?: string, fileInfo?: unknown }): void
31+
open(options: { path?: string }): void
3232
get mimetypes(): string[]
3333
}
3434
}
@@ -74,7 +74,7 @@ export function getSidebarActions() {
7474
/**
7575
* Get all additional activity stream entries for a given file object
7676
*
77-
* @param options Filter options for the additonal entries
77+
* @param options Filter options for the additional entries
7878
*/
7979
export async function getAdditionalEntries(options: ActivityFactoryQueryOptions) {
8080
if (window.OCA?.Activity?.__sidebar_factories === undefined) {

src/views/ActivityTab.vue

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
:class="{ 'icon-loading': loading }"
99
class="activity">
1010
<!-- error message -->
11-
<NcEmptyContent v-if="error || fileInfo === null" :name="error">
11+
<NcEmptyContent v-if="error || !node" :name="error">
1212
<template #icon>
1313
<NcIconSvgWrapper :svg="lightningBoltSVG" />
1414
</template>
@@ -20,7 +20,7 @@
2020
v-for="plugin, index of sidebarPlugins"
2121
:key="index"
2222
:plugin="plugin"
23-
:file-info="fileInfo"
23+
:node="node"
2424
@reload-activities="getActivities()" />
2525
</div>
2626

@@ -54,6 +54,8 @@
5454
</template>
5555

5656
<script lang='ts'>
57+
import type { IFolder, INode, IView } from '@nextcloud/files'
58+
import type { PropType } from 'vue'
5759
import type { IActivitySidebarAction, IActivitySidebarEntry } from '../models/ActivityAPI.ts'
5860
5961
import lightningBoltSVG from '@mdi/svg/svg/lightning-bolt.svg?raw'
@@ -72,6 +74,7 @@ import logger from '../utils/logger.ts'
7274
7375
const ActivityTab = defineComponent({
7476
name: 'ActivityTab',
77+
7578
components: {
7679
ActivityComponent,
7780
NcEmptyContent,
@@ -80,30 +83,66 @@ const ActivityTab = defineComponent({
8083
ActivitySidebarPlugin,
8184
},
8285
86+
props: {
87+
/**
88+
* The node currently displayed in the sidebar
89+
*/
90+
node: {
91+
type: Object as PropType<INode>,
92+
required: true,
93+
},
94+
95+
/**
96+
* The folder shown in the files app
97+
*/
98+
// eslint-disable-next-line vue/no-unused-properties
99+
folder: {
100+
type: Object as PropType<IFolder | undefined>,
101+
required: false,
102+
default: undefined,
103+
},
104+
105+
/**
106+
* The view shown in the files app
107+
*/
108+
// eslint-disable-next-line vue/no-unused-properties
109+
view: {
110+
type: Object as PropType<IView | undefined>,
111+
required: false,
112+
default: undefined,
113+
},
114+
},
115+
83116
expose: ['update'],
84117
85118
data() {
86119
return {
87120
error: '',
88121
loading: true,
89-
fileInfo: null,
90122
activities: [] as (IActivitySidebarEntry | ActivityModel)[],
91123
lightningBoltSVG,
92124
sidebarPlugins: [] as IActivitySidebarAction[],
93125
}
94126
},
95127
128+
watch: {
129+
node: {
130+
immediate: true,
131+
async handler(node: INode) {
132+
await this.update(node)
133+
},
134+
},
135+
},
136+
96137
mounted() {
97138
this.sidebarPlugins = getSidebarActions()
98139
},
99140
100141
methods: {
101142
/**
102-
* Update current fileInfo and fetch new activities
103-
*
104-
* @param fileInfo the current file FileInfo
143+
* Update current view and fetch new activities
105144
*/
106-
async update(fileInfo) {
145+
async update() {
107146
this.sidebarPlugins = []
108147
const sidebarPlugins = getSidebarActions()
109148
if (sidebarPlugins.length > 0) {
@@ -112,7 +151,6 @@ const ActivityTab = defineComponent({
112151
})
113152
}
114153
115-
this.fileInfo = fileInfo
116154
this.resetState()
117155
await this.getActivities()
118156
},
@@ -125,7 +163,7 @@ const ActivityTab = defineComponent({
125163
this.loading = true
126164
127165
const activities = await this.processActivities(await this.loadRealActivities())
128-
const otherEntries = await getAdditionalEntries({ fileInfo: this.fileInfo })
166+
const otherEntries = await getAdditionalEntries({ node: this.node })
129167
this.activities = [...activities, ...otherEntries].sort((a, b) => b.timestamp - a.timestamp)
130168
} catch (error) {
131169
this.error = t('activity', 'Unable to load the activity list')
@@ -155,7 +193,7 @@ const ActivityTab = defineComponent({
155193
params: {
156194
format: 'json',
157195
object_type: 'files',
158-
object_id: this.fileInfo.id,
196+
object_id: this.node.fileid,
159197
},
160198
},
161199
)
@@ -177,7 +215,9 @@ const ActivityTab = defineComponent({
177215
processActivities(activities): ActivityModel[] {
178216
activities = activities.map((activity) => new ActivityModel(activity))
179217
180-
logger.debug(`Processed ${activities.length} activity(ies)`, { activities, fileInfo: this.fileInfo })
218+
logger.debug(`Processed ${activities.length} activity(ies)`, {
219+
activities, node: this.node,
220+
})
181221
182222
const filters = getActivityFilters()
183223
return activities.filter((activity) => !filters || filters.every((filter) => filter(activity)))

0 commit comments

Comments
 (0)