Merge pull request #60523 from nextcloud/backport/60150/stable31
Some checks are pending
Integration sqlite / changes (push) Waiting to run
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, --tags ~@large files_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, capabilities_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, collaboration_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, comments_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, dav_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, federation_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, file_conversions) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, files_reminders) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, filesdrop_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, ldap_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, openldap_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, openldap_numerical_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, remoteapi_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, setup_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, sharees_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, sharing_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, theming_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite (stable31, 8.1, stable31, videoverification_features) (push) Blocked by required conditions
Integration sqlite / integration-sqlite-summary (push) Blocked by required conditions
Psalm static code analysis / static-code-analysis (push) Waiting to run
Psalm static code analysis / static-code-analysis-security (push) Waiting to run
Psalm static code analysis / static-code-analysis-ocp (push) Waiting to run
Psalm static code analysis / static-code-analysis-ncu (push) Waiting to run

[stable31] fix(comments): Add an action to comment notification that dismisses it
This commit is contained in:
Joas Schilling 2026-05-20 10:20:39 +02:00 committed by GitHub
commit 6d87a25ee6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 142 additions and 27 deletions

View file

@ -79,7 +79,10 @@ class NotificationsController extends Controller {
$url = $this->urlGenerator->linkToRouteAbsolute(
'files.viewcontroller.showFile',
[ 'fileid' => $comment->getObjectId() ]
[
'fileid' => $comment->getObjectId(),
'opendetails' => 'true',
]
);
return new RedirectResponse($url);

View file

@ -15,6 +15,7 @@ use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Notification\AlreadyProcessedException;
use OCP\Notification\IAction;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
use OCP\Notification\UnknownNotificationException;
@ -116,14 +117,23 @@ class Notifier implements INotifier {
'name' => $displayName,
];
}
$commentLink = $this->url->linkToRouteAbsolute(
'comments.Notifications.view',
['id' => $comment->getId()],
);
[$message, $messageParameters] = $this->commentToRichMessage($comment);
$notification->setRichSubject($subject, $subjectParameters)
->setRichMessage($message, $messageParameters)
->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/comment.svg')))
->setLink($this->url->linkToRouteAbsolute(
'comments.Notifications.view',
['id' => $comment->getId()])
);
->setLink($commentLink);
$action = $notification->createAction();
$action->setLink($commentLink, IAction::TYPE_WEB);
$action->setPrimary(true);
$action->setParsedLabel($l->t('Go to file'));
$notification->addParsedAction($action);
return $notification;
break;

View file

