Skip to content

Commit 6cf147f

Browse files
karlitschekclaude
andcommitted
feat(stream): add an actions menu to each activity entry
Activity entries were read-only: seeing that a file changed gave no way to act on it without leaving for the Files app and finding it again. Only third parties could contribute anything interactive, through registerSidebarAction(), and that applies to the panel as a whole rather than to a single entry. Each entry now carries an actions menu offering, where applicable: - View, opening the file in the Viewer overlay without leaving the stream - Show in Files, revealing the file inside its folder - Copy link, always putting an absolute URL on the clipboard so it stays usable elsewhere Links deliberately do not reuse the host from the activity's own link. Providers build absolute URLs out of the server's configuration rather than the current request, so an activity recorded by a background job carries overwrite.cli.url — routinely http://localhost:8080, which no browser visiting the instance can reach. Only the path is trustworthy. Where a file id is known the link is rebuilt from scratch as /f/<fileid>, which also addresses the file itself rather than the containing directory a provider link often points at, and lets the server redirect to whichever view holds it. Otherwise the provider's path is kept and re-anchored onto the origin the session is actually using. Show in Files reveals rather than opens. `/f/<fileid>` opens the file in the Viewer on arrival, because the server sets `openfile=true` for anything that is not a folder, so the link asks for `openfile=false` explicitly. Opening is what the View action is for, and one menu entry doing both is merely surprising. The copied link is left at the server's default instead: whoever receives it wants the file, not this menu's idea of how to look at the folder around it. Actions appear only when they can do something. View needs a MIME type the Viewer advertises, which is only known where previews were requested, so it is absent in the sidebar. An activity with no file behind it renders no menu at all rather than an empty one. The menu is faded out until its row is hovered, and only on devices with a pointer, so a touch user is never asked to hover to discover it. Fading rather than hiding keeps it reachable by keyboard, and the row owns the hover rule because the row owns its layout. GenericActivity already had an unused `&__actions` style block, which now has markup to match. Clipboard failures are reported rather than swallowed, including the insecure-origin case where navigator.clipboard does not exist at all. The test config gains @nextcloud/dialogs in vitest's list of inlined dependencies. It ships CSS imports that Node cannot load, which previously went unnoticed because every test touching that module mocked it; pulling it into the entry components made three unrelated suites fail to collect. Signed-off-by: Frank Karlitschek <karlitschek@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 932d60e commit 6cf147f

36 files changed

Lines changed: 664 additions & 102 deletions

js/ActivityComponent.vue_vue_type_script_setup_true_lang-Ce6NNuXP.chunk.mjs renamed to js/ActivityComponent.vue_vue_type_script_setup_true_lang-BSef5VlI.chunk.mjs

Lines changed: 33 additions & 33 deletions
Large diffs are not rendered by default.

js/ActivityComponent.vue_vue_type_script_setup_true_lang-Ce6NNuXP.chunk.mjs.license renamed to js/ActivityComponent.vue_vue_type_script_setup_true_lang-BSef5VlI.chunk.mjs.license

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ SPDX-FileCopyrightText: Julius Härtl <jus@bitgrid.net>
1616
SPDX-FileCopyrightText: Mark <mark@remarkablemark.org>
1717
SPDX-FileCopyrightText: Max <max@nextcloud.com>
1818
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
19+
SPDX-FileCopyrightText: Rob Cresswell <robcresswell@pm.me>
1920
SPDX-FileCopyrightText: Sindre Sorhus
2021
SPDX-FileCopyrightText: Stefan Thomas <justmoon@members.fsf.org> (http://www.justmoon.net)
2122
SPDX-FileCopyrightText: Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)
@@ -243,6 +244,9 @@ This file is generated from multiple sources. Included packages:
243244
- vite
244245
- version: 7.3.6
245246
- license: MIT
247+
- vue-material-design-icons
248+
- version: 5.3.1
249+
- license: MIT
246250
- vue-router
247251
- version: 5.1.0
248252
- license: MIT

js/ActivityComponent.vue_vue_type_script_setup_true_lang-BSef5VlI.chunk.mjs.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/ActivityComponent.vue_vue_type_script_setup_true_lang-Ce6NNuXP.chunk.mjs.map