@ -0,0 +1,114 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { testSupportedBrowser } from '../../utils/RedirectUnsupportedBrowsers.js'
// Mock the router so generateUrl returns a predictable path
vi.mock('@nextcloud/router', () => ({
generateUrl: (path: string) => `/index.php${path}`,
}))
// Mock the logger to suppress output
vi.mock('../../logger.js', () => ({
default: { debug: vi.fn() },
}))
const browserStorage = vi.hoisted(() => ({ getItem: vi.fn(() => null) }))
vi.mock('../../services/BrowserStorageService.js', () => ({ default: browserStorage }))
const supportedUA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
const unsupportedUA = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'
describe('testSupportedBrowser', () => {
let originalLocation: Location
beforeEach(() => {
originalLocation = window.location
// Reset the override flag
browserStorage.getItem.mockReturnValue(null)
// Default to a path that isn't the unsupported-browser page
Object.defineProperty(window, 'location', {
configurable: true,
writable: true,
value: {
href: 'http://localhost/apps/files',
origin: 'http://localhost',
pathname: '/apps/files',
reload: vi.fn(),
},
})
vi.spyOn(window.history, 'pushState').mockImplementation(() => {})
})
afterEach(() => {
Object.defineProperty(window, 'location', {
configurable: true,
writable: true,
value: originalLocation,
})
vi.restoreAllMocks()
})
it('does nothing for a supported browser', () => {
Object.defineProperty(window.navigator, 'userAgent', { configurable: true, value: supportedUA })
testSupportedBrowser()
expect(window.history.pushState).not.toHaveBeenCalled()
expect(window.location.reload).not.toHaveBeenCalled()
})
it('redirects an unsupported browser to the warning page', () => {
Object.defineProperty(window.navigator, 'userAgent', { configurable: true, value: unsupportedUA })
testSupportedBrowser()
expect(window.history.pushState).toHaveBeenCalledOnce()
const [, , url] = (window.history.pushState as ReturnType<typeof vi.fn>).mock.calls[0]
expect(url).toMatch(/^\/index\.php\/unsupported\?redirect_url=/)
expect(window.location.reload).toHaveBeenCalledOnce()
})
it('encodes the redirect URL with btoa, not window.Buffer', () => {
Object.defineProperty(window.navigator, 'userAgent', { configurable: true, value: unsupportedUA })
testSupportedBrowser()
const [, , url] = (window.history.pushState as ReturnType<typeof vi.fn>).mock.calls[0]
const encoded = new URL(`http://localhost${url}`).searchParams.get('redirect_url')
expect(encoded).toBe(btoa('/apps/files'))
})
it('does not throw regardless of override flag state', () => {
// isBrowserOverridden is read at module-load time so the mock won't flip it
// retroactively — but we can at least assert the function never throws,
// which is the regression guard for the window.Buffer removal.
Object.defineProperty(window.navigator, 'userAgent', { configurable: true, value: unsupportedUA })
expect(() => testSupportedBrowser()).not.toThrow()
})
it('does not redirect when already on the unsupported-browser page', () => {
Object.defineProperty(window.navigator, 'userAgent', { configurable: true, value: unsupportedUA })
Object.defineProperty(window, 'location', {
configurable: true,
writable: true,
value: {
href: 'http://localhost/index.php/unsupported',
origin: 'http://localhost',
pathname: '/index.php/unsupported',
reload: vi.fn(),
},
})
testSupportedBrowser()
expect(window.history.pushState).not.toHaveBeenCalled()
expect(window.location.reload).not.toHaveBeenCalled()
})
})

View file

@ -33,7 +33,7 @@ export const testSupportedBrowser = function() {
// redirect to the unsupported warning page
if (window.location.pathname.indexOf(redirectPath) === -1) {
const redirectUrl = window.location.href.replace(window.location.origin, '')
const base64Param = Buffer.from(redirectUrl).toString('base64')
const base64Param = btoa(redirectUrl)
history.pushState(null, null, `${redirectPath}?redirect_url=${base64Param}`)
window.location.reload()
}

4
dist/7883-7883.js vendored

File diff suppressed because one or more lines are too long

View file

@ -2,18 +2,15 @@ SPDX-License-Identifier: MIT
SPDX-License-Identifier: ISC
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-License-Identifier: CC-BY-4.0
SPDX-License-Identifier: BSD-3-Clause
SPDX-License-Identifier: Apache-2.0
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-FileCopyrightText: dangreen
SPDX-FileCopyrightText: baseline-browser-mapping developers
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
SPDX-FileCopyrightText: Sergey Rubanov <chi187@gmail.com>
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
SPDX-FileCopyrightText: Kilian Valkhof
SPDX-FileCopyrightText: GitHub Inc.
SPDX-FileCopyrightText: Feross Aboukhadijeh
SPDX-FileCopyrightText: Dmitry Soshnikov
SPDX-FileCopyrightText: Christoph Wurst
SPDX-FileCopyrightText: Ben Briggs
@ -42,9 +39,6 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/router
- version: 3.1.0
- license: GPL-3.0-or-later
- base64-js
- version: 1.5.1
- license: MIT
- baseline-browser-mapping
- version: 2.9.11
- license: Apache-2.0
@ -60,12 +54,6 @@ This file is generated from multiple sources. Included packages:
- electron-to-chromium
- version: 1.5.267
- license: ISC
- ieee754
- version: 1.2.1
- license: BSD-3-Clause
- buffer
- version: 6.0.3
- license: MIT
- node-releases
- version: 2.0.27
- license: MIT

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
(()=>{"use strict";var e,r,t,o={47210(e,r,t){var o=t(21777);t.nc=(0,o.aV)(),window.TESTING||OC?.config?.no_unsupported_browser_warning||window.addEventListener("DOMContentLoaded",(async function(){const{testSupportedBrowser:e}=await Promise.all([t.e(4208),t.e(7883)]).then(t.bind(t,77883));e()}))}},n={};function a(e){var r=n[e];if(void 0!==r)return r.exports;var t=n[e]={id:e,loaded:!1,exports:{}};return o[e].call(t.exports,t,t.exports,a),t.loaded=!0,t.exports}a.m=o,e=[],a.O=(r,t,o,n)=>{if(!t){var i=1/0;for(s=0;s<e.length;s++){for(var[t,o,n]=e[s],l=!0,d=0;d<t.length;d++)(!1&n||i>=n)&&Object.keys(a.O).every((e=>a.O[e](t[d])))?t.splice(d--,1):(l=!1,n<i&&(i=n));if(l){e.splice(s--,1);var c=o();void 0!==c&&(r=c)}}return r}n=n||0;for(var s=e.length;s>0&&e[s-1][2]>n;s--)e[s]=e[s-1];e[s]=[t,o,n]},a.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return a.d(r,{a:r}),r},a.d=(e,r)=>{for(var t in r)a.o(r,t)&&!a.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((r,t)=>(a.f[t](e,r),r)),[])),a.u=e=>e+"-"+e+".js?v=9d4000663d2aae402ae8",a.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="nextcloud:",a.l=(e,o,n,i)=>{if(r[e])r[e].push(o);else{var l,d;if(void 0!==n)for(var c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var u=c[s];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==t+n){l=u;break}}l||(d=!0,(l=document.createElement("script")).charset="utf-8",a.nc&&l.setAttribute("nonce",a.nc),l.setAttribute("data-webpack",t+n),l.src=e),r[e]=[o];var p=(t,o)=>{l.onerror=l.onload=null,clearTimeout(f);var n=r[e];if(delete r[e],l.parentNode&&l.parentNode.removeChild(l),n&&n.forEach((e=>e(o))),t)return t(o)},f=setTimeout(p.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=p.bind(null,l.onerror),l.onload=p.bind(null,l.onload),d&&document.head.appendChild(l)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.j=3604,(()=>{var e;globalThis.importScripts&&(e=globalThis.location+"");var r=globalThis.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var o=t.length-1;o>-1&&(!e||!/^http(s?):/.test(e));)e=t[o--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),a.p=e})(),(()=>{a.b="undefined"!=typeof document&&document.baseURI||self.location.href;var e={3604:0};a.f.j=(r,t)=>{var o=a.o(e,r)?e[r]:void 0;if(0!==o)if(o)t.push(o[2]);else{var n=new Promise(((t,n)=>o=e[r]=[t,n]));t.push(o[2]=n);var i=a.p+a.u(r),l=new Error;a.l(i,(t=>{if(a.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var n=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+n+": "+i+")",l.name="ChunkLoadError",l.type=n,l.request=i,o[1](l)}}),"chunk-"+r,r)}},a.O.j=r=>0===e[r];var r=(r,t)=>{var o,n,[i,l,d]=t,c=0;if(i.some((r=>0!==e[r]))){for(o in l)a.o(l,o)&&(a.m[o]=l[o]);if(d)var s=d(a)}for(r&&r(t);c<i.length;c++)n=i[c],a.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return a.O(s)},t=globalThis.webpackChunknextcloud=globalThis.webpackChunknextcloud||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),a.nc=void 0;var i=a.O(void 0,[4208],(()=>a(47210)));i=a.O(i)})();
//# sourceMappingURL=core-unsupported-browser-redirect.js.map?v=06e623e4e4e29be34011
(()=>{"use strict";var e,r,t,o={47210(e,r,t){var o=t(21777);t.nc=(0,o.aV)(),window.TESTING||OC?.config?.no_unsupported_browser_warning||window.addEventListener("DOMContentLoaded",(async function(){const{testSupportedBrowser:e}=await Promise.all([t.e(4208),t.e(7883)]).then(t.bind(t,77883));e()}))}},n={};function a(e){var r=n[e];if(void 0!==r)return r.exports;var t=n[e]={id:e,loaded:!1,exports:{}};return o[e].call(t.exports,t,t.exports,a),t.loaded=!0,t.exports}a.m=o,e=[],a.O=(r,t,o,n)=>{if(!t){var i=1/0;for(s=0;s<e.length;s++){for(var[t,o,n]=e[s],l=!0,d=0;d<t.length;d++)(!1&n||i>=n)&&Object.keys(a.O).every((e=>a.O[e](t[d])))?t.splice(d--,1):(l=!1,n<i&&(i=n));if(l){e.splice(s--,1);var c=o();void 0!==c&&(r=c)}}return r}n=n||0;for(var s=e.length;s>0&&e[s-1][2]>n;s--)e[s]=e[s-1];e[s]=[t,o,n]},a.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return a.d(r,{a:r}),r},a.d=(e,r)=>{for(var t in r)a.o(r,t)&&!a.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((r,t)=>(a.f[t](e,r),r)),[])),a.u=e=>e+"-"+e+".js?v=4fafd59634333811d007",a.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="nextcloud:",a.l=(e,o,n,i)=>{if(r[e])r[e].push(o);else{var l,d;if(void 0!==n)for(var c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var u=c[s];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==t+n){l=u;break}}l||(d=!0,(l=document.createElement("script")).charset="utf-8",a.nc&&l.setAttribute("nonce",a.nc),l.setAttribute("data-webpack",t+n),l.src=e),r[e]=[o];var p=(t,o)=>{l.onerror=l.onload=null,clearTimeout(f);var n=r[e];if(delete r[e],l.parentNode&&l.parentNode.removeChild(l),n&&n.forEach((e=>e(o))),t)return t(o)},f=setTimeout(p.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=p.bind(null,l.onerror),l.onload=p.bind(null,l.onload),d&&document.head.appendChild(l)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.j=3604,(()=>{var e;globalThis.importScripts&&(e=globalThis.location+"");var r=globalThis.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var o=t.length-1;o>-1&&(!e||!/^http(s?):/.test(e));)e=t[o--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),a.p=e})(),(()=>{a.b="undefined"!=typeof document&&document.baseURI||self.location.href;var e={3604:0};a.f.j=(r,t)=>{var o=a.o(e,r)?e[r]:void 0;if(0!==o)if(o)t.push(o[2]);else{var n=new Promise(((t,n)=>o=e[r]=[t,n]));t.push(o[2]=n);var i=a.p+a.u(r),l=new Error;a.l(i,(t=>{if(a.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var n=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+n+": "+i+")",l.name="ChunkLoadError",l.type=n,l.request=i,o[1](l)}}),"chunk-"+r,r)}},a.O.j=r=>0===e[r];var r=(r,t)=>{var o,n,[i,l,d]=t,c=0;if(i.some((r=>0!==e[r]))){for(o in l)a.o(l,o)&&(a.m[o]=l[o]);if(d)var s=d(a)}for(r&&r(t);c<i.length;c++)n=i[c],a.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return a.O(s)},t=globalThis.webpackChunknextcloud=globalThis.webpackChunknextcloud||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),a.nc=void 0;var i=a.O(void 0,[4208],(()=>a(47210)));i=a.O(i)})();
//# sourceMappingURL=core-unsupported-browser-redirect.js.map?v=25f0af982a1484fb9558

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long