Lines changed: 0 additions & 1 deletion
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
(function(){"use strict";try{if(typeof document<"u"){var t=document.createElement("style");t.appendChild(document.createTextNode(".download-summary[data-v-81e4514f]{display:flex;align-items:flex-start;padding:8px 0;margin-bottom:calc(var(--default-grid-baseline) * 2);color:var(--color-text-maxcontrast)}.download-summary__icon[data-v-81e4514f]{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:20px;height:20px;margin-top:2px;opacity:.5}.download-summary__text[data-v-81e4514f]{padding:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.activity[data-v-50818749]{display:flex;flex-direction:column;overflow:hidden;height:100%}.activity__actions[data-v-50818749]{display:flex;flex-direction:column;width:100%}.activity__list[data-v-50818749]{flex-grow:1;overflow:scroll}.activity__empty-content[data-v-50818749]{height:100%}[data-v-50818749] .empty-content__icon span{background-size:64px;width:64px;height:64px}")),document.head.appendChild(t)}}catch(e){console.error("vite-plugin-css-injected-by-js",e)}})();
2-
import{l as R}from"./activity-sidebar.mjs";import{b as A,j as I,h as G,m as D}from"./_plugin-vue_export-helper-YgYGpvaA.chunk.mjs";import{d as b,x as V,B as E,y as M,a as i,c as o,j as T,a1 as p,M as h,m as n,e as S,f as u,t as B,E as g,l as H,b as r,g as y,F as v,L as k,n as q}from"./translation-DoG5ZELJ-ns3A9vL-.chunk.mjs";import{a as j}from"./index-BrNGPgve.chunk.mjs";import{N as F,_ as O,A as W}from"./ActivityComponent.vue_vue_type_script_setup_true_lang-Ce6NNuXP.chunk.mjs";import{l as f}from"./logger-CXg3FpL2.chunk.mjs";import{g as U,a as z,b as Z}from"./api-fwrRGLr6.chunk.mjs";import"./preload-helper-DxYC2qmj.chunk.mjs";var w;(function(t){t[t.User=0]="User",t[t.Group=1]="Group",t[t.Link=3]="Link",t[t.Email=4]="Email",t[t.Remote=6]="Remote",t[t.Team=7]="Team",t[t.Guest=8]="Guest",t[t.RemoteGroup=9]="RemoteGroup",t[t.Room=10]="Room",t[t.Deck=12]="Deck",t[t.FederatedGroup=14]="FederatedGroup",t[t.ScienceMesh=15]="ScienceMesh"})(w||(w={}));const $=b({__name:"ActivitySidebarPlugin",props:{plugin:{},node:{}},emits:["reloadActivities"],setup(t,{emit:e}){const a=t,s=e,d=T();return V(()=>a.plugin.mount(d.value,{node:a.node,context:E()?.proxy??void 0,reload:()=>s("reloadActivities")})),M(()=>a.plugin.unmount()),(_,c)=>(i(),o("div",{ref_key:"attachTarget",ref:d},null,512))}}),J='<svg xmlns="http://www.w3.org/2000/svg" id="mdi-download-circle" viewBox="0 0 24 24"><path d="M12 2C17.5 2 22 6.5 22 12C22 17.5 17.5 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2M8 17H16V15H8V17M16 10H13.5V6H10.5V10H8L12 14L16 10Z" /></svg>',K=b({name:"DownloadSummary",components:{NcIconSvgWrapper:A},props:{fileId:{type:Number,required:!0}},data(){return{totalCount:0,monthlyCount:0,downloadSVG:J}},computed:{summaryText(){return this.monthlyCount>0&&this.monthlyCount<this.totalCount?p("activity","Downloaded %n time (%s in the last 30 days)","Downloaded %n times (%s in the last 30 days)",this.totalCount,[String(this.monthlyCount)]):p("activity","Downloaded %n time","Downloaded %n times",this.totalCount)}},watch:{fileId:{immediate:!0,handler(){this.fetchCounts()}}},methods:{async fetchCounts(){if(this.fileId){this.totalCount=0,this.monthlyCount=0;try{const t=await I.get(j("apps/activity/api/v2/activity/downloads/count"),{params:{format:"json",object_type:"files",object_id:this.fileId}});this.totalCount=t.data.ocs.data.total,this.monthlyCount=t.data.ocs.data.last30d}catch(t){f.error("Failed to fetch download counts",{error:t})}}},t:h,n:p}}),Q={key:0,class:"download-summary"},X={class:"download-summary__icon"},Y={class:"download-summary__text"};function tt(t,e,a,s,d,_){const c=n("NcIconSvgWrapper");return t.totalCount>0?(i(),o("div",Q,[S("span",X,[u(c,{svg:t.downloadSVG,size:20},null,8,["svg"])]),S("span",Y,B(t.summaryText),1)])):g("",!0)}const it=G(K,[["render",tt],["__scopeId","data-v-81e4514f"]]),et=b({name:"ActivityTab",components:{ActivityComponent:O,DownloadSummary:it,NcEmptyContent:F,NcIconSvgWrapper:A,NcLoadingIcon:D,ActivitySidebarPlugin:$},props:{node:{type:Object,required:!0},folder:{type:Object,required:!1,default:void 0},view:{type:Object,required:!1,default:void 0}},expose:["update"],data(){return{error:"",loading:!0,activities:[],lightningBoltSVG:R,sidebarPlugins:[]}},computed:{hasPublicLink(){return Object.values(this.node?.attributes?.["share-types"]??{}).flat().includes(w.Link)}},watch:{node:{immediate:!0,async handler(){await this.update()}}},async mounted(){this.node&&await this.update()},methods:{async update(){this.sidebarPlugins=[];const t=Z();t.length>0&&H(()=>{this.sidebarPlugins=t}),this.resetState(),await this.getActivities()},async getActivities(){try{this.loading=!0;const t=await this.processActivities(await this.loadRealActivities()),e=await z({node:this.node});this.activities=[...t,...e].sort((a,s)=>s.timestamp-a.timestamp)}catch(t){this.error=h("activity","Unable to load the activity list"),f.error("Error loading the activity list",{error:t})}finally{this.loading=!1}},resetState(){this.loading=!0,this.error="",this.activities=[]},async loadRealActivities(){try{const{data:t}=await I.get(j("apps/activity/api/v2/activity/filter"),{params:{format:"json",object_type:"files",object_id:this.node.fileid}});return t.ocs.data}catch(t){if(t.response!==void 0&&t.response.status===304)return[];throw t}},processActivities(t){t=t.map(a=>new W(a)),f.debug(`Processed ${t.length} activity(ies)`,{activities:t,node:this.node});const e=U();return t.filter(a=>!e||e.every(s=>s(a)))},t:h}}),at={key:0,class:"activity__actions"},ot={key:4,class:"activity__list"};function st(t,e,a,s,d,_){const c=n("NcIconSvgWrapper"),m=n("NcEmptyContent"),P=n("ActivitySidebarPlugin"),L=n("DownloadSummary"),N=n("NcLoadingIcon"),x=n("ActivityComponent");return i(),o("div",{class:q([{"icon-loading":t.loading},"activity"])},[t.error||!t.node?(i(),r(m,{key:0,name:t.error},{icon:y(()=>[u(c,{svg:t.lightningBoltSVG},null,8,["svg"])]),_:1},8,["name"])):(i(),o(v,{key:1},[t.sidebarPlugins.length>0?(i(),o("div",at,[(i(!0),o(v,null,k(t.sidebarPlugins,(l,C)=>(i(),r(P,{key:C,plugin:l,node:t.node,onReloadActivities:e[0]||(e[0]=nt=>t.getActivities())},null,8,["plugin","node"]))),128))])):g("",!0),t.hasPublicLink&&t.node.fileid?(i(),r(L,{key:1,fileId:t.node.fileid},null,8,["fileId"])):g("",!0),t.loading?(i(),r(m,{key:2,class:"activity__empty-content",name:t.t("activity","Loading activities")},{icon:y(()=>[u(N)]),_:1},8,["name"])):t.activities.length===0?(i(),r(m,{key:3,class:"activity__empty-content",name:t.t("activity","No activity yet")},{icon:y(()=>[u(c,{svg:t.lightningBoltSVG},null,8,["svg"])]),_:1},8,["name"])):(i(),o("ul",ot,[(i(!0),o(v,null,k(t.activities,l=>(i(),r(x,{key:l.id,activity:l,showPreviews:!1,onReload:e[1]||(e[1]=C=>t.getActivities())},null,8,["activity"]))),128))]))],64))],2)}const vt=G(et,[["render",st],["__scopeId","data-v-50818749"]]);export{vt as default};
3-
//# sourceMappingURL=ActivityTab-EzBumh36.chunk.mjs.map
2+
import{l as R}from"./activity-sidebar.mjs";import{b as A,j as I,h as G,m as D}from"./_plugin-vue_export-helper-DJ-g_lw0.chunk.mjs";import{d as b,x as V,B as E,y as M,a as i,c as o,j as T,a1 as p,M as h,m as n,e as S,f as u,t as B,E as g,l as H,b as r,g as y,F as v,L as k,n as q}from"./translation-DoG5ZELJ-ns3A9vL-.chunk.mjs";import{a as j}from"./index-BrNGPgve.chunk.mjs";import{N as F,_ as O,A as W}from"./ActivityComponent.vue_vue_type_script_setup_true_lang-BSef5VlI.chunk.mjs";import{l as f}from"./logger-CXg3FpL2.chunk.mjs";import{g as U,a as z,b as Z}from"./api-fwrRGLr6.chunk.mjs";import"./preload-helper-DxYC2qmj.chunk.mjs";var w;(function(t){t[t.User=0]="User",t[t.Group=1]="Group",t[t.Link=3]="Link",t[t.Email=4]="Email",t[t.Remote=6]="Remote",t[t.Team=7]="Team",t[t.Guest=8]="Guest",t[t.RemoteGroup=9]="RemoteGroup",t[t.Room=10]="Room",t[t.Deck=12]="Deck",t[t.FederatedGroup=14]="FederatedGroup",t[t.ScienceMesh=15]="ScienceMesh"})(w||(w={}));const $=b({__name:"ActivitySidebarPlugin",props:{plugin:{},node:{}},emits:["reloadActivities"],setup(t,{emit:e}){const a=t,s=e,d=T();return V(()=>a.plugin.mount(d.value,{node:a.node,context:E()?.proxy??void 0,reload:()=>s("reloadActivities")})),M(()=>a.plugin.unmount()),(_,c)=>(i(),o("div",{ref_key:"attachTarget",ref:d},null,512))}}),J='<svg xmlns="http://www.w3.org/2000/svg" id="mdi-download-circle" viewBox="0 0 24 24"><path d="M12 2C17.5 2 22 6.5 22 12C22 17.5 17.5 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2M8 17H16V15H8V17M16 10H13.5V6H10.5V10H8L12 14L16 10Z" /></svg>',K=b({name:"DownloadSummary",components:{NcIconSvgWrapper:A},props:{fileId:{type:Number,required:!0}},data(){return{totalCount:0,monthlyCount:0,downloadSVG:J}},computed:{summaryText(){return this.monthlyCount>0&&this.monthlyCount<this.totalCount?p("activity","Downloaded %n time (%s in the last 30 days)","Downloaded %n times (%s in the last 30 days)",this.totalCount,[String(this.monthlyCount)]):p("activity","Downloaded %n time","Downloaded %n times",this.totalCount)}},watch:{fileId:{immediate:!0,handler(){this.fetchCounts()}}},methods:{async fetchCounts(){if(this.fileId){this.totalCount=0,this.monthlyCount=0;try{const t=await I.get(j("apps/activity/api/v2/activity/downloads/count"),{params:{format:"json",object_type:"files",object_id:this.fileId}});this.totalCount=t.data.ocs.data.total,this.monthlyCount=t.data.ocs.data.last30d}catch(t){f.error("Failed to fetch download counts",{error:t})}}},t:h,n:p}}),Q={key:0,class:"download-summary"},X={class:"download-summary__icon"},Y={class:"download-summary__text"};function tt(t,e,a,s,d,_){const c=n("NcIconSvgWrapper");return t.totalCount>0?(i(),o("div",Q,[S("span",X,[u(c,{svg:t.downloadSVG,size:20},null,8,["svg"])]),S("span",Y,B(t.summaryText),1)])):g("",!0)}const it=G(K,[["render",tt],["__scopeId","data-v-81e4514f"]]),et=b({name:"ActivityTab",components:{ActivityComponent:O,DownloadSummary:it,NcEmptyContent:F,NcIconSvgWrapper:A,NcLoadingIcon:D,ActivitySidebarPlugin:$},props:{node:{type:Object,required:!0},folder:{type:Object,required:!1,default:void 0},view:{type:Object,required:!1,default:void 0}},expose:["update"],data(){return{error:"",loading:!0,activities:[],lightningBoltSVG:R,sidebarPlugins:[]}},computed:{hasPublicLink(){return Object.values(this.node?.attributes?.["share-types"]??{}).flat().includes(w.Link)}},watch:{node:{immediate:!0,async handler(){await this.update()}}},async mounted(){this.node&&await this.update()},methods:{async update(){this.sidebarPlugins=[];const t=Z();t.length>0&&H(()=>{this.sidebarPlugins=t}),this.resetState(),await this.getActivities()},async getActivities(){try{this.loading=!0;const t=await this.processActivities(await this.loadRealActivities()),e=await z({node:this.node});this.activities=[...t,...e].sort((a,s)=>s.timestamp-a.timestamp)}catch(t){this.error=h("activity","Unable to load the activity list"),f.error("Error loading the activity list",{error:t})}finally{this.loading=!1}},resetState(){this.loading=!0,this.error="",this.activities=[]},async loadRealActivities(){try{const{data:t}=await I.get(j("apps/activity/api/v2/activity/filter"),{params:{format:"json",object_type:"files",object_id:this.node.fileid}});return t.ocs.data}catch(t){if(t.response!==void 0&&t.response.status===304)return[];throw t}},processActivities(t){t=t.map(a=>new W(a)),f.debug(`Processed ${t.length} activity(ies)`,{activities:t,node:this.node});const e=U();return t.filter(a=>!e||e.every(s=>s(a)))},t:h}}),at={key:0,class:"activity__actions"},ot={key:4,class:"activity__list"};function st(t,e,a,s,d,_){const c=n("NcIconSvgWrapper"),m=n("NcEmptyContent"),P=n("ActivitySidebarPlugin"),L=n("DownloadSummary"),N=n("NcLoadingIcon"),x=n("ActivityComponent");return i(),o("div",{class:q([{"icon-loading":t.loading},"activity"])},[t.error||!t.node?(i(),r(m,{key:0,name:t.error},{icon:y(()=>[u(c,{svg:t.lightningBoltSVG},null,8,["svg"])]),_:1},8,["name"])):(i(),o(v,{key:1},[t.sidebarPlugins.length>0?(i(),o("div",at,[(i(!0),o(v,null,k(t.sidebarPlugins,(l,C)=>(i(),r(P,{key:C,plugin:l,node:t.node,onReloadActivities:e[0]||(e[0]=nt=>t.getActivities())},null,8,["plugin","node"]))),128))])):g("",!0),t.hasPublicLink&&t.node.fileid?(i(),r(L,{key:1,fileId:t.node.fileid},null,8,["fileId"])):g("",!0),t.loading?(i(),r(m,{key:2,class:"activity__empty-content",name:t.t("activity","Loading activities")},{icon:y(()=>[u(N)]),_:1},8,["name"])):t.activities.length===0?(i(),r(m,{key:3,class:"activity__empty-content",name:t.t("activity","No activity yet")},{icon:y(()=>[u(c,{svg:t.lightningBoltSVG},null,8,["svg"])]),_:1},8,["name"])):(i(),o("ul",ot,[(i(!0),o(v,null,k(t.activities,l=>(i(),r(x,{key:l.id,activity:l,showPreviews:!1,onReload:e[1]||(e[1]=C=>t.getActivities())},null,8,["activity"]))),128))]))],64))],2)}const vt=G(et,[["render",st],["__scopeId","data-v-50818749"]]);export{vt as default};
3+
//# sourceMappingURL=ActivityTab-BQORJhkx.chunk.mjs.map
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/_plugin-vue_export-helper-DJ-g_lw0.chunk.mjs

Lines changed: 22 additions & 0 deletions
Large diffs are not rendered by default.

js/_plugin-vue_export-helper-YgYGpvaA.chunk.mjs.license renamed to js/_plugin-vue_export-helper-DJ-g_lw0.chunk.mjs.license

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
SPDX-License-Identifier: AGPL-3.0-or-later
22
SPDX-License-Identifier: GPL-3.0-or-later
33
SPDX-License-Identifier: MIT
4+
SPDX-FileCopyrightText: @nextcloud/dialogs developers
45
SPDX-FileCopyrightText: David Clark
56
SPDX-FileCopyrightText: Eduardo San Martin Morote
67
SPDX-FileCopyrightText: Evan You
78
SPDX-FileCopyrightText: Guillaume Chau <guillaume.b.chau@gmail.com>
89
SPDX-FileCopyrightText: Matt Zabriskie
910
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
11+
SPDX-FileCopyrightText: Varun A P
1012
SPDX-FileCopyrightText: atomiks
1113

1214
This file is generated from multiple sources. Included packages:
@@ -22,6 +24,9 @@ This file is generated from multiple sources. Included packages:
2224
- @nextcloud/axios
2325
- version: 2.6.0
2426
- license: GPL-3.0-or-later
27+
- @nextcloud/dialogs
28+
- version: 7.4.1
29+
- license: AGPL-3.0-or-later
2530
- @nextcloud/initial-state
2631
- version: 3.0.0
2732
- license: GPL-3.0-or-later
@@ -46,6 +51,9 @@ This file is generated from multiple sources. Included packages:
4651
- tabbable
4752
- version: 6.5.0
4853
- license: MIT
54+
- toastify-js
55+
- version: 1.12.0
56+
- license: MIT
4957
- vue-router
5058
- version: 5.1.0
5159
- license: MIT

js/_plugin-vue_export-helper-DJ-g_lw0.chunk.mjs.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)