summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/comments/lib/Listener/LoadSidebarScripts.php6
-rw-r--r--apps/comments/src/comments-activity-tab.ts85
-rw-r--r--apps/comments/src/comments-tab.js79
-rw-r--r--apps/comments/src/components/Comment.vue5
-rw-r--r--apps/comments/src/mixins/CommentMixin.js21
-rw-r--r--apps/comments/src/mixins/CommentView.ts72
-rw-r--r--apps/comments/src/services/CommentsInstance.js19
-rw-r--r--apps/comments/src/services/DeleteComment.js8
-rw-r--r--apps/comments/src/services/EditComment.js8
-rw-r--r--apps/comments/src/services/GetComments.ts23
-rw-r--r--apps/comments/src/services/NewComment.js16
-rw-r--r--apps/comments/src/services/ReadComments.ts12
-rw-r--r--apps/comments/src/views/ActivityCommentAction.vue71
-rw-r--r--apps/comments/src/views/ActivityCommentEntry.vue87
-rw-r--r--apps/comments/src/views/Comments.vue84
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php19
-rw-r--r--apps/dav/lib/Settings/AvailabilitySettings.php8
-rw-r--r--apps/files/l10n/sr.js2
-rw-r--r--apps/files/l10n/sr.json2
-rw-r--r--apps/files/src/mixins/filesSorting.ts2
-rw-r--r--apps/files/src/views/FilesList.vue51
-rw-r--r--apps/files_sharing/src/views/SharingDetailsTab.vue40
-rw-r--r--apps/files_versions/lib/Listener/FileEventsListener.php8
-rw-r--r--apps/settings/css/settings.css2
-rw-r--r--apps/settings/css/settings.css.map2
-rw-r--r--apps/settings/css/settings.scss16
-rw-r--r--apps/settings/l10n/ar.js2
-rw-r--r--apps/settings/l10n/ar.json2
-rw-r--r--apps/settings/l10n/cs.js2
-rw-r--r--apps/settings/l10n/cs.json2
-rw-r--r--apps/settings/l10n/de.js2
-rw-r--r--apps/settings/l10n/de.json2
-rw-r--r--apps/settings/l10n/fr.js2
-rw-r--r--apps/settings/l10n/fr.json2
-rw-r--r--apps/settings/l10n/sv.js2
-rw-r--r--apps/settings/l10n/sv.json2
-rw-r--r--apps/settings/l10n/zh_TW.js2
-rw-r--r--apps/settings/l10n/zh_TW.json2
-rw-r--r--apps/settings/src/components/AppList/AppItem.vue37
-rw-r--r--apps/settings/src/components/AppList/AppScore.vue10
-rw-r--r--apps/settings/templates/settings/admin/overview.php2
-rw-r--r--apps/systemtags/css/settings.css29
-rw-r--r--apps/systemtags/js/admin.js193
-rw-r--r--apps/systemtags/lib/Settings/Admin.php1
-rw-r--r--apps/systemtags/src/admin.ts32
-rw-r--r--apps/systemtags/src/components/SystemTagForm.vue326
-rw-r--r--apps/systemtags/src/components/SystemTags.vue39
-rw-r--r--apps/systemtags/src/services/api.ts71
-rw-r--r--apps/systemtags/src/services/files.ts82
-rw-r--r--apps/systemtags/src/types.ts2
-rw-r--r--apps/systemtags/src/utils.ts10
-rw-r--r--apps/systemtags/src/views/SystemTagsSection.vue99
-rw-r--r--apps/systemtags/templates/admin.php39
-rw-r--r--apps/theming/l10n/gl.js17
-rw-r--r--apps/theming/l10n/gl.json17
-rw-r--r--apps/user_status/lib/Db/UserStatus.php2
-rw-r--r--apps/user_status/lib/Service/StatusService.php8
-rw-r--r--apps/user_status/tests/Unit/Service/StatusServiceTest.php30
-rw-r--r--apps/workflowengine/lib/Controller/AWorkflowController.php4
-rw-r--r--apps/workflowengine/lib/Controller/UserWorkflowsController.php4
-rw-r--r--apps/workflowengine/src/store.js7
61 files changed, 1259 insertions, 574 deletions
diff --git a/apps/comments/lib/Listener/LoadSidebarScripts.php b/apps/comments/lib/Listener/LoadSidebarScripts.php
index a77cd4e0af3..39c81c03ad1 100644
--- a/apps/comments/lib/Listener/LoadSidebarScripts.php
+++ b/apps/comments/lib/Listener/LoadSidebarScripts.php
@@ -28,6 +28,8 @@ namespace OCA\Comments\Listener;
use OCA\Comments\AppInfo\Application;
use OCA\Files\Event\LoadSidebar;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Services\IInitialState;
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
@@ -36,6 +38,8 @@ use OCP\Util;
class LoadSidebarScripts implements IEventListener {
public function __construct(
private ICommentsManager $commentsManager,
+ private IInitialState $initialState,
+ private IAppManager $appManager,
) {
}
@@ -46,6 +50,8 @@ class LoadSidebarScripts implements IEventListener {
$this->commentsManager->load();
+ $this->initialState->provideInitialState('activityEnabled', $this->appManager->isEnabledForUser('activity'));
+
// TODO: make sure to only include the sidebar script when
// we properly split it between files list and sidebar
Util::addScript(Application::APP_ID, 'comments');
diff --git a/apps/comments/src/comments-activity-tab.ts b/apps/comments/src/comments-activity-tab.ts
new file mode 100644
index 00000000000..0cb3bf70bbb
--- /dev/null
+++ b/apps/comments/src/comments-activity-tab.ts
@@ -0,0 +1,85 @@
+/**
+ * @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
+ *
+ * @author Ferdinand Thiessen <opensource@fthiessen.de>
+ *
+ * @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 moment from '@nextcloud/moment'
+import Vue from 'vue'
+import logger from './logger.js'
+import { getComments } from './services/GetComments.js'
+
+let ActivityTabPluginView
+let ActivityTabPluginInstance
+
+/**
+ * Register the comments plugins for the Activity sidebar
+ */
+export function registerCommentsPlugins() {
+ window.OCA.Activity.registerSidebarAction({
+ mount: async (el, { context, fileInfo, reload }) => {
+ if (!ActivityTabPluginView) {
+ const { default: ActivityCommmentAction } = await import('./views/ActivityCommentAction.vue')
+ ActivityTabPluginView = Vue.extend(ActivityCommmentAction)
+ }
+ ActivityTabPluginInstance = new ActivityTabPluginView({
+ parent: context,
+ propsData: {
+ reloadCallback: reload,
+ resourceId: fileInfo.id,
+ },
+ })
+ ActivityTabPluginInstance.$mount(el)
+ logger.info('Comments plugin mounted in Activity sidebar action', { fileInfo })
+ },
+ unmount: () => {
+ // destroy previous instance if available
+ if (ActivityTabPluginInstance) {
+ ActivityTabPluginInstance.$destroy()
+ }
+ },
+ })
+
+ window.OCA.Activity.registerSidebarEntries(async ({ fileInfo, limit, offset }) => {
+ const { data: comments } = await getComments({ resourceType: 'files', resourceId: fileInfo.id }, { limit, offset })
+ logger.debug('Loaded comments', { fileInfo, comments })
+ const { default: CommentView } = await import('./views/ActivityCommentEntry.vue')
+ const CommentsViewObject = Vue.extend(CommentView)
+
+ return comments.map((comment) => ({
+ timestamp: moment(comment.props.creationDateTime).toDate().getTime(),
+ mount(element, { context, reload }) {
+ this._CommentsViewInstance = new CommentsViewObject({
+ parent: context,
+ propsData: {
+ comment,
+ resourceId: fileInfo.id,
+ reloadCallback: reload,
+ },
+ })
+ this._CommentsViewInstance.$mount(element)
+ },
+ unmount() {
+ this._CommentsViewInstance.$destroy()
+ },
+ }))
+ })
+
+ window.OCA.Activity.registerSidebarFilter((activity) => activity.type !== 'comments')
+ logger.info('Comments plugin registered for Activity sidebar action')
+}
diff --git a/apps/comments/src/comments-tab.js b/apps/comments/src/comments-tab.js
index 121b8d686f4..1a367cc18ee 100644
--- a/apps/comments/src/comments-tab.js
+++ b/apps/comments/src/comments-tab.js
@@ -22,40 +22,53 @@
// eslint-disable-next-line n/no-missing-import, import/no-unresolved
import MessageReplyText from '@mdi/svg/svg/message-reply-text.svg?raw'
+import { getRequestToken } from '@nextcloud/auth'
+import { loadState } from '@nextcloud/initial-state'
+import { registerCommentsPlugins } from './comments-activity-tab.ts'
-// Init Comments tab component
-let TabInstance = null
-const commentTab = new OCA.Files.Sidebar.Tab({
- id: 'comments',
- name: t('comments', 'Comments'),
- iconSvg: MessageReplyText,
+// @ts-expect-error __webpack_nonce__ is injected by webpack
+__webpack_nonce__ = btoa(getRequestToken())
- async mount(el, fileInfo, context) {
- if (TabInstance) {
+if (loadState('comments', 'activityEnabled', false) && OCA?.Activity?.registerSidebarAction !== undefined) {
+ // Do not mount own tab but mount into activity
+ window.addEventListener('DOMContentLoaded', function() {
+ registerCommentsPlugins()
+ })
+} else {
+ // Init Comments tab component
+ let TabInstance = null
+ const commentTab = new OCA.Files.Sidebar.Tab({
+ id: 'comments',
+ name: t('comments', 'Comments'),
+ iconSvg: MessageReplyText,
+
+ async mount(el, fileInfo, context) {
+ if (TabInstance) {
+ TabInstance.$destroy()
+ }
+ TabInstance = new OCA.Comments.View('files', {
+ // Better integration with vue parent component
+ parent: context,
+ })
+ // Only mount after we have all the info we need
+ await TabInstance.update(fileInfo.id)
+ TabInstance.$mount(el)
+ },
+ update(fileInfo) {
+ TabInstance.update(fileInfo.id)
+ },
+ destroy() {
TabInstance.$destroy()
- }
- TabInstance = new OCA.Comments.View('files', {
- // Better integration with vue parent component
- parent: context,
- })
- // Only mount after we have all the info we need
- await TabInstance.update(fileInfo.id)
- TabInstance.$mount(el)
- },
- update(fileInfo) {
- TabInstance.update(fileInfo.id)
- },
- destroy() {
- TabInstance.$destroy()
- TabInstance = null
- },
- scrollBottomReached() {
- TabInstance.onScrollBottomReached()
- },
-})
+ TabInstance = null
+ },
+ scrollBottomReached() {
+ TabInstance.onScrollBottomReached()
+ },
+ })
-window.addEventListener('DOMContentLoaded', function() {
- if (OCA.Files && OCA.Files.Sidebar) {
- OCA.Files.Sidebar.registerTab(commentTab)
- }
-})
+ window.addEventListener('DOMContentLoaded', function() {
+ if (OCA.Files && OCA.Files.Sidebar) {
+ OCA.Files.Sidebar.registerTab(commentTab)
+ }
+ })
+}
diff --git a/apps/comments/src/components/Comment.vue b/apps/comments/src/components/Comment.vue
index e8ae9a88e77..a5b72efb74b 100644
--- a/apps/comments/src/components/Comment.vue
+++ b/apps/comments/src/components/Comment.vue
@@ -111,6 +111,7 @@
<script>
import { getCurrentUser } from '@nextcloud/auth'
+import { translate as t } from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
@@ -235,6 +236,8 @@ export default {
},
methods: {
+ t,
+
/**
* Update local Message on outer change
*
@@ -279,7 +282,7 @@ $comment-padding: 10px;
.comment {
display: flex;
- gap: 16px;
+ gap: 8px;
padding: 5px $comment-padding;
&__side {
diff --git a/apps/comments/src/mixins/CommentMixin.js b/apps/comments/src/mixins/CommentMixin.js
index 545625ab97e..cf93dead9ba 100644
--- a/apps/comments/src/mixins/CommentMixin.js
+++ b/apps/comments/src/mixins/CommentMixin.js
@@ -20,10 +20,11 @@
*
*/
+import { showError, showUndo, TOAST_UNDO_TIMEOUT } from '@nextcloud/dialogs'
import NewComment from '../services/NewComment.js'
import DeleteComment from '../services/DeleteComment.js'
import EditComment from '../services/EditComment.js'
-import { showError, showUndo, TOAST_UNDO_TIMEOUT } from '@nextcloud/dialogs'
+import logger from '../logger.js'
export default {
props: {
@@ -35,10 +36,14 @@ export default {
type: String,
default: '',
},
- ressourceId: {
+ resourceId: {
type: [String, Number],
required: true,
},
+ resourceType: {
+ type: String,
+ default: 'files',
+ },
},
data() {
@@ -62,8 +67,8 @@ export default {
async onEditComment(message) {
this.loading = true
try {
- await EditComment(this.commentsType, this.ressourceId, this.id, message)
- this.logger.debug('Comment edited', { commentsType: this.commentsType, ressourceId: this.ressourceId, id: this.id, message })
+ await EditComment(this.resourceType, this.resourceId, this.id, message)
+ logger.debug('Comment edited', { resourceType: this.resourceType, resourceId: this.resourceId, id: this.id, message })
this.$emit('update:message', message)
this.editing = false
} catch (error) {
@@ -85,8 +90,8 @@ export default {
},
async onDelete() {
try {
- await DeleteComment(this.commentsType, this.ressourceId, this.id)
- this.logger.debug('Comment deleted', { commentsType: this.commentsType, ressourceId: this.ressourceId, id: this.id })
+ await DeleteComment(this.resourceType, this.resourceId, this.id)
+ logger.debug('Comment deleted', { resourceType: this.resourceType, resourceId: this.resourceId, id: this.id })
this.$emit('delete', this.id)
} catch (error) {
showError(t('comments', 'An error occurred while trying to delete the comment'))
@@ -99,8 +104,8 @@ export default {
async onNewComment(message) {
this.loading = true
try {
- const newComment = await NewComment(this.commentsType, this.ressourceId, message)
- this.logger.debug('New comment posted', { commentsType: this.commentsType, ressourceId: this.ressourceId, newComment })
+ const newComment = await NewComment(this.resourceType, this.resourceId, message)
+ logger.debug('New comment posted', { resourceType: this.resourceType, resourceId: this.resourceId, newComment })
this.$emit('new', newComment)
// Clear old content
diff --git a/apps/comments/src/mixins/CommentView.ts b/apps/comments/src/mixins/CommentView.ts
new file mode 100644
index 00000000000..a49e33f7fd5
--- /dev/null
+++ b/apps/comments/src/mixins/CommentView.ts
@@ -0,0 +1,72 @@
+import axios from '@nextcloud/axios'
+import { getCurrentUser } from '@nextcloud/auth'
+import { loadState } from '@nextcloud/initial-state'
+import { generateOcsUrl } from '@nextcloud/router'
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+ props: {
+ resourceId: {
+ type: Number,
+ required: true,
+ },
+ resourceType: {
+ type: String,
+ default: 'files',
+ },
+ },
+ data() {
+ return {
+ editorData: {
+ actorDisplayName: getCurrentUser()!.displayName as string,
+ actorId: getCurrentUser()!.uid as string,
+ key: 'editor',
+ },
+ userData: {},
+ }
+ },
+ methods: {
+ /**
+ * Autocomplete @mentions
+ *
+ * @param {string} search the query
+ * @param {Function} callback the callback to process the results with
+ */
+ async autoComplete(search, callback) {
+ const { data } = await axios.get(generateOcsUrl('core/autocomplete/get'), {
+ params: {
+ search,
+ itemType: 'files',
+ itemId: this.resourceId,
+ sorter: 'commenters|share-recipients',
+ limit: loadState('comments', 'maxAutoCompleteResults'),
+ },
+ })
+ // Save user data so it can be used by the editor to replace mentions
+ data.ocs.data.forEach(user => { this.userData[user.id] = user })
+ return callback(Object.values(this.userData))
+ },
+
+ /**
+ * Make sure we have all mentions as Array of objects
+ *
+ * @param mentions the mentions list
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ genMentionsData(mentions: any[]): Record<string, object> {
+ Object.values(mentions)
+ .flat()
+ .forEach(mention => {
+ this.userData[mention.mentionId] = {
+ // TODO: support groups
+ icon: 'icon-user',
+ id: mention.mentionId,
+ label: mention.mentionDisplayName,
+ source: 'users',
+ primary: getCurrentUser()?.uid === mention.mentionId,
+ }
+ })
+ return this.userData
+ },
+ },
+})
diff --git a/apps/comments/src/services/CommentsInstance.js b/apps/comments/src/services/CommentsInstance.js
index e283e833c34..f71763a5591 100644
--- a/apps/comments/src/services/CommentsInstance.js
+++ b/apps/comments/src/services/CommentsInstance.js
@@ -47,19 +47,18 @@ export default class CommentInstance {
/**
* Initialize a new Comments instance for the desired type
*
- * @param {string} commentsType the comments endpoint type
+ * @param {string} resourceType the comments endpoint type
* @param {object} options the vue options (propsData, parent, el...)
*/
- constructor(commentsType = 'files', options) {
- // Add comments type as a global mixin
- Vue.mixin({
- data() {
- return {
- commentsType,
- }
+ constructor(resourceType = 'files', options = {}) {
+ // Merge options and set `resourceType` property
+ options = {
+ ...options,
+ propsData: {
+ ...(options.propsData ?? {}),
+ resourceType,
},
- })
-
+ }
// Init Comments component
const View = Vue.extend(CommentsApp)
return new View(options)
diff --git a/apps/comments/src/services/DeleteComment.js b/apps/comments/src/services/DeleteComment.js
index 43d53129f72..ecfc0fe8b65 100644
--- a/apps/comments/src/services/DeleteComment.js
+++ b/apps/comments/src/services/DeleteComment.js
@@ -25,12 +25,12 @@ import client from './DavClient.js'
/**
* Delete a comment
*
- * @param {string} commentsType the ressource type
- * @param {number} ressourceId the ressource ID
+ * @param {string} resourceType the resource type
+ * @param {number} resourceId the resource ID
* @param {number} commentId the comment iD
*/
-export default async function(commentsType, ressourceId, commentId) {
- const commentPath = ['', commentsType, ressourceId, commentId].join('/')
+export default async function(resourceType, resourceId, commentId) {
+ const commentPath = ['', resourceType, resourceId, commentId].join('/')
// Fetch newly created comment data
await client.deleteFile(commentPath)
diff --git a/apps/comments/src/services/EditComment.js b/apps/comments/src/services/EditComment.js
index 51d0d4cca65..1462e99d1db 100644
--- a/apps/comments/src/services/EditComment.js
+++ b/apps/comments/src/services/EditComment.js
@@ -25,13 +25,13 @@ import client from './DavClient.js'
/**
* Edit an existing comment
*
- * @param {string} commentsType the ressource type
- * @param {number} ressourceId the ressource ID
+ * @param {string} resourceType the resource type
+ * @param {number} resourceId the resource ID
* @param {number} commentId the comment iD
* @param {string} message the message content
*/
-export default async function(commentsType, ressourceId, commentId, message) {
- const commentPath = ['', commentsType, ressourceId, commentId].join('/')
+export default async function(resourceType, resourceId, commentId, message) {
+ const commentPath = ['', resourceType, resourceId, commentId].join('/')
return await client.customRequest(commentPath, Object.assign({
method: 'PROPPATCH',
diff --git a/apps/comments/src/services/GetComments.ts b/apps/comments/src/services/GetComments.ts
index d74e92bce68..c55cb4ee4a0 100644
--- a/apps/comments/src/services/GetComments.ts
+++ b/apps/comments/src/services/GetComments.ts
@@ -20,7 +20,7 @@
*
*/
-import { parseXML, type DAVResult, type FileStat } from 'webdav'
+import { parseXML, type DAVResult, type FileStat, type ResponseDataDetailed } from 'webdav'
// https://github.com/perry-mitchell/webdav-client/issues/339
import { processResponsePayload } from '../../../../node_modules/webdav/dist/node/response.js'
@@ -33,16 +33,18 @@ export const DEFAULT_LIMIT = 20
* Retrieve the comments list
*
* @param {object} data destructuring object
- * @param {string} data.commentsType the ressource type
- * @param {number} data.ressourceId the ressource ID
+ * @param {string} data.resourceType the resource type
+ * @param {number} data.resourceId the resource ID
* @param {object} [options] optional options for axios
* @param {number} [options.offset] the pagination offset
- * @return {object[]} the comments list
+ * @param {number} [options.limit] the pagination limit, defaults to 20
+ * @param {Date} [options.datetime] optional date to query
+ * @return {{data: object[]}} the comments list
*/
-export const getComments = async function({ commentsType, ressourceId }, options: { offset: number }) {
- const ressourcePath = ['', commentsType, ressourceId].join('/')
-
- const response = await client.customRequest(ressourcePath, Object.assign({
+export const getComments = async function({ resourceType, resourceId }, options: { offset: number, limit?: number, datetime?: Date }) {
+ const resourcePath = ['', resourceType, resourceId].join('/')
+ const datetime = options.datetime ? `<oc:datetime>${options.datetime.toISOString()}</oc:datetime>` : ''
+ const response = await client.customRequest(resourcePath, Object.assign({
method: 'REPORT',
data: `<?xml version="1.0"?>
<oc:filter-comments
@@ -50,15 +52,16 @@ export const getComments = async function({ commentsType, ressourceId }, options
xmlns:oc="http://owncloud.org/ns"
xmlns:nc="http://nextcloud.org/ns"
xmlns:ocs="http://open-collaboration-services.org/ns">
- <oc:limit>${DEFAULT_LIMIT}</oc:limit>
+ <oc:limit>${options.limit ?? DEFAULT_LIMIT}</oc:limit>
<oc:offset>${options.offset || 0}</oc:offset>
+ ${datetime}
</oc:filter-comments>`,
}, options))
const responseData = await response.text()
const result = await parseXML(responseData)
const stat = getDirectoryFiles(result, true)
- return processResponsePayload(response, stat, true)
+ return processResponsePayload(response, stat, true) as ResponseDataDetailed<FileStat[]>
}
// https://github.com/perry-mitchell/webdav-client/blob/8d9694613c978ce7404e26a401c39a41f125f87f/source/operations/directoryContents.ts
diff --git a/apps/comments/src/services/NewComment.js b/apps/comments/src/services/NewComment.js
index a7fb58e32fb..e82f25efe2a 100644
--- a/apps/comments/src/services/NewComment.js
+++ b/apps/comments/src/services/NewComment.js
@@ -29,27 +29,27 @@ import client from './DavClient.js'
/**
* Retrieve the comments list
*
- * @param {string} commentsType the ressource type
- * @param {number} ressourceId the ressource ID
+ * @param {string} resourceType the resource type
+ * @param {number} resourceId the resource ID
* @param {string} message the message
* @return {object} the new comment
*/
-export default async function(commentsType, ressourceId, message) {
- const ressourcePath = ['', commentsType, ressourceId].join('/')
+export default async function(resourceType, resourceId, message) {
+ const resourcePath = ['', resourceType, resourceId].join('/')
- const response = await axios.post(getRootPath() + ressourcePath, {
+ const response = await axios.post(getRootPath() + resourcePath, {
actorDisplayName: getCurrentUser().displayName,
actorId: getCurrentUser().uid,
actorType: 'users',
creationDateTime: (new Date()).toUTCString(),
message,
- objectType: 'files',
+ objectType: resourceType,
verb: 'comment',
})
- // Retrieve comment id from ressource location
+ // Retrieve comment id from resource location
const commentId = parseInt(response.headers['content-location'].split('/').pop())
- const commentPath = ressourcePath + '/' + commentId
+ const commentPath = resourcePath + '/' + commentId
// Fetch newly created comment data
const comment = await client.stat(commentPath, {
diff --git a/apps/comments/src/services/ReadComments.ts b/apps/comments/src/services/ReadComments.ts
index 4c7e44fe2f7..ff29026098a 100644
--- a/apps/comments/src/services/ReadComments.ts
+++ b/apps/comments/src/services/ReadComments.ts
@@ -27,19 +27,19 @@ import type { Response } from 'webdav'
/**
* Mark comments older than the date timestamp as read
*
- * @param commentsType the ressource type
- * @param ressourceId the ressource ID
+ * @param resourceType the resource type
+ * @param resourceId the resource ID
* @param date the date object
*/
export const markCommentsAsRead = (
- commentsType: string,
- ressourceId: number,
+ resourceType: string,
+ resourceId: number,
date: Date,
): Promise<Response> => {
- const ressourcePath = ['', commentsType, ressourceId].join('/')
+ const resourcePath = ['', resourceType, resourceId].join('/')
const readMarker = date.toUTCString()
- return client.customRequest(ressourcePath, {
+ return client.customRequest(resourcePath, {
method: 'PROPPATCH',
data: `<?xml version="1.0"?>
<d:propertyupdate
diff --git a/apps/comments/src/views/ActivityCommentAction.vue b/apps/comments/src/views/ActivityCommentAction.vue
new file mode 100644
index 00000000000..1bdeca883f7
--- /dev/null
+++ b/apps/comments/src/views/ActivityCommentAction.vue
@@ -0,0 +1,71 @@
+<!--
+ - @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
+ -
+ - @author Ferdinand Thiessen <opensource@fthiessen.de>
+ -
+ - @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/>.
+ -
+ -->
+
+<template>
+ <Comment v-bind="editorData"
+ :auto-complete="autoComplete"
+ :resource-type="resourceType"
+ :editor="true"
+ :user-data="userData"
+ :resource-id="resourceId"
+ class="comments-action"
+ @new="onNewComment" />
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+import Comment from '../components/Comment.vue'
+import CommentView from '../mixins/CommentView.js'
+import logger from '../logger'
+import { showError } from '@nextcloud/dialogs'
+import { translate as t } from '@nextcloud/l10n'
+
+export default defineComponent({
+ components: {
+ Comment,
+ },
+ mixins: [CommentView],
+ props: {
+ reloadCallback: {
+ type: Function,
+ required: true,
+ },
+ },
+ methods: {
+ onNewComment() {
+ try {
+ // just force reload
+ this.reloadCallback()
+ } catch (e) {
+ showError(t('comments', 'Could not reload comments'))
+ logger.debug(e)
+ }
+ },
+ },
+})
+</script>
+
+<style scoped>
+.comments-action {
+ padding: 0;
+}
+</style>
diff --git a/apps/comments/src/views/ActivityCommentEntry.vue b/apps/comments/src/views/ActivityCommentEntry.vue
new file mode 100644
index 00000000000..38fc2d5f1ef
--- /dev/null
+++ b/apps/comments/src/views/ActivityCommentEntry.vue
@@ -0,0 +1,87 @@
+<!--
+ - @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
+ -
+ - @author Ferdinand Thiessen <opensource@fthiessen.de>
+ -
+ - @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/>.
+ -
+ -->
+
+<template>
+ <Comment ref="comment"
+ tag="li"
+ v-bind="comment.props"
+ :auto-complete="autoComplete"
+ :resource-type="resourceType"
+ :message="commentMessage"
+ :resource-id="resourceId"
+ :user-data="genMentionsData(comment.props.mentions)"
+ class="comments-activity"
+ @delete="reloadCallback()" />
+</template>
+
+<script lang="ts">
+import { translate as t } from '@nextcloud/l10n'
+
+import Comment from '../components/Comment.vue'
+import CommentView from '../mixins/CommentView'
+
+export default {
+ name: 'ActivityCommentEntry',
+
+ components: {
+ Comment,
+ },
+
+ mixins: [CommentView],
+ props: {
+ comment: {
+ type: Object,
+ required: true,
+ },
+ reloadCallback: {
+ type: Function,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ commentMessage: '',
+ }
+ },
+
+ watch: {
+ comment() {
+ this.commentMessage = this.comment.props.message
+ },
+ },
+
+ mounted() {
+ this.commentMessage = this.comment.props.message
+ },
+
+ methods: {
+ t,
+ },
+}
+</script>
+
+<style scoped>
+.comments-activity {
+ padding: 0;
+}
+</style>
diff --git a/apps/comments/src/views/Comments.vue b/apps/comments/src/views/Comments.vue
index 93e9031df5a..7936610ad12 100644
--- a/apps/comments/src/views/Comments.vue
+++ b/apps/comments/src/views/Comments.vue
@@ -28,9 +28,10 @@
<!-- Editor -->
<Comment v-bind="editorData"
:auto-complete="autoComplete"
- :user-data="userData"
+ :resource-type="resourceType"
:editor="true"
- :ressource-id="ressourceId"
+ :user-data="userData"
+ :resource-id="resourceId"
class="comments__writer"
@new="onNewComment" />
@@ -49,8 +50,9 @@
tag="li"
v-bind="comment.props"
:auto-complete="autoComplete"
+ :resource-type="resourceType"
:message.sync="comment.props.message"
- :ressource-id="ressourceId"
+ :resource-id="resourceId"
:user-data="genMentionsData(comment.props.mentions)"
class="comments__list"
@delete="onDelete" />
@@ -82,11 +84,8 @@
</template>
<script>
-import { generateOcsUrl } from '@nextcloud/router'
-import { getCurrentUser } from '@nextcloud/auth'
-import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
-import axios from '@nextcloud/axios'
+import { translate as t } from '@nextcloud/l10n'
import VTooltip from 'v-tooltip'
import Vue from 'vue'
import VueObserveVisibility from 'vue-observe-visibility'
@@ -101,6 +100,7 @@ import Comment from '../components/Comment.vue'
import { getComments, DEFAULT_LIMIT } from '../services/GetComments.ts'
import cancelableRequest from '../utils/cancelableRequest.js'
import { markCommentsAsRead } from '../services/ReadComments.ts'
+import CommentView from '../mixins/CommentView'
Vue.use(VTooltip)
Vue.use(VueObserveVisibility)
@@ -109,7 +109,6 @@ export default {
name: 'Comments',
components: {
- // Avatar,
Comment,
NcEmptyContent,
NcButton,
@@ -118,24 +117,20 @@ export default {
AlertCircleOutlineIcon,
},
+ mixins: [CommentView],
+
data() {
return {
error: '',
loading: false,
done: false,
- ressourceId: null,
+ resourceId: null,
offset: 0,
comments: [],
cancelRequest: () => {},
- editorData: {
- actorDisplayName: getCurrentUser().displayName,
- actorId: getCurrentUser().uid,
- key: 'editor',
- },
-
Comment,
userData: {},
}
@@ -151,10 +146,12 @@ export default {
},
methods: {
+ t,
+
async onVisibilityChange(isVisible) {
if (isVisible) {
try {
- await markCommentsAsRead(this.commentsType, this.ressourceId, new Date())
+ await markCommentsAsRead(this.resourceType, this.resourceId, new Date())
} catch (e) {
showError(e.message || t('comments', 'Failed to mark comments as read'))
}
@@ -162,12 +159,12 @@ export default {
},
/**
- * Update current ressourceId and fetch new data
+ * Update current resourceId and fetch new data
*
- * @param {number} ressourceId the current ressourceId (fileId...)
+ * @param {number} resourceId the current resourceId (fileId...)
*/
- async update(ressourceId) {
- this.ressourceId = ressourceId
+ async update(resourceId) {
+ this.resourceId = resourceId
this.resetState()
this.getComments()
},
@@ -189,28 +186,6 @@ export default {
},
/**
- * Make sure we have all mentions as Array of objects
- *
- * @param {any[]} mentions the mentions list
- * @return {Record<string, object>}
- */
- genMentionsData(mentions) {
- Object.values(mentions)
- .flat()
- .forEach(mention => {
- this.userData[mention.mentionId] = {
- // TODO: support groups
- icon: 'icon-user',
- id: mention.mentionId,
- label: mention.mentionDisplayName,
- source: 'users',
- primary: getCurrentUser().uid === mention.mentionId,
- }
- })
- return this.userData
- },
-
- /**
* Get the existing shares infos
*/
async getComments() {
@@ -227,8 +202,8 @@ export default {
// Fetch comments
const { data: comments } = await request({
- commentsType: this.commentsType,
- ressourceId: this.ressourceId,
+ resourceType: this.resourceType,
+ resourceId: this.resourceId,
}, { offset: this.offset }) || { data: [] }
this.logger.debug(`Processed ${comments.length} comments`, { comments })
@@ -256,27 +231,6 @@ export default {
},
/**
- * Autocomplete @mentions
- *
- * @param {string} search the query
- * @param {Function} callback the callback to process the results with
- */
- async autoComplete(search, callback) {
- const results = await axios.get(generateOcsUrl('core/autocomplete/get'), {
- params: {
- search,
- itemType: 'files',
- itemId: this.ressourceId,
- sorter: 'commenters|share-recipients',
- limit: loadState('comments', 'maxAutoCompleteResults'),
- },
- })
- // Save user data so it can be used by the editor to replace mentions
- results.data.ocs.data.forEach(user => { this.userData[user.id] = user })
- return callback(Object.values(this.userData))
- },
-
- /**
* Add newly created comment to the list
*
* @param {object} comment the new comment
diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php
index 9a05abed049..8be0bd8050c 100644
--- a/apps/dav/lib/CardDAV/CardDavBackend.php
+++ b/apps/dav/lib/CardDAV/CardDavBackend.php
@@ -1106,7 +1106,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}
/**
- * @param array $addressBookIds
+ * @param int[] $addressBookIds
* @param string $pattern
* @param array $searchProperties
* @param array $options
@@ -1126,19 +1126,11 @@ class CardDavBackend implements BackendInterface, SyncSupport {
string $pattern,
array $searchProperties,
array $options = []): array {
- $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
- $useWildcards = !\array_key_exists('wildcard', $options) || $options['wildcard'] !== false;
-
- $query2 = $this->db->getQueryBuilder();
-
- $addressBookOr = $query2->expr()->orX();
- foreach ($addressBookIds as $addressBookId) {
- $addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
- }
-
- if ($addressBookOr->count() === 0) {
+ if (empty($addressBookIds)) {
return [];
}
+ $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
+ $useWildcards = !\array_key_exists('wildcard', $options) || $options['wildcard'] !== false;
if ($escapePattern) {
$searchProperties = array_filter($searchProperties, function ($property) use ($pattern) {
@@ -1161,9 +1153,10 @@ class CardDavBackend implements BackendInterface, SyncSupport {
return [];
}
+ $query2 = $this->db->getQueryBuilder();
$query2->selectDistinct('cp.cardid')
->from($this->dbCardsPropertiesTable, 'cp')
- ->andWhere($addressBookOr)
+ ->where($query2->expr()->in('cp.addressbookid', $query2->createNamedParameter($addressBookIds, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY))
->andWhere($query2->expr()->in('cp.name', $query2->createNamedParameter($searchProperties, IQueryBuilder::PARAM_STR_ARRAY)));
// No need for like when the pattern is empty
diff --git a/apps/dav/lib/Settings/AvailabilitySettings.php b/apps/dav/lib/Settings/AvailabilitySettings.php
index e2f2fe7cef6..c48ebe0255e 100644
--- a/apps/dav/lib/Settings/AvailabilitySettings.php
+++ b/apps/dav/lib/Settings/AvailabilitySettings.php
@@ -33,6 +33,7 @@ use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\Settings\ISettings;
+use OCP\User\IAvailabilityCoordinator;
use Psr\Log\LoggerInterface;
class AvailabilitySettings implements ISettings {
@@ -44,6 +45,7 @@ class AvailabilitySettings implements ISettings {
IInitialState $initialState,
?string $userId,
private LoggerInterface $logger,
+ private IAvailabilityCoordinator $coordinator,
private AbsenceMapper $absenceMapper) {
$this->config = $config;
$this->initialState = $initialState;
@@ -60,11 +62,7 @@ class AvailabilitySettings implements ISettings {
'no'
)
);
- $hideAbsenceSettings = $this->config->getAppValue(
- Application::APP_ID,
- 'hide_absence_settings',
- 'yes',
- ) === 'yes';
+ $hideAbsenceSettings = !$this->coordinator->isEnabled();
$this->initialState->provideInitialState('hide_absence_settings', $hideAbsenceSettings);
if (!$hideAbsenceSettings) {
try {
diff --git a/apps/files/l10n/sr.js b/apps/files/l10n/sr.js
index a674b6b9c74..0b99d1cae94 100644
--- a/apps/files/l10n/sr.js
+++ b/apps/files/l10n/sr.js
@@ -186,7 +186,7 @@ OC.L10N.register(
"\"{char}\" is not allowed inside a file name." : "„{char}“ није дозвољен каракетер у имену фајла.",
"Name cannot be empty" : "Назив не може бити празан",
"Another entry with the same name already exists" : "Већ постоји ставка са истим именом.",
- "Renamed \"{oldName}\" to \"{newName}\"" : "„{oldName}” је променњено на „{newName}”",
+ "Renamed \"{oldName}\" to \"{newName}\"" : "„{oldName}” је промењено на „{newName}”",
"Could not rename \"{oldName}\", it does not exist any more" : "Не може да се промени име фајла „{fileName}” јер фајл више не постоји",
"The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Назив „{targetName}” се већ користи у директоријуму „{dir}”. Молимо вас да изаберете неко друго име.",
"Could not rename \"{oldName}\"" : "Не може да се промени име фајла „{fileName}”",
diff --git a/apps/files/l10n/sr.json b/apps/files/l10n/sr.json
index 8f671ae71d5..efa0956db20 100644
--- a/apps/files/l10n/sr.json
+++ b/apps/files/l10n/sr.json
@@ -184,7 +184,7 @@
"\"{char}\" is not allowed inside a file name." : "„{char}“ није дозвољен каракетер у имену фајла.",
"Name cannot be empty" : "Назив не може бити празан",
"Another entry with the same name already exists" : "Већ постоји ставка са истим именом.",
- "Renamed \"{oldName}\" to \"{newName}\"" : "„{oldName}” је променњено на „{newName}”",
+ "Renamed \"{oldName}\" to \"{newName}\"" : "„{oldName}” је промењено на „{newName}”",
"Could not rename \"{oldName}\", it does not exist any more" : "Не може да се промени име фајла „{fileName}” јер фајл више не постоји",
"The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Назив „{targetName}” се већ користи у директоријуму „{dir}”. Молимо вас да изаберете неко друго име.",
"Could not rename \"{oldName}\"" : "Не може да се промени име фајла „{fileName}”",
diff --git a/apps/files/src/mixins/filesSorting.ts b/apps/files/src/mixins/filesSorting.ts
index 2ec3d94d67e..0457969c01b 100644
--- a/apps/files/src/mixins/filesSorting.ts
+++ b/apps/files/src/mixins/filesSorting.ts
@@ -47,7 +47,7 @@ export default Vue.extend({
*/
isAscSorting(): boolean {
const sortingDirection = this.getConfig(this.currentView.id)?.sorting_direction
- return sortingDirection === 'asc'
+ return sortingDirection !== 'desc'
},
},
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index 3bc4f27c2a2..0729e8a983a 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -218,6 +218,40 @@ export default Vue.extend({
},
/**
+ * Directory content sorting parameters
+ * Provided by an extra computed property for caching
+ */
+ sortingParameters() {
+ const identifiers = [
+ // 1: Sort favorites first if enabled
+ ...(this.userConfig.sort_favorites_first ? [v => v.attributes?.favorite !== 1] : []),
+ // 2: Sort folders first if sorting by name
+ ...(this.sortingMode === 'basename' ? [v => v.type !== 'folder'] : []),
+ // 3: Use sorting mode if NOT basename (to be able to use displayName too)
+ ...(this.sortingMode !== 'basename' ? [v => v[this.sortingMode]] : []),
+ // 4: Use displayName if available, fallback to name
+ v => v.attributes?.displayName || v.basename,
+ // 5: Finally, use basename if all previous sorting methods failed
+ v => v.basename,
+ ]
+ const orders = [
+ // (for 1): always sort favorites before normal files
+ ...(this.userConfig.sort_favorites_first ? ['asc'] : []),
+ // (for 2): always sort folders before files
+ ...(this.sortingMode === 'basename' ? ['asc'] : []),
+ // (for 3): Reverse if sorting by mtime as mtime higher means edited more recent -> lower
+ ...(this.sortingMode === 'mtime' ? [this.isAscSorting ? 'desc' : 'asc'] : []),
+ // (also for 3 so make sure not to conflict with 2 and 3)
+ ...(this.sortingMode !== 'mtime' && this.sortingMode !== 'basename' ? [this.isAscSorting ? 'asc' : 'desc'] : []),
+ // for 4: use configured sorting direction
+ this.isAscSorting ? 'asc' : 'desc',
+ // for 5: use configured sorting direction
+ this.isAscSorting ? 'asc' : 'desc',
+ ]
+ return [identifiers, orders] as const
+ },
+
+ /**
* The current directory contents.
*/
dirContentsSorted(): Node[] {
@@ -234,24 +268,9 @@ export default Vue.extend({
return this.isAscSorting ? results : results.reverse()
}
- const identifiers = [
- // Sort favorites first if enabled
- ...this.userConfig.sort_favorites_first ? [v => v.attributes?.favorite !== 1] : [],
- // Sort folders first if sorting by name
- ...this.sortingMode === 'basename' ? [v => v.type !== 'folder'] : [],
- // Use sorting mode if NOT basename (to be able to use displayName too)
- ...this.sortingMode !== 'basename' ? [v => v[this.sortingMode]] : [],
- // Use displayName if available, fallback to name
- v => v.attributes?.displayName || v.basename,
- // Finally, use basename if all previous sorting methods failed
- v => v.basename,
- ]
- const orders = new Array(identifiers.length).fill(this.isAscSorting ? 'asc' : 'desc')
-
return orderBy(
[...this.dirContents],
- identifiers,
- orders,
+ ...this.sortingParameters,
)
},
diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue
index 2492b700139..1ff5bbd66b4 100644
--- a/apps/files_sharing/src/views/SharingDetailsTab.vue
+++ b/apps/files_sharing/src/views/SharingDetailsTab.vue
@@ -78,15 +78,20 @@
</div>
<div class="sharingTabDetailsView__advanced-control">
<NcButton type="tertiary"
+ id="advancedSectionAccordionAdvancedControl"
alignment="end-reverse"
+ aria-controls="advancedSectionAccordionAdvanced"
+ :aria-expanded="advancedControlExpandedValue"
@click="advancedSectionAccordionExpanded = !advancedSectionAccordionExpanded">
{{ t('files_sharing', 'Advanced settings') }}
<template #icon>
- <MenuDownIcon />
+ <MenuDownIcon v-if="!advancedSectionAccordionExpanded" />
+ <MenuUpIcon v-else />
</template>
</NcButton>
</div>
- <div v-if="advancedSectionAccordionExpanded" class="sharingTabDetailsView__advanced">
+ <div v-if="advancedSectionAccordionExpanded" id="advancedSectionAccordionAdvanced" class="sharingTabDetailsView__advanced"
+ aria-labelledby="advancedSectionAccordionAdvancedControl" role="region">
<section>
<NcInputField v-if="isPublicShare"
:value.sync="share.label"
@@ -172,24 +177,24 @@
{{ t('files_sharing', 'Delete') }}
</NcCheckboxRadioSwitch>
</section>
+ <div class="sharingTabDetailsView__delete">
+ <NcButton v-if="!isNewShare"
+ :aria-label="t('files_sharing', 'Delete share')"
+ :disabled="false"
+ :readonly="false"
+ type="tertiary"
+ @click.prevent="removeShare">
+ <template #icon>
+ <CloseIcon :size="16" />
+ </template>
+ {{ t('files_sharing', 'Delete share') }}
+ </NcButton>
+ </div>
</section>
</div>
</div>
<div class="sharingTabDetailsView__footer">
- <div class="sharingTabDetailsView__delete">
- <NcButton v-if="!isNewShare"
- :aria-label="t('files_sharing', 'Delete share')"
- :disabled="false"
- :readonly="false"
- type="tertiary"
- @click.prevent="removeShare">
- <template #icon>
- <CloseIcon :size="16" />
- </template>
- {{ t('files_sharing', 'Delete share') }}
- </NcButton>
- </div>
<div class="button-group">
<NcButton @click="$emit('close-sharing-details')">
{{ t('files_sharing', 'Cancel') }}
@@ -226,6 +231,7 @@ import UserIcon from 'vue-material-design-icons/AccountCircleOutline.vue'
import ViewIcon from 'vue-material-design-icons/Eye.vue'
import UploadIcon from 'vue-material-design-icons/Upload.vue'
import MenuDownIcon from 'vue-material-design-icons/MenuDown.vue'
+import MenuUpIcon from 'vue-material-design-icons/MenuUp.vue'
import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue'
import GeneratePassword from '../utils/GeneratePassword.js'
@@ -260,6 +266,7 @@ export default {
UploadIcon,
ViewIcon,
MenuDownIcon,
+ MenuUpIcon,
DotsHorizontalIcon,
},
mixins: [ShareTypes, ShareRequests, SharesMixin],
@@ -644,6 +651,9 @@ export default {
: translatedPermissions[permission].toLocaleLowerCase(getLanguage()))
.join(', ')
},
+ advancedControlExpandedValue() {
+ return this.advancedSectionAccordionExpanded ? 'true' : 'false'
+ }
},
watch: {
setCustomPermissions(isChecked) {
diff --git a/apps/files_versions/lib/Listener/FileEventsListener.php b/apps/files_versions/lib/Listener/FileEventsListener.php
index d90283bacc8..323b92b3920 100644
--- a/apps/files_versions/lib/Listener/FileEventsListener.php
+++ b/apps/files_versions/lib/Listener/FileEventsListener.php
@@ -351,7 +351,7 @@ class FileEventsListener implements IEventListener {
/**
* Retrieve the path relative to the current user root folder.
- * If no user is connected, use the node's owner.
+ * If no user is connected, try to use the node's owner.
*/
private function getPathForNode(Node $node): ?string {
try {
@@ -359,8 +359,12 @@ class FileEventsListener implements IEventListener {
->getUserFolder(\OC_User::getUser())
->getRelativePath($node->getPath());
} catch (\Throwable $ex) {
+ $owner = $node->getOwner();
+ if ($owner === null) {
+ return null;
+ }
return $this->rootFolder
- ->getUserFolder($node->getOwner()->getUid())
+ ->getUserFolder($owner->getUid())
->getRelativePath($node->getPath());
}
}
diff --git a/apps/settings/css/settings.css b/apps/settings/css/settings.css
index 24a198eb65c..5e84beae007 100644
--- a/apps/settings/css/settings.css
+++ b/apps/settings/css/settings.css
@@ -1 +1 @@
-input#openid,input#webdav{width:20em}.clear{clear:both}.nav-icon-personal-settings{background-image:var(--icon-personal-dark)}.nav-icon-security{background-image:var(--icon-toggle-filelist-dark)}.nav-icon-clientsbox{background-image:var(--icon-change-dark)}.nav-icon-federated-cloud{background-image:var(--icon-share-dark)}.nav-icon-second-factor-backup-codes,.nav-icon-ssl-root-certificate{background-image:var(--icon-password-dark)}#personal-settings-avatar-container{display:inline-grid;grid-template-columns:1fr;grid-template-rows:2fr 1fr 2fr;vertical-align:top}.profile-settings-container{display:inline-grid;grid-template-columns:1fr 1fr 1fr}.personal-show-container{width:100%}.personal-settings-setting-box .section{padding:10px 30px}.personal-settings-setting-box .section .headerbar-label{margin-bottom:0}.personal-settings-setting-box .section input[type=text],.personal-settings-setting-box .section input[type=email],.personal-settings-setting-box .section input[type=tel],.personal-settings-setting-box .section input[type=url]{width:100%}.personal-settings-setting-box-profile{grid-row:3/5}.personal-settings-setting-box-detail{grid-row:5}.personal-settings-setting-box-detail--without-profile{grid-row:3}select#timezone{width:100%}#personal-settings{display:grid;padding:20px;max-width:1700px;grid-template-columns:repeat(auto-fill, minmax(min(100%, 300px), 1fr));grid-column-gap:10px}#personal-settings .section{padding:10px 10px;border:0}#personal-settings .section h2{margin-bottom:12px}#personal-settings .section h3>label{font-weight:bold}#personal-settings .personal-info{margin-right:10%;margin-bottom:12px;margin-top:12px}#personal-settings .personal-info[class^=icon-],#personal-settings .personal-info[class*=" icon-"]{background-position:0px 2px;padding-left:30px;opacity:.7}.development-notice{text-align:center}.development-notice a:not(.link-button){text-decoration:underline}.development-notice a:not(.link-button):hover{background-color:var(--color-primary-element-hover)}.link-button{display:inline-block;margin:16px;padding:14px 20px;background-color:var(--color-primary-element);color:#fff;border-radius:var(--border-radius-pill);border:1px solid var(--color-primary-element);box-shadow:0 2px 9px var(--color-box-shadow)}.link-button:active,.link-button:hover,.link-button:focus{color:var(--color-primary-element);background-color:var(--color-primary-element-text);border-color:var(--color-primary-element) !important}.link-button.icon-file{padding-left:48px;background-position:24px}.personal-settings-container{display:inline-grid;grid-template-columns:1fr 1fr 1fr}.personal-settings-container:after{clear:both}.personal-settings-container>div h3{position:relative;display:inline-flex;flex-wrap:nowrap;justify-content:flex-start;width:100%;align-items:center;gap:8px}.personal-settings-container>div h3>label{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.personal-settings-container>div>form span[class^=icon-checkmark],.personal-settings-container>div>form span[class^=icon-error]{position:relative;right:8px;top:-28px;pointer-events:none;float:right}.personal-settings-container .verify{position:relative;left:100%;top:0;height:0}.personal-settings-container .verify img{padding:12px 7px 6px}.personal-settings-container .verify-action{cursor:pointer}.personal-settings-container input:disabled{background-color:#fff;color:#000;border:none;opacity:100}.verification-dialog{display:none;right:-9px;top:40px;width:275px}.verification-dialog p{padding:10px}.verification-dialog .verificationCode{font-family:monospace;display:block;overflow-wrap:break-word}.federation-menu{position:relative;cursor:pointer;width:44px;height:44px;padding:10px;margin:0;background:none;border:none}.federation-menu:hover,.federation-menu:focus{background-color:var(--color-background-hover);border-radius:var(--border-radius-pill)}.federation-menu:hover .icon-federation-menu,.federation-menu:focus .icon-federation-menu{opacity:.8}.federation-menu .icon-federation-menu{padding-left:16px;background-size:16px;background-position:left center;opacity:.3;cursor:inherit}.federation-menu .icon-federation-menu .icon-triangle-s{display:inline-block;vertical-align:middle;cursor:inherit}.federation-menu .federationScopeMenu{top:44px}.federation-menu .federationScopeMenu.popovermenu .menuitem{font-size:12.8px;line-height:1.6em}.federation-menu .federationScopeMenu.popovermenu .menuitem .menuitem-text-detail{opacity:.75}.federation-menu .federationScopeMenu.popovermenu .menuitem.active{box-shadow:inset 2px 0 var(--color-primary-element)}.federation-menu .federationScopeMenu.popovermenu .menuitem.active .menuitem-text{font-weight:bold}.federation-menu .federationScopeMenu.popovermenu .menuitem.disabled{opacity:.5;cursor:default}.federation-menu .federationScopeMenu.popovermenu .menuitem.disabled *{cursor:default}.clientsbox img{height:60px}#sslCertificate tr.expired{background-color:rgba(255,0,0,.5)}#sslCertificate td{padding:5px}#displaynameerror,#displaynamechanged{display:none}input#identity{width:20em}#showWizard{display:inline-block}.msg.success{color:#fff;background-color:#47a447;padding:3px}.msg.error{color:#fff;background-color:#d2322d;padding:3px}table.nostyle label{margin-right:2em}table.nostyle td{padding:.2em 0}#security-password #passwordform{display:flex;flex-wrap:wrap;flex-direction:column;gap:1rem}#security-password #passwordform .input-control{display:flex;flex-wrap:wrap;flex-direction:column}#security-password #passwordform .input-control label{margin-bottom:.5rem}#security-password #passwordform #pass1,#security-password #passwordform .personal-show-container{flex-shrink:1;width:300px;min-width:150px}#security-password #passwordform .personal-show-container #pass2{position:relative;top:.5rem}#security-password #passwordform .personal-show-container .personal-show-label{top:34px !important;margin-right:0;margin-top:0 !important;right:3px}#security-password #passwordform #pass2{width:100%}#security-password #passwordform .password-state{display:inline-block}#security-password #passwordform .strengthify-wrapper{position:absolute;left:0;width:100%;border-radius:0 0 2px 2px;margin-top:5px;overflow:hidden;height:3px}#two-factor-auth h3{margin-top:24px}#two-factor-auth li>div{margin-left:20px}#two-factor-auth .two-factor-provider-settings-icon{width:16px;height:16px;vertical-align:sub;filter:var(--background-invert-if-dark)}.isgroup .groupname{width:85%;display:block;overflow:hidden;text-overflow:ellipsis}.isgroup.active .groupname{width:65%}li.active .delete,li.active .rename{display:block}.app-navigation-entry-utils .delete,.app-navigation-entry-utils .rename{display:none}#usersearchform{position:absolute;top:2px;right:0}#usersearchform input{width:150px}#usersearchform label{font-weight:bold}table.grid{width:100%}table.grid th{height:2em;padding:0 1em 0 0;border-bottom:1px solid var(--color-border);text-align:left;font-weight:normal}table.grid td{border-bottom:1px solid var(--color-border);padding:0 1em 0 0;text-align:left;font-weight:normal}td.name,th.name{padding-left:.8em;min-width:5em;max-width:12em;text-overflow:ellipsis;overflow:hidden}td.password,th.password{padding-left:.8em}td.password>img,th.password>img{visibility:hidden}td.displayName>img,th.displayName>img{visibility:hidden}td.password,td.mailAddress,th.password,th.mailAddress{min-width:5em;max-width:12em;cursor:pointer}td.password span,td.mailAddress span,th.password span,th.mailAddress span{width:90%;display:inline-block;text-overflow:ellipsis;overflow:hidden}td.mailAddress,th.mailAddress{cursor:pointer}td.password>span,th.password>span{margin-right:1.2em;color:#c7c7c7}span.usersLastLoginTooltip{white-space:nowrap}#app-content>svg.app-filter{float:left;height:0;width:0}#app-category-app-bundles{margin-bottom:20px}.appinfo{margin:1em 40px}#app-navigation img{margin-bottom:-3px;margin-right:6px;width:16px}#app-navigation li span.no-icon{padding-left:32px}#app-navigation ul li.active>span.utils .delete,#app-navigation ul li.active>span.utils .rename{display:block}#app-navigation .appwarning{background:#fcc}#app-navigation.appwarning:hover{background:#fbb}#app-navigation .app-external{color:var(--color-text-maxcontrast)}span.version{margin-left:1em;margin-right:1em;color:var(--color-text-maxcontrast)}.app-version{color:var(--color-text-maxcontrast)}.app-level span{color:var(--color-text-maxcontrast);background-color:rgba(0,0,0,0);border:1px solid var(--color-text-maxcontrast);border-radius:var(--border-radius);padding:3px 6px}.app-level a{padding:10px;margin:-6px;white-space:nowrap}.app-level .official{background-position:left center;background-position:5px center;padding-left:25px}.app-level .supported{border-color:var(--color-success);background-position:left center;background-position:5px center;padding-left:25px;color:var(--color-success)}.app-score{position:relative;top:4px;opacity:.5}.app-settings-content #searchresults{display:none}#apps-list.store .section{border:0}#apps-list.store .app-name{display:block;margin:5px 0}#apps-list.store .app-name,#apps-list.store .app-image *{cursor:pointer}#apps-list.store .app-summary{opacity:.7}#apps-list.store .app-image-icon .icon-settings-dark{width:100%;height:150px;background-size:45px;opacity:.5}#apps-list.store .app-score-image{height:14px}#apps-list.store .actions{margin-top:10px}#app-sidebar #app-details-view h2 .icon-settings-dark,#app-sidebar #app-details-view h2 svg{display:inline-block;width:16px;height:16px;margin-right:10px;opacity:.7}#app-sidebar #app-details-view .app-level{clear:right;width:100%}#app-sidebar #app-details-view .app-level .supported,#app-sidebar #app-details-view .app-level .official{vertical-align:top}#app-sidebar #app-details-view .app-level .app-score-image{float:right}#app-sidebar #app-details-view .app-author,#app-sidebar #app-details-view .app-licence{color:var(--color-text-maxcontrast)}#app-sidebar #app-details-view .app-dependencies{margin:10px 0}#app-sidebar #app-details-view .app-description p{margin:10px 0}#app-sidebar #app-details-view .close{position:absolute;top:0;right:0;padding:14px;opacity:.5;z-index:1;width:44px;height:44px}#app-sidebar #app-details-view .actions{display:flex;align-items:center}#app-sidebar #app-details-view .actions .app-groups{padding:5px}#app-sidebar #app-details-view .appslink{text-decoration:underline;margin-right:5px}#app-sidebar #app-details-view .app-level,#app-sidebar #app-details-view .actions,#app-sidebar #app-details-view .documentation,#app-sidebar #app-details-view .app-dependencies,#app-sidebar #app-details-view .app-description{margin:20px 0}@media only screen and (min-width: 1601px){.store .section{width:25%}.with-app-sidebar .store .section{width:33%}}@media only screen and (max-width: 1600px){.store .section{width:25%}.with-app-sidebar .store .section{width:33%}}@media only screen and (max-width: 1400px){.store .section{width:33%}.with-app-sidebar .store .section{width:50%}}@media only screen and (max-width: 900px){.store .section{width:50%}.with-app-sidebar .store .section{width:100%}}@media only screen and (max-width: 1024px){.store .section{width:50%}}@media only screen and (max-width: 480px){.store .section{width:100%}}@media only screen and (max-width: 900px){.apps-list.installed .app-version,.apps-list.installed .app-level{display:none !important}}@media only screen and (max-width: 500px){.apps-list.installed .app-groups{display:none !important}}.section{margin-bottom:0}.section:not(:last-child){border-bottom:1px solid var(--color-border)}.section h2{margin-bottom:22px}.section h2 .icon-info{padding:6px 20px;vertical-align:text-bottom;display:inline-block}.followupsection{display:block;padding:0 30px 30px 30px}.app-image{position:relative;height:150px;opacity:1;overflow:hidden}.app-description-toggle-show,.app-description-toggle-hide{clear:both;padding:7px 0;cursor:pointer;opacity:.5}.app-description-container{clear:both;position:relative;top:7px}.app-description{clear:both}#app-category-1{margin-bottom:18px}#app-category-925{text-transform:capitalize}.app-dependencies{color:#ce3702}.missing-dependencies{list-style:initial;list-style-type:initial;list-style-position:inside}.apps-list{display:flex;flex-wrap:wrap;align-content:flex-start}.apps-list .section{cursor:pointer}.apps-list .app-list-move{transition:transform 1s}.apps-list #app-list-update-all{margin-left:10px}.apps-list .toolbar{height:60px;padding:8px;padding-left:60px;width:100%;background-color:var(--color-main-background);position:sticky;top:0;z-index:1;display:flex;align-items:center}.apps-list.installed{margin-bottom:100px}.apps-list.installed .apps-list-container{display:table;width:100%;height:auto;white-space:normal}.apps-list.installed .section{display:table-row;padding:0;margin:0}.apps-list.installed .section>*{display:table-cell;height:initial;vertical-align:middle;float:none;border-bottom:1px solid var(--color-border);padding:6px;box-sizing:border-box}.apps-list.installed .section>.actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:end}.apps-list.installed .section.selected{background-color:var(--color-background-dark)}.apps-list.installed .groups-enable{margin-top:0}.apps-list.installed .groups-enable label{margin-right:3px}.apps-list.installed .app-image{width:44px;height:auto;text-align:right}.apps-list.installed .app-image-icon svg,.apps-list.installed .app-image-icon .icon-settings-dark{margin-top:5px;width:20px;height:20px;opacity:.5;background-size:cover;display:inline-block}.apps-list.installed .actions{text-align:right}.apps-list.installed .actions .icon-loading-small{display:inline-block;top:4px;margin-right:10px}.apps-list:not(.installed) .app-image-icon svg{position:absolute;bottom:43px;width:64px;height:64px;opacity:.1}.apps-list.hidden{display:none}.apps-list .section{position:relative;flex:0 0 auto}.apps-list .section h2.app-name{display:block;margin:8px 0}.apps-list .section:hover{background-color:var(--color-background-dark)}.apps-list .app-description p{margin:10px 0}.apps-list .app-description ul{list-style:disc}.apps-list .app-description ol{list-style:decimal}.apps-list .app-description ol ol,.apps-list .app-description ol ul{padding-left:15px}.apps-list .app-description>ul,.apps-list .app-description>ol{margin-left:19px}.apps-list .app-description ul ol,.apps-list .app-description ul ul{padding-left:15px}.apps-list .apps-header{position:relative}.apps-list .apps-header div{display:table-cell;height:70px}.apps-list .apps-header h2{padding-left:6px;padding-top:15px;margin-bottom:12px}.apps-list .apps-header h2 .enable{position:relative;top:-1px;margin-left:12px}.apps-list .apps-header h2+.section{margin-top:50px}@media(max-width: 512px){.apps-list.installed .section>.actions{display:table-cell}}#apps-list-search .section h2{margin-bottom:0}#log{white-space:normal;margin-bottom:14px}#lessLog{display:none}table.grid td.date{white-space:nowrap}#log-section p{margin-top:20px}#security-warning-state-ok span,#security-warning-state-warning span,#security-warning-state-failure span,#security-warning-state-loading span{vertical-align:middle}#security-warning-state-ok span.message,#security-warning-state-warning span.message,#security-warning-state-failure span.message,#security-warning-state-loading span.message{padding:12px}#security-warning-state-ok span.icon,#security-warning-state-warning span.icon,#security-warning-state-failure span.icon,#security-warning-state-loading span.icon{width:32px;height:32px;background-position:center center;display:inline-block;border-radius:50%}#security-warning-state-ok span.icon-checkmark-white,#security-warning-state-warning span.icon-checkmark-white,#security-warning-state-failure span.icon-checkmark-white,#security-warning-state-loading span.icon-checkmark-white{background-color:var(--color-success)}#security-warning-state-ok span.icon-error-white,#security-warning-state-warning span.icon-error-white,#security-warning-state-failure span.icon-error-white,#security-warning-state-loading span.icon-error-white{background-color:var(--color-warning)}#security-warning-state-ok span.icon-close-white,#security-warning-state-warning span.icon-close-white,#security-warning-state-failure span.icon-close-white,#security-warning-state-loading span.icon-close-white{background-color:var(--color-error)}#shareAPI.loading>div{display:none}#shareAPI p{padding-bottom:.8em}#shareAPI .indent{padding-left:28px}#shareAPI .double-indent{padding-left:56px}#shareAPI .nocheckbox{padding-left:20px}#shareAPI #s2id_linksExcludedGroups{width:200px !important}#shareApiDefaultPermissionsSection label{margin-right:20px}#fileSharingSettings h3{display:inline-block}#publicShareDisclaimerText{width:calc(100% - 23px);max-width:600px;height:150px;margin-left:20px;box-sizing:border-box}.icon-info{padding:11px 20px;vertical-align:text-bottom;opacity:.5}#two-factor-auth h2,#shareAPI h2,#mail_general_settings h2{display:inline-block}.mail_settings p label:first-child{display:inline-block;width:300px;text-align:right}.mail_settings p select:nth-child(2),.mail_settings p input:not([type=button]){width:143px}@media(max-width: 768px){.mail_settings p label:first-child{width:unset;text-align:left;display:block;margin-top:calc(var(--default-grid-baseline)*2)}}#mail_smtpport{width:60px}.cronlog{margin-left:10px}.status{display:inline-block;height:16px;width:16px;vertical-align:text-bottom}.status.success{border-radius:50%}#selectGroups select{box-sizing:border-box;display:inline-block;height:36px;padding:7px 10px}#log .log-message{word-break:break-all;min-width:180px}span.success{background-color:var(--color-success);border-radius:var(--border-radius)}span.error{background-color:var(--color-error)}span.indeterminate{background-color:var(--color-warning);border-radius:40% 0}doesnotexist:-o-prefocus,.strengthify-wrapper{left:185px;width:129px}.trusted-domain-warning{color:#fff;padding:5px;background:#ce3702;border-radius:5px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace}#postsetupchecks ul{margin-left:44px;list-style:disc}#postsetupchecks ul li{margin:10px 0}#postsetupchecks ul ul{list-style:circle}#postsetupchecks .loading{height:50px;background-position:left center}#postsetupchecks .errors,#postsetupchecks .errors a{color:var(--color-error)}#postsetupchecks .warnings,#postsetupchecks .warnings a{color:var(--color-warning)}#postsetupchecks .hint{margin:20px 0}#security-warning a{text-decoration:underline}#security-warning .extra-top-margin{margin-top:12px}.security-warning__heading{display:flex;flex-wrap:wrap;margin-bottom:calc(var(--default-grid-baseline)*8)}.security-warning__heading>h2{margin-bottom:0px}.security-warning__heading>a{width:44px}#admin-tips li{list-style:initial}#admin-tips li a{display:inline-block;padding:3px 0}#warning{color:red}.settings-hint{margin-top:-12px;margin-bottom:12px;opacity:.7}.animated{animation:blink-animation 1s steps(5, start) 4}@keyframes blink-animation{to{opacity:.6}}@-webkit-keyframes blink-animation{to{opacity:1}}/*# sourceMappingURL=settings.css.map */
+input#openid,input#webdav{width:20em}.clear{clear:both}.nav-icon-personal-settings{background-image:var(--icon-personal-dark)}.nav-icon-security{background-image:var(--icon-toggle-filelist-dark)}.nav-icon-clientsbox{background-image:var(--icon-change-dark)}.nav-icon-federated-cloud{background-image:var(--icon-share-dark)}.nav-icon-second-factor-backup-codes,.nav-icon-ssl-root-certificate{background-image:var(--icon-password-dark)}#personal-settings-avatar-container{display:inline-grid;grid-template-columns:1fr;grid-template-rows:2fr 1fr 2fr;vertical-align:top}.profile-settings-container{display:inline-grid;grid-template-columns:1fr 1fr 1fr}.personal-show-container{width:100%}.personal-settings-setting-box .section{padding:10px 30px}.personal-settings-setting-box .section .headerbar-label{margin-bottom:0}.personal-settings-setting-box .section input[type=text],.personal-settings-setting-box .section input[type=email],.personal-settings-setting-box .section input[type=tel],.personal-settings-setting-box .section input[type=url]{width:100%}.personal-settings-setting-box-profile{grid-row:3/5}.personal-settings-setting-box-detail{grid-row:5}.personal-settings-setting-box-detail--without-profile{grid-row:3}select#timezone{width:100%}#personal-settings{display:grid;padding:20px;max-width:1700px;grid-template-columns:repeat(auto-fill, minmax(min(100%, 300px), 1fr));grid-column-gap:10px}#personal-settings .section{padding:10px 10px;border:0}#personal-settings .section h2{margin-bottom:12px}#personal-settings .section h3>label{font-weight:bold}#personal-settings .personal-info{margin-right:10%;margin-bottom:12px;margin-top:12px}#personal-settings .personal-info[class^=icon-],#personal-settings .personal-info[class*=" icon-"]{background-position:0px 2px;padding-left:30px;opacity:.7}.development-notice{text-align:center}.development-notice a:not(.link-button){text-decoration:underline}.development-notice a:not(.link-button):hover{background-color:var(--color-primary-element-hover)}.link-button{display:inline-block;margin:16px;padding:14px 20px;background-color:var(--color-primary-element);color:#fff;border-radius:var(--border-radius-pill);border:1px solid var(--color-primary-element);box-shadow:0 2px 9px var(--color-box-shadow)}.link-button:active,.link-button:hover,.link-button:focus{color:var(--color-primary-element);background-color:var(--color-primary-element-text);border-color:var(--color-primary-element) !important}.link-button.icon-file{padding-left:48px;background-position:24px}.personal-settings-container{display:inline-grid;grid-template-columns:1fr 1fr 1fr}.personal-settings-container:after{clear:both}.personal-settings-container>div h3{position:relative;display:inline-flex;flex-wrap:nowrap;justify-content:flex-start;width:100%;align-items:center;gap:8px}.personal-settings-container>div h3>label{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.personal-settings-container>div>form span[class^=icon-checkmark],.personal-settings-container>div>form span[class^=icon-error]{position:relative;right:8px;top:-28px;pointer-events:none;float:right}.personal-settings-container .verify{position:relative;left:100%;top:0;height:0}.personal-settings-container .verify img{padding:12px 7px 6px}.personal-settings-container .verify-action{cursor:pointer}.personal-settings-container input:disabled{background-color:#fff;color:#000;border:none;opacity:100}.verification-dialog{display:none;right:-9px;top:40px;width:275px}.verification-dialog p{padding:10px}.verification-dialog .verificationCode{font-family:monospace;display:block;overflow-wrap:break-word}.federation-menu{position:relative;cursor:pointer;width:44px;height:44px;padding:10px;margin:0;background:none;border:none}.federation-menu:hover,.federation-menu:focus{background-color:var(--color-background-hover);border-radius:var(--border-radius-pill)}.federation-menu:hover .icon-federation-menu,.federation-menu:focus .icon-federation-menu{opacity:.8}.federation-menu .icon-federation-menu{padding-left:16px;background-size:16px;background-position:left center;opacity:.3;cursor:inherit}.federation-menu .icon-federation-menu .icon-triangle-s{display:inline-block;vertical-align:middle;cursor:inherit}.federation-menu .federationScopeMenu{top:44px}.federation-menu .federationScopeMenu.popovermenu .menuitem{font-size:12.8px;line-height:1.6em}.federation-menu .federationScopeMenu.popovermenu .menuitem .menuitem-text-detail{opacity:.75}.federation-menu .federationScopeMenu.popovermenu .menuitem.active{box-shadow:inset 2px 0 var(--color-primary-element)}.federation-menu .federationScopeMenu.popovermenu .menuitem.active .menuitem-text{font-weight:bold}.federation-menu .federationScopeMenu.popovermenu .menuitem.disabled{opacity:.5;cursor:default}.federation-menu .federationScopeMenu.popovermenu .menuitem.disabled *{cursor:default}.clientsbox img{height:60px}#sslCertificate tr.expired{background-color:rgba(255,0,0,.5)}#sslCertificate td{padding:5px}#displaynameerror,#displaynamechanged{display:none}input#identity{width:20em}#showWizard{display:inline-block}.msg.success{color:#fff;background-color:#47a447;padding:3px}.msg.error{color:#fff;background-color:#d2322d;padding:3px}table.nostyle label{margin-right:2em}table.nostyle td{padding:.2em 0}#security-password #passwordform{display:flex;flex-wrap:wrap;flex-direction:column;gap:1rem}#security-password #passwordform .input-control{display:flex;flex-wrap:wrap;flex-direction:column}#security-password #passwordform .input-control label{margin-bottom:.5rem}#security-password #passwordform #pass1,#security-password #passwordform .personal-show-container{flex-shrink:1;width:300px;min-width:150px}#security-password #passwordform .personal-show-container #pass2{position:relative;top:.5rem}#security-password #passwordform .personal-show-container .personal-show-label{top:34px !important;margin-right:0;margin-top:0 !important;right:3px}#security-password #passwordform #pass2{width:100%}#security-password #passwordform .password-state{display:inline-block}#security-password #passwordform .strengthify-wrapper{position:absolute;left:0;width:100%;border-radius:0 0 2px 2px;margin-top:5px;overflow:hidden;height:3px}#two-factor-auth h3{margin-top:24px}#two-factor-auth li>div{margin-left:20px}#two-factor-auth .two-factor-provider-settings-icon{width:16px;height:16px;vertical-align:sub;filter:var(--background-invert-if-dark)}.isgroup .groupname{width:85%;display:block;overflow:hidden;text-overflow:ellipsis}.isgroup.active .groupname{width:65%}li.active .delete,li.active .rename{display:block}.app-navigation-entry-utils .delete,.app-navigation-entry-utils .rename{display:none}#usersearchform{position:absolute;top:2px;right:0}#usersearchform input{width:150px}#usersearchform label{font-weight:bold}table.grid{width:100%}table.grid th{height:2em;padding:0 1em 0 0;border-bottom:1px solid var(--color-border);text-align:left;font-weight:normal}table.grid td{border-bottom:1px solid var(--color-border);padding:0 1em 0 0;text-align:left;font-weight:normal}td.name,th.name{padding-left:.8em;min-width:5em;max-width:12em;text-overflow:ellipsis;overflow:hidden}td.password,th.password{padding-left:.8em}td.password>img,th.password>img{visibility:hidden}td.displayName>img,th.displayName>img{visibility:hidden}td.password,td.mailAddress,th.password,th.mailAddress{min-width:5em;max-width:12em;cursor:pointer}td.password span,td.mailAddress span,th.password span,th.mailAddress span{width:90%;display:inline-block;text-overflow:ellipsis;overflow:hidden}td.mailAddress,th.mailAddress{cursor:pointer}td.password>span,th.password>span{margin-right:1.2em;color:#c7c7c7}span.usersLastLoginTooltip{white-space:nowrap}#app-content>svg.app-filter{float:left;height:0;width:0}#app-category-app-bundles{margin-bottom:20px}.appinfo{margin:1em 40px}#app-navigation img{margin-bottom:-3px;margin-right:6px;width:16px}#app-navigation li span.no-icon{padding-left:32px}#app-navigation ul li.active>span.utils .delete,#app-navigation ul li.active>span.utils .rename{display:block}#app-navigation .appwarning{background:#fcc}#app-navigation.appwarning:hover{background:#fbb}#app-navigation .app-external{color:var(--color-text-maxcontrast)}span.version{margin-left:1em;margin-right:1em;color:var(--color-text-maxcontrast)}.app-version{color:var(--color-text-maxcontrast)}.app-level span{color:var(--color-text-maxcontrast);background-color:rgba(0,0,0,0);border:1px solid var(--color-text-maxcontrast);border-radius:var(--border-radius);padding:3px 6px}.app-level a{padding:10px;margin:-6px;white-space:nowrap}.app-level .official{background-position:left center;background-position:5px center;padding-left:25px}.app-level .supported{border-color:var(--color-success);background-position:left center;background-position:5px center;padding-left:25px;color:var(--color-success)}.app-score{position:relative;top:4px;opacity:.5}.app-settings-content #searchresults{display:none}#apps-list.store .section{border:0}#apps-list.store .app-name{display:block;margin:5px 0}#apps-list.store .app-image-icon .icon-settings-dark{width:100%;height:150px;background-size:45px;opacity:.5}#apps-list.store .app-score-image{height:14px}#apps-list.store .actions{margin-top:10px}#apps-list.store .actions button{margin:10px 0}#app-sidebar #app-details-view h2 .icon-settings-dark,#app-sidebar #app-details-view h2 svg{display:inline-block;width:16px;height:16px;margin-right:10px;opacity:.7}#app-sidebar #app-details-view .app-level{clear:right;width:100%}#app-sidebar #app-details-view .app-level .supported,#app-sidebar #app-details-view .app-level .official{vertical-align:top}#app-sidebar #app-details-view .app-level .app-score-image{float:right}#app-sidebar #app-details-view .app-author,#app-sidebar #app-details-view .app-licence{color:var(--color-text-maxcontrast)}#app-sidebar #app-details-view .app-dependencies{margin:10px 0}#app-sidebar #app-details-view .app-description p{margin:10px 0}#app-sidebar #app-details-view .close{position:absolute;top:0;right:0;padding:14px;opacity:.5;z-index:1;width:44px;height:44px}#app-sidebar #app-details-view .actions{display:flex;align-items:center}#app-sidebar #app-details-view .actions .app-groups{padding:5px}#app-sidebar #app-details-view .appslink{text-decoration:underline;margin-right:5px}#app-sidebar #app-details-view .app-level,#app-sidebar #app-details-view .actions,#app-sidebar #app-details-view .documentation,#app-sidebar #app-details-view .app-dependencies,#app-sidebar #app-details-view .app-description{margin:20px 0}@media only screen and (min-width: 1601px){.store .section{width:25%}.with-app-sidebar .store .section{width:33%}}@media only screen and (max-width: 1600px){.store .section{width:25%}.with-app-sidebar .store .section{width:33%}}@media only screen and (max-width: 1400px){.store .section{width:33%}.with-app-sidebar .store .section{width:50%}}@media only screen and (max-width: 900px){.store .section{width:50%}.with-app-sidebar .store .section{width:100%}}@media only screen and (max-width: 1024px){.store .section{width:50%}}@media only screen and (max-width: 480px){.store .section{width:100%}}@media only screen and (max-width: 900px){.apps-list.installed .app-version,.apps-list.installed .app-level{display:none !important}}@media only screen and (max-width: 500px){.apps-list.installed .app-groups{display:none !important}}.section{margin-bottom:0}.section:not(:last-child){border-bottom:1px solid var(--color-border)}.section h2{margin-bottom:22px}.section h2 .icon-info{padding:6px 20px;vertical-align:text-bottom;display:inline-block}.followupsection{display:block;padding:0 30px 30px 30px}.app-image{position:relative;height:150px;opacity:1;overflow:hidden}.app-description-toggle-show,.app-description-toggle-hide{clear:both;padding:7px 0;cursor:pointer;opacity:.5}.app-description-container{clear:both;position:relative;top:7px}.app-description{clear:both}#app-category-1{margin-bottom:18px}#app-category-925{text-transform:capitalize}.app-dependencies{color:#ce3702}.missing-dependencies{list-style:initial;list-style-type:initial;list-style-position:inside}.apps-list{display:flex;flex-wrap:wrap;align-content:flex-start}.apps-list .app-list-move{transition:transform 1s}.apps-list #app-list-update-all{margin-left:10px}.apps-list .toolbar{height:60px;padding:8px;padding-left:60px;width:100%;background-color:var(--color-main-background);position:sticky;top:0;z-index:1;display:flex;align-items:center}.apps-list.installed{margin-bottom:100px}.apps-list.installed .apps-list-container{display:table;width:100%;height:auto;white-space:normal}.apps-list.installed .section{display:table-row;padding:0;margin:0}.apps-list.installed .section>*{display:table-cell;height:initial;vertical-align:middle;float:none;border-bottom:1px solid var(--color-border);padding:6px;box-sizing:border-box}.apps-list.installed .section>.actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:end}.apps-list.installed .section.selected{background-color:var(--color-background-dark)}.apps-list.installed .groups-enable{margin-top:0}.apps-list.installed .groups-enable label{margin-right:3px}.apps-list.installed .app-image{width:44px;height:auto;text-align:right}.apps-list.installed .app-image-icon svg,.apps-list.installed .app-image-icon .icon-settings-dark{margin-top:5px;width:20px;height:20px;opacity:.5;background-size:cover;display:inline-block}.apps-list.installed .actions{text-align:right}.apps-list.installed .actions .icon-loading-small{display:inline-block;top:4px;margin-right:10px}.apps-list:not(.installed) .app-image-icon svg{position:absolute;bottom:43px;width:64px;height:64px;opacity:.1}.apps-list.hidden{display:none}.apps-list .section{position:relative;flex:0 0 auto}.apps-list .section h2.app-name{display:block;margin:8px 0}.apps-list .section:hover{background-color:var(--color-background-dark)}.apps-list .app-description p{margin:10px 0}.apps-list .app-description ul{list-style:disc}.apps-list .app-description ol{list-style:decimal}.apps-list .app-description ol ol,.apps-list .app-description ol ul{padding-left:15px}.apps-list .app-description>ul,.apps-list .app-description>ol{margin-left:19px}.apps-list .app-description ul ol,.apps-list .app-description ul ul{padding-left:15px}.apps-list .apps-header{position:relative}.apps-list .apps-header div{display:table-cell;height:70px}.apps-list .apps-header h2{padding-left:6px;padding-top:15px;margin-bottom:12px}.apps-list .apps-header h2 .enable{position:relative;top:-1px;margin-left:12px}.apps-list .apps-header h2+.section{margin-top:50px}@media(max-width: 512px){.apps-list.installed .section>.actions{display:table-cell}}#apps-list-search .section h2{margin-bottom:0}#log{white-space:normal;margin-bottom:14px}#lessLog{display:none}table.grid td.date{white-space:nowrap}#log-section p{margin-top:20px}#security-warning-state-ok span,#security-warning-state-warning span,#security-warning-state-failure span,#security-warning-state-loading span{vertical-align:middle}#security-warning-state-ok span.message,#security-warning-state-warning span.message,#security-warning-state-failure span.message,#security-warning-state-loading span.message{padding:12px}#security-warning-state-ok span.icon,#security-warning-state-warning span.icon,#security-warning-state-failure span.icon,#security-warning-state-loading span.icon{width:32px;height:32px;background-position:center center;display:inline-block;border-radius:50%}#security-warning-state-ok span.icon-checkmark-white,#security-warning-state-warning span.icon-checkmark-white,#security-warning-state-failure span.icon-checkmark-white,#security-warning-state-loading span.icon-checkmark-white{background-color:var(--color-success)}#security-warning-state-ok span.icon-error-white,#security-warning-state-warning span.icon-error-white,#security-warning-state-failure span.icon-error-white,#security-warning-state-loading span.icon-error-white{background-color:var(--color-warning)}#security-warning-state-ok span.icon-close-white,#security-warning-state-warning span.icon-close-white,#security-warning-state-failure span.icon-close-white,#security-warning-state-loading span.icon-close-white{background-color:var(--color-error)}#shareAPI.loading>div{display:none}#shareAPI p{padding-bottom:.8em}#shareAPI .indent{padding-left:28px}#shareAPI .double-indent{padding-left:56px}#shareAPI .nocheckbox{padding-left:20px}#shareAPI #s2id_linksExcludedGroups{width:200px !important}#shareApiDefaultPermissionsSection label{margin-right:20px}#fileSharingSettings h3{display:inline-block}#publicShareDisclaimerText{width:calc(100% - 23px);max-width:600px;height:150px;margin-left:20px;box-sizing:border-box}.icon-info{padding:11px 20px;vertical-align:text-bottom;opacity:.5}#two-factor-auth h2,#shareAPI h2,#mail_general_settings h2{display:inline-block}.mail_settings p label:first-child{display:inline-block;width:300px;text-align:right}.mail_settings p select:nth-child(2),.mail_settings p input:not([type=button]){width:143px}@media(max-width: 768px){.mail_settings p label:first-child{width:unset;text-align:left;display:block;margin-top:calc(var(--default-grid-baseline)*2)}}#mail_smtpport{width:60px}.cronlog{margin-left:10px}.status{display:inline-block;height:16px;width:16px;vertical-align:text-bottom}.status.success{border-radius:50%}#selectGroups select{box-sizing:border-box;display:inline-block;height:36px;padding:7px 10px}#log .log-message{word-break:break-all;min-width:180px}span.success{background-color:var(--color-success);border-radius:var(--border-radius)}span.error{background-color:var(--color-error)}span.indeterminate{background-color:var(--color-warning);border-radius:40% 0}doesnotexist:-o-prefocus,.strengthify-wrapper{left:185px;width:129px}.trusted-domain-warning{color:#fff;padding:5px;background:#ce3702;border-radius:5px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace}#postsetupchecks ul{margin-left:44px;list-style:disc}#postsetupchecks ul li{margin:10px 0}#postsetupchecks ul ul{list-style:circle}#postsetupchecks .loading{height:50px;background-position:left center}#postsetupchecks .errors,#postsetupchecks .errors a{color:var(--color-error)}#postsetupchecks .warnings,#postsetupchecks .warnings a{color:var(--color-warning)}#postsetupchecks .hint{margin:20px 0}#security-warning a{text-decoration:underline}#security-warning .extra-top-margin{margin-top:12px}.security-warning__heading{display:flex;flex-wrap:wrap;margin-bottom:calc(var(--default-grid-baseline)*8)}.security-warning__heading>h2{margin-bottom:0px}.security-warning__heading>a{width:44px}#admin-tips li{list-style:initial}#admin-tips li a{display:inline-block;padding:3px 0}#warning{color:red}.settings-hint{margin-top:-12px;margin-bottom:12px;opacity:.7}.animated{animation:blink-animation 1s steps(5, start) 4}@keyframes blink-animation{to{opacity:.6}}@-webkit-keyframes blink-animation{to{opacity:1}}/*# sourceMappingURL=settings.css.map */
diff --git a/apps/settings/css/settings.css.map b/apps/settings/css/settings.css.map
index 9b146c4c1fa..87079582632 100644
--- a/apps/settings/css/settings.css.map
+++ b/apps/settings/css/settings.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["settings.scss","../../../core/css/functions.scss"],"names":[],"mappings":"AAQC,0BACC,WAKF,OACC,WAID,4BC8CC,2CD1CD,mBC0CC,kDDtCD,qBCsCC,yCDlCD,0BCkCC,wCD9BD,oEC8BC,2CD1BD,oCACC,oBACA,0BACA,+BACA,mBAGD,4BACC,oBACA,kCAGD,yBACC,WAIA,wCACC,kBACA,yDACC,gBAIA,mOACC,WAKH,uCACC,aAGD,sCACC,WAED,uDACC,WAKD,gBACC,WAIF,mBACC,aACA,aACA,iBACA,uEACA,qBAEA,4BACC,kBACA,SAEA,+BACC,mBAIA,qCACC,iBAKH,kCACC,iBACA,mBACA,gBAGD,mGACC,4BACA,kBACA,WAMF,oBACC,kBACA,wCACC,0BACA,8CACC,oDAKH,aACC,qBACA,YACA,kBACA,8CACA,WACA,wCACA,8CACA,6CAEA,0DAGC,mCACA,mDACA,qDAGD,uBACC,kBACA,yBAIF,6BACC,oBACA,kCAEA,mCACC,WAIA,oCACC,kBACA,oBACA,iBACA,2BACA,WACA,mBACA,QAEA,0CACC,mBACA,uBACA,gBAKD,gIACC,kBACA,UACA,UACA,oBACA,YAKH,qCACC,kBACA,UACA,MACA,SAEA,yCACC,qBAIF,4CACC,eAGD,4CACC,sBACA,WACA,YACA,YAMF,qBACC,aACA,WACA,SACA,YAEA,uBACC,aAGD,uCACC,sBACA,cACA,yBAIF,iBACC,kBACA,eACA,WACA,YACA,aACA,SACA,gBACA,YAEA,8CAEC,+CACA,wCAEA,0FACC,WAIF,uCACC,kBACA,qBACA,gCACA,WACA,eAEA,wDACC,qBACA,sBACA,eAIF,sCACC,SAGC,4DAEC,iBACA,kBAEA,kFACC,YAGD,mEACC,oDAEA,kFACC,iBAIF,qEACC,WAEA,eAEA,uEACC,eAQN,gBACC,YAIA,2BACC,kCAGD,mBACC,YAIF,sCAEC,aAGD,eACC,WAGD,YACC,qBAIA,aACC,WACA,yBACA,YAGD,WACC,WACA,yBACA,YAMD,oBACC,iBAGD,iBACC,eAKD,iCACC,aACA,eACA,sBACA,SACA,gDACC,aACA,eACA,sBACA,sDACC,oBAIF,kGACC,cACA,YACA,gBAKA,iEACC,kBACA,UAED,+EACC,oBACA,eACA,wBACA,UAIF,wCACC,WAGD,iDACC,qBAGD,sDACC,kBACA,OACA,WACA,0BACA,eACA,gBACA,WAQF,oBACC,gBAGD,wBACC,iBAGD,oDACC,WACA,YACA,mBACA,wCAOD,oBACC,UACA,cACA,gBACA,uBAGD,2BACC,UAKD,oCAEC,cAKD,wEAEC,aAIF,gBACC,kBACA,QACA,QAEA,sBACC,YAGD,sBACC,iBAKF,WACC,WAEA,cACC,WACA,kBACA,4CACA,gBACA,mBAGD,cACC,4CACA,kBACA,gBACA,mBAKD,gBACC,kBACA,cACA,eACA,uBACA,gBAGD,wBACC,kBAEA,gCACC,kBAIF,sCACC,kBAGD,sDAEC,cACA,eACA,eAEA,0EACC,UACA,qBACA,uBACA,gBAIF,8BACC,eAGD,kCACC,mBACA,cAIF,2BACC,mBAID,4BACC,WACA,SACA,QAGD,0BACC,mBAGD,SACC,gBAKA,oBACC,mBACA,iBACA,WAGD,gCACC,kBAIA,gGACC,cAIF,4BACC,gBAGD,iCACC,gBAGD,8BACC,oCAIF,aACC,gBACA,iBACA,oCAGD,aACC,oCAIA,gBACC,oCACA,+BACA,+CACA,mCACA,gBAGD,aACC,aACA,YACA,mBAGD,qBACC,gCACA,+BACA,kBAGD,sBACC,kCACA,gCACA,+BACA,kBACA,2BAIF,WACC,kBACA,QACA,WAIA,qCACC,aAMD,0BACC,SAGD,2BACC,cACA,aAGD,yDACC,eAGD,8BACC,WAGD,qDACC,WACA,aACA,qBACA,WAGD,kCACC,YAGD,0BACC,gBAMA,4FAEC,qBACA,WACA,YACA,kBACA,WAIF,0CACC,YACA,WAEA,yGAEC,mBAGD,2DACC,YAIF,uFACC,oCAGD,iDACC,cAGD,kDACC,cAGD,sCACC,kBACA,MACA,QACA,aACA,WACA,UACA,WACA,YAGD,wCACC,aACA,mBAEA,oDACC,YAIF,yCACC,0BACA,iBAGD,iOAKC,cAIF,2CACC,gBACC,UAED,kCACC,WAIF,2CACC,gBACC,UAED,kCACC,WAIF,2CACC,gBACC,UAED,kCACC,WAIF,0CACC,gBACC,UAED,kCACC,YAIF,2CACC,gBACC,WAIF,0CACC,gBACC,YAKF,0CAEE,kEACC,yBAKH,0CACC,iCACC,yBAIF,SACC,gBAEA,0BACC,4CAID,YACC,mBAEA,uBACC,iBACA,2BACA,qBAKH,iBACC,cACA,yBAGD,WACC,kBACA,aACA,UACA,gBAGD,0DACC,WACA,cACA,eACA,WAGD,2BACC,WACA,kBACA,QAGD,iBACC,WAGD,gBACC,mBAKD,kBACC,0BAGD,kBACC,cAGD,sBACC,mBACA,wBACA,2BAGD,WAgHC,aACA,eACA,yBA9GA,oBACC,eAGD,0BACC,wBAGD,gCACC,iBAGD,oBACC,OAfgB,KAgBhB,QAjBiB,IAmBjB,aAlBgB,KAmBhB,WACA,8CACA,gBACA,MACA,UACA,aACA,mBAGD,qBAQC,oBAPA,0CACC,cACA,WACA,YACA,mBAKD,8BACC,kBACA,UACA,SAEA,gCACC,mBACA,eACA,sBACA,WACA,4CACA,YACA,sBAGD,uCACC,aACA,QACA,eACA,oBAGD,uCACC,8CAKF,oCACC,aAEA,0CACC,iBAIF,gCACC,WACA,YACA,iBAGD,kGAEC,eACA,WACA,YACA,WACA,sBACA,qBAGD,8BACC,iBAEA,kDACC,qBACA,QACA,kBAKH,+CACC,kBACA,YAEA,WACA,YACA,WAOD,kBACC,aAGD,oBACC,kBACA,cAEA,gCACC,cACA,aAGD,0BACC,8CAKD,8BACC,cAGD,+BACC,gBAGD,+BACC,mBAEA,oEACC,kBAKD,8DACC,iBAKD,oEACC,kBAMH,wBACC,kBAEA,4BACC,mBACA,YAGD,2BACC,iBACA,iBACA,mBACA,mCACC,kBACA,SACA,iBAGD,oCACC,gBAOJ,yBACC,uCACC,oBAMA,8BACC,gBAMH,KACC,mBACA,mBAGD,SACC,aAGD,mBACC,mBAGD,eACC,gBAOA,+IACC,sBAEA,+KACC,aAGD,mKACC,WACA,YACA,kCACA,qBACA,kBAGD,mOACC,sCAGD,mNACC,sCAGD,mNACC,oCAMF,sBACC,aAGD,YACC,oBAGD,kBACC,kBAGD,yBACC,kBAGD,sBACC,kBAGD,oCACC,uBAIF,yCACC,kBAGD,wBACC,qBAGD,2BACC,wBAEA,gBACA,aACA,iBACA,sBAKD,WACC,kBACA,2BACA,WAGD,2DAGC,qBAIA,mCACC,qBACA,YACA,iBAGD,+EAEC,YAIF,yBACC,mCACC,YACA,gBACA,cACA,iDAIF,eACC,WAGD,SACC,iBAGD,QACC,qBACA,YACA,WACA,2BAEA,gBACC,kBAIF,qBACC,sBACA,qBACA,YACA,iBAGD,kBACC,qBACA,gBAIA,aACC,sCACA,mCAGD,WACC,oCAGD,mBACC,sCACA,oBAMF,8CACC,WACA,YAGD,wBACC,WACA,YACA,mBACA,kBACA,+DAIA,oBACC,iBACA,gBAEA,uBACC,cAGD,uBACC,kBAIF,0BACC,YACA,gCAGD,oDACC,yBAGD,wDACC,2BAGD,uBACC,cAKD,oBACC,0BAGD,oCACC,gBAIF,2BACC,aACA,eACA,mDAEA,8BACC,kBAGD,6BACC,WAIF,eACC,mBAEA,iBACC,qBACA,cAIF,SACC,UAGD,eACC,iBACA,mBACA,WAGD,UACI,+CAGJ,2BACE,GACE,YAGJ,mCACE,GACE","file":"settings.css"} \ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["settings.scss","../../../core/css/functions.scss"],"names":[],"mappings":"AAQC,0BACC,WAKF,OACC,WAID,4BC8CC,2CD1CD,mBC0CC,kDDtCD,qBCsCC,yCDlCD,0BCkCC,wCD9BD,oEC8BC,2CD1BD,oCACC,oBACA,0BACA,+BACA,mBAGD,4BACC,oBACA,kCAGD,yBACC,WAIA,wCACC,kBACA,yDACC,gBAIA,mOACC,WAKH,uCACC,aAGD,sCACC,WAED,uDACC,WAKD,gBACC,WAIF,mBACC,aACA,aACA,iBACA,uEACA,qBAEA,4BACC,kBACA,SAEA,+BACC,mBAIA,qCACC,iBAKH,kCACC,iBACA,mBACA,gBAGD,mGACC,4BACA,kBACA,WAMF,oBACC,kBACA,wCACC,0BACA,8CACC,oDAKH,aACC,qBACA,YACA,kBACA,8CACA,WACA,wCACA,8CACA,6CAEA,0DAGC,mCACA,mDACA,qDAGD,uBACC,kBACA,yBAIF,6BACC,oBACA,kCAEA,mCACC,WAIA,oCACC,kBACA,oBACA,iBACA,2BACA,WACA,mBACA,QAEA,0CACC,mBACA,uBACA,gBAKD,gIACC,kBACA,UACA,UACA,oBACA,YAKH,qCACC,kBACA,UACA,MACA,SAEA,yCACC,qBAIF,4CACC,eAGD,4CACC,sBACA,WACA,YACA,YAMF,qBACC,aACA,WACA,SACA,YAEA,uBACC,aAGD,uCACC,sBACA,cACA,yBAIF,iBACC,kBACA,eACA,WACA,YACA,aACA,SACA,gBACA,YAEA,8CAEC,+CACA,wCAEA,0FACC,WAIF,uCACC,kBACA,qBACA,gCACA,WACA,eAEA,wDACC,qBACA,sBACA,eAIF,sCACC,SAGC,4DAEC,iBACA,kBAEA,kFACC,YAGD,mEACC,oDAEA,kFACC,iBAIF,qEACC,WAEA,eAEA,uEACC,eAQN,gBACC,YAIA,2BACC,kCAGD,mBACC,YAIF,sCAEC,aAGD,eACC,WAGD,YACC,qBAIA,aACC,WACA,yBACA,YAGD,WACC,WACA,yBACA,YAMD,oBACC,iBAGD,iBACC,eAKD,iCACC,aACA,eACA,sBACA,SACA,gDACC,aACA,eACA,sBACA,sDACC,oBAIF,kGACC,cACA,YACA,gBAKA,iEACC,kBACA,UAED,+EACC,oBACA,eACA,wBACA,UAIF,wCACC,WAGD,iDACC,qBAGD,sDACC,kBACA,OACA,WACA,0BACA,eACA,gBACA,WAQF,oBACC,gBAGD,wBACC,iBAGD,oDACC,WACA,YACA,mBACA,wCAOD,oBACC,UACA,cACA,gBACA,uBAGD,2BACC,UAKD,oCAEC,cAKD,wEAEC,aAIF,gBACC,kBACA,QACA,QAEA,sBACC,YAGD,sBACC,iBAKF,WACC,WAEA,cACC,WACA,kBACA,4CACA,gBACA,mBAGD,cACC,4CACA,kBACA,gBACA,mBAKD,gBACC,kBACA,cACA,eACA,uBACA,gBAGD,wBACC,kBAEA,gCACC,kBAIF,sCACC,kBAGD,sDAEC,cACA,eACA,eAEA,0EACC,UACA,qBACA,uBACA,gBAIF,8BACC,eAGD,kCACC,mBACA,cAIF,2BACC,mBAID,4BACC,WACA,SACA,QAGD,0BACC,mBAGD,SACC,gBAKA,oBACC,mBACA,iBACA,WAGD,gCACC,kBAIA,gGACC,cAIF,4BACC,gBAGD,iCACC,gBAGD,8BACC,oCAIF,aACC,gBACA,iBACA,oCAGD,aACC,oCAIA,gBACC,oCACA,+BACA,+CACA,mCACA,gBAGD,aACC,aACA,YACA,mBAGD,qBACC,gCACA,+BACA,kBAGD,sBACC,kCACA,gCACA,+BACA,kBACA,2BAIF,WACC,kBACA,QACA,WAIA,qCACC,aAMD,0BACC,SAGD,2BACC,cACA,aAGD,qDACC,WACA,aACA,qBACA,WAGD,kCACC,YAGD,0BACC,gBAEA,iCACC,cAOD,4FAEC,qBACA,WACA,YACA,kBACA,WAIF,0CACC,YACA,WAEA,yGAEC,mBAGD,2DACC,YAIF,uFACC,oCAGD,iDACC,cAGD,kDACC,cAGD,sCACC,kBACA,MACA,QACA,aACA,WACA,UACA,WACA,YAGD,wCACC,aACA,mBAEA,oDACC,YAIF,yCACC,0BACA,iBAGD,iOAKC,cAIF,2CACC,gBACC,UAED,kCACC,WAIF,2CACC,gBACC,UAED,kCACC,WAIF,2CACC,gBACC,UAED,kCACC,WAIF,0CACC,gBACC,UAED,kCACC,YAIF,2CACC,gBACC,WAIF,0CACC,gBACC,YAKF,0CAEE,kEACC,yBAKH,0CACC,iCACC,yBAIF,SACC,gBAEA,0BACC,4CAID,YACC,mBAEA,uBACC,iBACA,2BACA,qBAKH,iBACC,cACA,yBAGD,WACC,kBACA,aACA,UACA,gBAGD,0DACC,WACA,cACA,eACA,WAGD,2BACC,WACA,kBACA,QAGD,iBACC,WAGD,gBACC,mBAKD,kBACC,0BAGD,kBACC,cAGD,sBACC,mBACA,wBACA,2BAGD,WA4GC,aACA,eACA,yBA1GA,0BACC,wBAGD,gCACC,iBAGD,oBACC,OAXgB,KAYhB,QAbiB,IAejB,aAdgB,KAehB,WACA,8CACA,gBACA,MACA,UACA,aACA,mBAGD,qBAQC,oBAPA,0CACC,cACA,WACA,YACA,mBAKD,8BACC,kBACA,UACA,SAEA,gCACC,mBACA,eACA,sBACA,WACA,4CACA,YACA,sBAGD,uCACC,aACA,QACA,eACA,oBAGD,uCACC,8CAKF,oCACC,aAEA,0CACC,iBAIF,gCACC,WACA,YACA,iBAGD,kGAEC,eACA,WACA,YACA,WACA,sBACA,qBAGD,8BACC,iBAEA,kDACC,qBACA,QACA,kBAKH,+CACC,kBACA,YAEA,WACA,YACA,WAOD,kBACC,aAGD,oBACC,kBACA,cAEA,gCACC,cACA,aAGD,0BACC,8CAKD,8BACC,cAGD,+BACC,gBAGD,+BACC,mBAEA,oEACC,kBAKD,8DACC,iBAKD,oEACC,kBAMH,wBACC,kBAEA,4BACC,mBACA,YAGD,2BACC,iBACA,iBACA,mBACA,mCACC,kBACA,SACA,iBAGD,oCACC,gBAOJ,yBACC,uCACC,oBAMA,8BACC,gBAMH,KACC,mBACA,mBAGD,SACC,aAGD,mBACC,mBAGD,eACC,gBAOA,+IACC,sBAEA,+KACC,aAGD,mKACC,WACA,YACA,kCACA,qBACA,kBAGD,mOACC,sCAGD,mNACC,sCAGD,mNACC,oCAMF,sBACC,aAGD,YACC,oBAGD,kBACC,kBAGD,yBACC,kBAGD,sBACC,kBAGD,oCACC,uBAIF,yCACC,kBAGD,wBACC,qBAGD,2BACC,wBAEA,gBACA,aACA,iBACA,sBAKD,WACC,kBACA,2BACA,WAGD,2DAGC,qBAIA,mCACC,qBACA,YACA,iBAGD,+EAEC,YAIF,yBACC,mCACC,YACA,gBACA,cACA,iDAIF,eACC,WAGD,SACC,iBAGD,QACC,qBACA,YACA,WACA,2BAEA,gBACC,kBAIF,qBACC,sBACA,qBACA,YACA,iBAGD,kBACC,qBACA,gBAIA,aACC,sCACA,mCAGD,WACC,oCAGD,mBACC,sCACA,oBAMF,8CACC,WACA,YAGD,wBACC,WACA,YACA,mBACA,kBACA,+DAIA,oBACC,iBACA,gBAEA,uBACC,cAGD,uBACC,kBAIF,0BACC,YACA,gCAGD,oDACC,yBAGD,wDACC,2BAGD,uBACC,cAKD,oBACC,0BAGD,oCACC,gBAIF,2BACC,aACA,eACA,mDAEA,8BACC,kBAGD,6BACC,WAIF,eACC,mBAEA,iBACC,qBACA,cAIF,SACC,UAGD,eACC,iBACA,mBACA,WAGD,UACI,+CAGJ,2BACE,GACE,YAGJ,mCACE,GACE","file":"settings.css"} \ No newline at end of file
diff --git a/apps/settings/css/settings.scss b/apps/settings/css/settings.scss
index f0d03d07a51..e1ece77ea83 100644
--- a/apps/settings/css/settings.scss
+++ b/apps/settings/css/settings.scss
@@ -650,14 +650,6 @@ span.version {
margin: 5px 0;
}
- .app-name, .app-image * {
- cursor: pointer;
- }
-
- .app-summary {
- opacity: .7;
- }
-
.app-image-icon .icon-settings-dark {
width: 100%;
height: 150px;
@@ -671,6 +663,10 @@ span.version {
.actions {
margin-top: 10px;
+
+ button {
+ margin: 10px 0;
+ }
}
}
@@ -881,10 +877,6 @@ span.version {
$toolbar-padding: 8px;
$toolbar-height: 44px + $toolbar-padding * 2;
- .section {
- cursor: pointer;
- }
-
.app-list-move {
transition: transform 1s;
}
diff --git a/apps/settings/l10n/ar.js b/apps/settings/l10n/ar.js
index d02b47937d2..a3aefac88eb 100644
--- a/apps/settings/l10n/ar.js
+++ b/apps/settings/l10n/ar.js
@@ -167,6 +167,8 @@ OC.L10N.register(
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "لا يحتوي PHP على دعم FreeType ، مما يؤدي إلى كسر صور الملف الشخصي وواجهة الإعدادات.",
"PHP getenv" : "PHP getenv",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "يبدو أن PHP لم يتم إعدادها بشكل صحيح للاستعلام عن متغيرات بيئة النظام. يقوم الاختبار باستخدام getenv (\"PATH\") بإرجاع استجابة فارغة فقط.",
+ "PHP memory limit" : "PHP memory limit",
+ "The PHP memory limit is below the recommended value of %s." : "The PHP memory limit is below the recommended value of %s.",
"PHP modules" : "وحدات الـ PHP",
"This instance is missing some required PHP modules. It is required to install them: %s." : "يفتقد هذا الخادوم إلى بعض الأجزاء modules من PHP و التي يتوجب تثبيتها: %s.",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "هذا الخادوم يفتقد إلى بعض الأجزاء modules من PHP المُوصى بها. لتحسين الأداء و التوافقية يُنصح بشدة بتثبيتها: %s.",
diff --git a/apps/settings/l10n/ar.json b/apps/settings/l10n/ar.json
index da36969176b..de69f6e804d 100644
--- a/apps/settings/l10n/ar.json
+++ b/apps/settings/l10n/ar.json
@@ -165,6 +165,8 @@
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "لا يحتوي PHP على دعم FreeType ، مما يؤدي إلى كسر صور الملف الشخصي وواجهة الإعدادات.",
"PHP getenv" : "PHP getenv",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "يبدو أن PHP لم يتم إعدادها بشكل صحيح للاستعلام عن متغيرات بيئة النظام. يقوم الاختبار باستخدام getenv (\"PATH\") بإرجاع استجابة فارغة فقط.",
+ "PHP memory limit" : "PHP memory limit",
+ "The PHP memory limit is below the recommended value of %s." : "The PHP memory limit is below the recommended value of %s.",
"PHP modules" : "وحدات الـ PHP",
"This instance is missing some required PHP modules. It is required to install them: %s." : "يفتقد هذا الخادوم إلى بعض الأجزاء modules من PHP و التي يتوجب تثبيتها: %s.",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "هذا الخادوم يفتقد إلى بعض الأجزاء modules من PHP المُوصى بها. لتحسين الأداء و التوافقية يُنصح بشدة بتثبيتها: %s.",
diff --git a/apps/settings/l10n/cs.js b/apps/settings/l10n/cs.js
index 8bc0f4a78fd..5753bd6b971 100644
--- a/apps/settings/l10n/cs.js
+++ b/apps/settings/l10n/cs.js
@@ -167,6 +167,8 @@ OC.L10N.register(
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "Vámi využívaná verze PHP nepodporuje FreeType, což bude mít za následky vizuální nedostatky u obrázků profilů a v rozhraní pro nastavování.",
"PHP getenv" : "PHP getenv",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "Zdá se, že PHP není správně nastaveno pro dotazování proměnných prostředí systému. Test s příkazem getenv(\"PATH\") vrátí pouze prázdnou odpověď.",
+ "PHP memory limit" : "PHP limit paměti",
+ "The PHP memory limit is below the recommended value of %s." : "Limit paměti pro PHP je nastaven na níže než doporučenou hodnotu %s.",
"PHP modules" : "PHP moduly",
"This instance is missing some required PHP modules. It is required to install them: %s." : "Tato instance postrádá některé potřebné PHP moduly. Je třeba je nainstalovat: %s.",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "Této instanci chybí některé doporučené moduly pro PHP. V zájmu lepšího výkonu a kompatibility, je důrazně doporučeno je nainstalovat: %s.",
diff --git a/apps/settings/l10n/cs.json b/apps/settings/l10n/cs.json
index 7bc1dabeeb8..54b3a53b924 100644
--- a/apps/settings/l10n/cs.json
+++ b/apps/settings/l10n/cs.json
@@ -165,6 +165,8 @@
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "Vámi využívaná verze PHP nepodporuje FreeType, což bude mít za následky vizuální nedostatky u obrázků profilů a v rozhraní pro nastavování.",
"PHP getenv" : "PHP getenv",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "Zdá se, že PHP není správně nastaveno pro dotazování proměnných prostředí systému. Test s příkazem getenv(\"PATH\") vrátí pouze prázdnou odpověď.",
+ "PHP memory limit" : "PHP limit paměti",
+ "The PHP memory limit is below the recommended value of %s." : "Limit paměti pro PHP je nastaven na níže než doporučenou hodnotu %s.",
"PHP modules" : "PHP moduly",
"This instance is missing some required PHP modules. It is required to install them: %s." : "Tato instance postrádá některé potřebné PHP moduly. Je třeba je nainstalovat: %s.",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "Této instanci chybí některé doporučené moduly pro PHP. V zájmu lepšího výkonu a kompatibility, je důrazně doporučeno je nainstalovat: %s.",
diff --git a/apps/settings/l10n/de.js b/apps/settings/l10n/de.js
index 2f9865efb5b..8308f794228 100644
--- a/apps/settings/l10n/de.js
+++ b/apps/settings/l10n/de.js
@@ -121,7 +121,7 @@ OC.L10N.register(
"Personal" : "Persönlich",
"Administration" : "Verwaltung",
"Additional settings" : "Zusätzliche Einstellungen",
- "Artificial Intelligence" : "Künstiche Intelligenz",
+ "Artificial Intelligence" : "Künstliche Intelligenz",
"Administration privileges" : "Administratorrechte",
"Groupware" : "Groupware",
"Overview" : "Übersicht",
diff --git a/apps/settings/l10n/de.json b/apps/settings/l10n/de.json
index eb7a7a0c9c6..2967b288ddd 100644
--- a/apps/settings/l10n/de.json
+++ b/apps/settings/l10n/de.json
@@ -119,7 +119,7 @@
"Personal" : "Persönlich",
"Administration" : "Verwaltung",
"Additional settings" : "Zusätzliche Einstellungen",
- "Artificial Intelligence" : "Künstiche Intelligenz",
+ "Artificial Intelligence" : "Künstliche Intelligenz",
"Administration privileges" : "Administratorrechte",
"Groupware" : "Groupware",
"Overview" : "Übersicht",
diff --git a/apps/settings/l10n/fr.js b/apps/settings/l10n/fr.js
index da76de23bdf..63e3e7640da 100644
--- a/apps/settings/l10n/fr.js
+++ b/apps/settings/l10n/fr.js
@@ -163,6 +163,8 @@ OC.L10N.register(
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "Votre PHP ne prend pas en charge FreeType, provoquant la casse des images de profil et de l'interface des paramètres.",
"PHP getenv" : "PHP getenv",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "PHP ne semble pas être configuré de manière à récupérer les valeurs des variables d’environnement. Le test de la commande getenv(\"PATH\") retourne seulement une réponse vide. ",
+ "PHP memory limit" : "Limite de mémoire PHP",
+ "The PHP memory limit is below the recommended value of %s." : "La limite de mémoire PHP est sous la valeur recommandée de %s.",
"PHP modules" : "Modules PHP",
"This instance is missing some required PHP modules. It is required to install them: %s." : "Cette instance ne dispose pas de plusieurs modules nécessaires sur cette instance. Il est obligatoire de les installer : %s.",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "Cette instance ne dispose pas de plusieurs modules PHP recommandés. Il est recommandé de les installer pour améliorer les performances, et la compatibilité : %s.",
diff --git a/apps/settings/l10n/fr.json b/apps/settings/l10n/fr.json
index 5b665620df4..893b39068ad 100644
--- a/apps/settings/l10n/fr.json
+++ b/apps/settings/l10n/fr.json
@@ -161,6 +161,8 @@
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "Votre PHP ne prend pas en charge FreeType, provoquant la casse des images de profil et de l'interface des paramètres.",
"PHP getenv" : "PHP getenv",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "PHP ne semble pas être configuré de manière à récupérer les valeurs des variables d’environnement. Le test de la commande getenv(\"PATH\") retourne seulement une réponse vide. ",
+ "PHP memory limit" : "Limite de mémoire PHP",
+ "The PHP memory limit is below the recommended value of %s." : "La limite de mémoire PHP est sous la valeur recommandée de %s.",
"PHP modules" : "Modules PHP",
"This instance is missing some required PHP modules. It is required to install them: %s." : "Cette instance ne dispose pas de plusieurs modules nécessaires sur cette instance. Il est obligatoire de les installer : %s.",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "Cette instance ne dispose pas de plusieurs modules PHP recommandés. Il est recommandé de les installer pour améliorer les performances, et la compatibilité : %s.",
diff --git a/apps/settings/l10n/sv.js b/apps/settings/l10n/sv.js
index d782f57ff4b..4c5ae12f80f 100644
--- a/apps/settings/l10n/sv.js
+++ b/apps/settings/l10n/sv.js
@@ -165,6 +165,8 @@ OC.L10N.register(
"Supported" : "Stöds",
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "Din PHP har inte FreeType-stöd, vilket resulterar i brott i profilbilder och inställningsgränssnittet.",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "PHP verkar inte vara korrekt inställd för att fråga efter systemmiljövariabler. Testet med getenv(\"PATH\") ger bara ett tomt svar.",
+ "PHP memory limit" : "PHP minnesgräns",
+ "The PHP memory limit is below the recommended value of %s." : "Minnesgränsen för PHP är under det rekommenderade värdet på %s.",
"PHP modules" : "PHP-moduler",
"This instance is missing some required PHP modules. It is required to install them: %s." : "Den här instansen saknar några nödvändiga PHP-moduler. Det är nödvändigt att installera dem: %s.",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "Den här instansen saknar några rekommenderade PHP-moduler. För förbättrad prestanda och bättre kompatibilitet rekommenderas starkt att du installerar dem: %s.",
diff --git a/apps/settings/l10n/sv.json b/apps/settings/l10n/sv.json
index 088d4540880..839cabbf9f6 100644
--- a/apps/settings/l10n/sv.json
+++ b/apps/settings/l10n/sv.json
@@ -163,6 +163,8 @@
"Supported" : "Stöds",
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "Din PHP har inte FreeType-stöd, vilket resulterar i brott i profilbilder och inställningsgränssnittet.",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "PHP verkar inte vara korrekt inställd för att fråga efter systemmiljövariabler. Testet med getenv(\"PATH\") ger bara ett tomt svar.",
+ "PHP memory limit" : "PHP minnesgräns",
+ "The PHP memory limit is below the recommended value of %s." : "Minnesgränsen för PHP är under det rekommenderade värdet på %s.",
"PHP modules" : "PHP-moduler",
"This instance is missing some required PHP modules. It is required to install them: %s." : "Den här instansen saknar några nödvändiga PHP-moduler. Det är nödvändigt att installera dem: %s.",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "Den här instansen saknar några rekommenderade PHP-moduler. För förbättrad prestanda och bättre kompatibilitet rekommenderas starkt att du installerar dem: %s.",
diff --git a/apps/settings/l10n/zh_TW.js b/apps/settings/l10n/zh_TW.js
index d954e4633ed..8574b203d8c 100644
--- a/apps/settings/l10n/zh_TW.js
+++ b/apps/settings/l10n/zh_TW.js
@@ -167,6 +167,8 @@ OC.L10N.register(
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "您的 PHP 並未啟用 FreeType 支援,導致大頭貼產生器和設定界面無法使用。",
"PHP getenv" : "PHP getenv",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "PHP 設定似乎不完整,導致無法正確取得系統環境變數,因為偵測到 getenv(\"PATH\") 回傳資料為空值",
+ "PHP memory limit" : "PHP 記憶體限制",
+ "The PHP memory limit is below the recommended value of %s." : "目前的 PHP 的記憶體限制設定低於建議值 %s。",
"PHP modules" : "PHP 模組",
"This instance is missing some required PHP modules. It is required to install them: %s." : "此站台缺少一些必要的 PHP 模組。必須安裝這些模組:%s。",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "此站台缺少一些建議的 PHP 模組。為了改善效能與相容性,強烈建立您安裝這些模組:%s。",
diff --git a/apps/settings/l10n/zh_TW.json b/apps/settings/l10n/zh_TW.json
index 0f53999f196..731d0f6302a 100644
--- a/apps/settings/l10n/zh_TW.json
+++ b/apps/settings/l10n/zh_TW.json
@@ -165,6 +165,8 @@
"Your PHP does not have FreeType support, resulting in breakage of profile pictures and the settings interface." : "您的 PHP 並未啟用 FreeType 支援,導致大頭貼產生器和設定界面無法使用。",
"PHP getenv" : "PHP getenv",
"PHP does not seem to be setup properly to query system environment variables. The test with getenv(\"PATH\") only returns an empty response." : "PHP 設定似乎不完整,導致無法正確取得系統環境變數,因為偵測到 getenv(\"PATH\") 回傳資料為空值",
+ "PHP memory limit" : "PHP 記憶體限制",
+ "The PHP memory limit is below the recommended value of %s." : "目前的 PHP 的記憶體限制設定低於建議值 %s。",
"PHP modules" : "PHP 模組",
"This instance is missing some required PHP modules. It is required to install them: %s." : "此站台缺少一些必要的 PHP 模組。必須安裝這些模組:%s。",
"This instance is missing some recommended PHP modules. For improved performance and better compatibility it is highly recommended to install them: %s." : "此站台缺少一些建議的 PHP 模組。為了改善效能與相容性,強烈建立您安裝這些模組:%s。",
diff --git a/apps/settings/src/components/AppList/AppItem.vue b/apps/settings/src/components/AppList/AppItem.vue
index 179497a0f19..0838f2c8822 100644
--- a/apps/settings/src/components/AppList/AppItem.vue
+++ b/apps/settings/src/components/AppList/AppItem.vue
@@ -23,12 +23,10 @@
<template>
<component :is="listView ? `tr` : `li`"
class="section"
- :class="{ selected: isSelected }"
- @click="showAppDetails">
+ :class="{ selected: isSelected }">
<component :is="dataItemTag"
class="app-image app-image-icon"
- :headers="getDataItemHeaders(`app-table-col-icon`)"
- @click="showAppDetails">
+ :headers="getDataItemHeaders(`app-table-col-icon`)">
<div v-if="(listView && !app.preview) || (!listView && !screenshotLoaded)" class="icon-settings-dark" />
<svg v-else-if="listView && app.preview"
@@ -48,9 +46,11 @@
</component>
<component :is="dataItemTag"
class="app-name"
- :headers="getDataItemHeaders(`app-table-col-name`)"
- @click="showAppDetails">
- {{ app.name }}
+ :headers="getDataItemHeaders(`app-table-col-name`)">
+ <router-link class="app-name--link" :to="{ name: 'apps-details', params: { category: category, id: app.id }}"
+ :aria-label="t('settings', 'Show details for {appName} app', { appName:app.name })">
+ {{ app.name }}
+ </router-link>
</component>
<component :is="dataItemTag"
v-if="!listView"
@@ -185,19 +185,6 @@ export default {
},
methods: {
- async showAppDetails(event) {
- if (event.currentTarget.tagName === 'INPUT' || event.currentTarget.tagName === 'A') {
- return
- }
- try {
- await this.$router.push({
- name: 'apps-details',
- params: { category: this.category, id: this.app.id },
- })
- } catch (e) {
- // we already view this app
- }
- },
prefix(prefix, content) {
return prefix + '_' + content
},
@@ -217,4 +204,14 @@ export default {
.app-image img {
width: 100%;
}
+
+.app-name--link::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
</style>
diff --git a/apps/settings/src/components/AppList/AppScore.vue b/apps/settings/src/components/AppList/AppScore.vue
index 0569d687e88..69290645ef9 100644
--- a/apps/settings/src/components/AppList/AppScore.vue
+++ b/apps/settings/src/components/AppList/AppScore.vue
@@ -21,7 +21,7 @@
-->
<template>
- <img :src="scoreImage" class="app-score-image">
+ <img :src="scoreImage" :alt="t('settings', 'Rating: {score}/10', {score:appScore})" class="app-score-image">
</template>
<script>
import { imagePath } from '@nextcloud/router'
@@ -30,11 +30,13 @@ export default {
name: 'AppScore',
props: ['score'],
computed: {
+ appScore() {
+ return Math.round(this.score * 10)
+ },
scoreImage() {
- const score = Math.round(this.score * 10)
- const imageName = 'rating/s' + score + '.svg'
+ const imageName = 'rating/s' + this.appScore + '.svg'
return imagePath('core', imageName)
- },
+ }
},
}
</script>
diff --git a/apps/settings/templates/settings/admin/overview.php b/apps/settings/templates/settings/admin/overview.php
index d8b3674f47e..7d0ea25872a 100644
--- a/apps/settings/templates/settings/admin/overview.php
+++ b/apps/settings/templates/settings/admin/overview.php
@@ -70,7 +70,7 @@
<!-- should be the last part, so Updater can follow if enabled (it has no heading therefore). -->
<h2><?php p($l->t('Version'));?></h2>
<?php if ($theme->getTitle() === 'Nextcloud'): ?>
- <p><strong><a href="<?php print_unescaped($theme->getBaseUrl()); ?>" rel="noreferrer noopener" target="_blank">Nextcloud Hub 6</a> (<?php p(OC_Util::getHumanVersion()) ?>)</strong></p>
+ <p><strong><a href="<?php print_unescaped($theme->getBaseUrl()); ?>" rel="noreferrer noopener" target="_blank">Nextcloud Hub 7</a> (<?php p(OC_Util::getHumanVersion()) ?>)</strong></p>
<?php else: ?>
<p><strong><a href="<?php print_unescaped($theme->getBaseUrl()); ?>" rel="noreferrer noopener" target="_blank"><?php p($theme->getTitle()); ?></a> <?php p(OC_Util::getHumanVersion()) ?></strong></p>
<?php endif; ?>
diff --git a/apps/systemtags/css/settings.css b/apps/systemtags/css/settings.css
deleted file mode 100644
index eb1d2ad45f6..00000000000
--- a/apps/systemtags/css/settings.css
+++ /dev/null
@@ -1,29 +0,0 @@
-.systemtag-input {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
-}
-.systemtag-input--name {
- margin-right: 3px;
-}
-.systemtag-input--name,
-.systemtag-input--level {
- display: flex;
- flex-direction: column;
-}
-.systemtag-input--actions {
- margin-top: 25px;
- display: flex;
- flex-direction: row;
-}
-#systemtags .select2-container {
- width: 100%;
- max-width: 400px;
-}
-#systemtags .select2-container .select2-choice {
- height: auto;
-}
-#systemtag_name {
- width: 100%;
- max-width: 400px;
-} \ No newline at end of file
diff --git a/apps/systemtags/js/admin.js b/apps/systemtags/js/admin.js
deleted file mode 100644
index 0b9d9ec4a41..00000000000
--- a/apps/systemtags/js/admin.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- * @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
- *
- * @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/>.
- *
- */
-
-(function() {
- if (!OCA.SystemTags) {
- /**
- * @namespace
- */
- OCA.SystemTags = {};
- }
-
- OCA.SystemTags.Admin = {
-
- collection: null,
-
- init: function() {
- var self = this;
-
- this.collection = OC.SystemTags.collection;
- this.collection.fetch({
- success: function() {
- $('#systemtag').select2(_.extend(self.select2));
- $('#systemtag').parent().children('.select2-container').attr('aria-expanded', 'false')
- }
- });
-
- var self = this;
- $('#systemtag_name').on('keyup', function(e) {
- if (e.which === 13) {
- _.bind(self._onClickSubmit, self)();
- }
- });
- $('#systemtag_submit').on('click', _.bind(this._onClickSubmit, this));
- $('#systemtag_delete').on('click', _.bind(this._onClickDelete, this));
- $('#systemtag_reset').on('click', _.bind(this._onClickReset, this));
- $('#systemtag').select2(_.extend(self.select2)).on('select2-open', () => {
- $('.select2-container').attr('aria-expanded', 'true')
- });
- $('#systemtag').select2(_.extend(self.select2)).on('select2-close', () => {
- $('.select2-container').attr('aria-expanded', 'false')
- });
- },
-
- /**
- * Selecting a systemtag in select2
- *
- * @param {OC.SystemTags.SystemTagModel} tag
- */
- onSelectTag: function (tag) {
- var level = 0;
- if (tag.get('userVisible')) {
- level += 2;
- if (tag.get('userAssignable')) {
- level += 1;
- }
- }
-
- $('#systemtag_name').val(tag.get('name'));
- $('#systemtag_level').val(level);
-
- this._prepareForm(tag.get('id'));
- },
-
- /**
- * Clicking the "Create"/"Update" button
- */
- _onClickSubmit: function () {
- var level = parseInt($('#systemtag_level').val(), 10),
- tagId = $('#systemtags').attr('data-systemtag-id');
- var data = {
- name: $('#systemtag_name').val(),
- userVisible: level === 2 || level === 3,
- userAssignable: level === 3
- };
-
- if (!data.name) {
- OCP.Toast.error(t('systemtags_manager', 'Tag name is empty'));
- return;
- }
-
- if (tagId) {
- var model = this.collection.get(tagId);
- model.save(data);
- } else {
- this.collection.create(data);
- }
-
- this._onClickReset();
- },
-
- /**
- * Clicking the "Delete" button
- */
- _onClickDelete: function () {
- var tagId = $('#systemtags').attr('data-systemtag-id');
- var model = this.collection.get(tagId);
- model.destroy();
-
- this._onClickReset();
- },
-
- /**
- * Clicking the "Reset" button
- */
- _onClickReset: function () {
- $('#systemtag_name').val('');
- $('#systemtag_level').val(3);
- this._prepareForm(0);
- },
-
- /**
- * Prepare the form for create/update
- *
- * @param {number} tagId
- */
- _prepareForm: function (tagId) {
- if (tagId > 0) {
- $('#systemtags').attr('data-systemtag-id', tagId);
- $('#systemtag_delete').removeClass('hidden');
- $('#systemtag_submit span').text(t('systemtags_manager', 'Update'));
- $('#systemtag_create').addClass('hidden');
- } else {
- $('#systemtag').select2('val', '');
- $('#systemtags').attr('data-systemtag-id', '');
- $('#systemtag_delete').addClass('hidden');
- $('#systemtag_submit span').text(t('systemtags_manager', 'Create'));
- $('#systemtag_create').removeClass('hidden');
- }
- },
-
- /**
- * Select2 options for the SystemTag dropdown
- */
- select2: {
- allowClear: false,
- multiple: false,
- placeholder: t('systemtags_manager', 'Select tag …'),
- query: _.debounce(function(query) {
- query.callback({
- results: OCA.SystemTags.Admin.collection.filterByName(query.term)
- });
- }, 100, true),
- id: function(element) {
- return element;
- },
- initSelection: function(element, callback) {
- var selection = ($(element).val() || []).split('|').sort();
- callback(selection);
- },
- formatResult: function (tag) {
- return OC.SystemTags.getDescriptiveTag(tag);
- },
- formatSelection: function (tag) {
- OCA.SystemTags.Admin.onSelectTag(tag);
- return OC.SystemTags.getDescriptiveTag(tag);
- },
- escapeMarkup: function(m) {
- return m;
- },
- sortResults: function(results) {
- results.sort(function(a, b) {
- return OC.Util.naturalSortCompare(a.get('name'), b.get('name'));
- });
- return results;
- }
- }
- };
-})();
-
-window.addEventListener('DOMContentLoaded', function() {
- if (!window.TESTING) {
- OCA.SystemTags.Admin.init();
- }
-});
-
diff --git a/apps/systemtags/lib/Settings/Admin.php b/apps/systemtags/lib/Settings/Admin.php
index 5a5cf3d49e2..cb88a8282eb 100644
--- a/apps/systemtags/lib/Settings/Admin.php
+++ b/apps/systemtags/lib/Settings/Admin.php
@@ -32,6 +32,7 @@ class Admin implements ISettings {
* @return TemplateResponse
*/
public function getForm() {
+ \OCP\Util::addScript('systemtags', 'admin');
return new TemplateResponse('systemtags', 'admin', [], '');
}
diff --git a/apps/systemtags/src/admin.ts b/apps/systemtags/src/admin.ts
new file mode 100644
index 00000000000..77f5e344f92
--- /dev/null
+++ b/apps/systemtags/src/admin.ts
@@ -0,0 +1,32 @@
+/**
+ * @copyright 2023 Christopher Ng <chrng8@gmail.com>
+ *
+ * @author Christopher Ng <chrng8@gmail.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 Vue from 'vue'
+import { getRequestToken } from '@nextcloud/auth'
+
+import SystemTagsSection from './views/SystemTagsSection.vue'
+
+// @ts-expect-error __webpack_nonce__ is injected by webpack
+__webpack_nonce__ = btoa(getRequestToken())
+
+const SystemTagsSectionView = Vue.extend(SystemTagsSection)
+new SystemTagsSectionView().$mount('#vue-admin-systemtags')
diff --git a/apps/systemtags/src/components/SystemTagForm.vue b/apps/systemtags/src/components/SystemTagForm.vue
new file mode 100644
index 00000000000..201e1fa9ed3
--- /dev/null
+++ b/apps/systemtags/src/components/SystemTagForm.vue
@@ -0,0 +1,326 @@
+<!--
+ - @copyright 2023 Christopher Ng <chrng8@gmail.com>
+ -
+ - @author Christopher Ng <chrng8@gmail.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/>.
+ -
+-->
+
+<template>
+ <form class="system-tag-form"
+ :disabled="loading"
+ aria-labelledby="system-tag-form-heading"
+ @submit.prevent="handleSubmit"
+ @reset="reset">
+ <h3 id="system-tag-form-heading">
+ {{ t('systemtags', 'Create or edit tags') }}
+ </h3>
+
+ <div class="system-tag-form__group">
+ <label for="system-tags-input">{{ t('systemtags', 'Search for a tag to edit') }}</label>
+ <NcSelectTags v-model="selectedTag"
+ input-id="system-tags-input"
+ :placeholder="t('systemtags', 'Collaborative tags …')"
+ :fetch-tags="false"
+ :options="tags"
+ :multiple="false"
+ passthru>
+ <template #no-options>
+ {{ t('systemtags', 'No tags to select') }}
+ </template>
+ </NcSelectTags>
+ </div>
+
+ <div class="system-tag-form__group">
+ <label for="system-tag-name">{{ t('systemtags', 'Tag name') }}</label>
+ <NcTextField id="system-tag-name"
+ ref="tagNameInput"
+ :value.sync="tagName"
+ :error="Boolean(errorMessage)"
+ :helper-text="errorMessage"
+ label-outside />
+ </div>
+
+ <div class="system-tag-form__group">
+ <label for="system-tag-level">{{ t('systemtags', 'Tag level') }}</label>
+ <NcSelect v-model="tagLevel"
+ input-id="system-tag-level"
+ :options="tagLevelOptions"
+ :reduce="level => level.id"
+ :clearable="false"
+ :disabled="loading" />
+ </div>
+
+ <div class="system-tag-form__row">
+ <NcButton v-if="isCreating"
+ native-type="submit"
+ :disabled="isCreateDisabled || loading">
+ {{ t('systemtags', 'Create') }}
+ </NcButton>
+ <template v-else>
+ <NcButton native-type="submit"
+ :disabled="isUpdateDisabled || loading">
+ {{ t('systemtags', 'Update') }}
+ </NcButton>
+ <NcButton :disabled="loading"
+ @click="handleDelete">
+ {{ t('systemtags', 'Delete') }}
+ </NcButton>
+ </template>
+ <NcButton native-type="reset"
+ :disabled="isResetDisabled || loading">
+ {{ t('systemtags', 'Reset') }}
+ </NcButton>
+ <NcLoadingIcon v-if="loading"
+ :name="t('systemtags', 'Loading …')"
+ :size="32" />
+ </div>
+ </form>
+</template>
+
+<script lang="ts">
+/* eslint-disable */
+import Vue, { type PropType } from 'vue'
+
+import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
+import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
+import NcSelectTags from '@nextcloud/vue/dist/Components/NcSelectTags.js'
+import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
+
+import { translate as t } from '@nextcloud/l10n'
+import { showSuccess } from '@nextcloud/dialogs'
+
+import { defaultBaseTag } from '../utils.js'
+import { createTag, deleteTag, updateTag } from '../services/api.js'
+
+import type { Tag, TagWithId } from '../types.js'
+
+enum TagLevel {
+ Public = 'Public',
+ Restricted = 'Restricted',
+ Invisible = 'Invisible',
+}
+
+interface TagLevelOption {
+ id: TagLevel
+ label: string
+}
+
+const tagLevelOptions: TagLevelOption[] = [
+ {
+ id: TagLevel.Public,
+ label: t('systemtags', 'Public'),
+ },
+ {
+ id: TagLevel.Restricted,
+ label: t('systemtags', 'Restricted'),
+ },
+ {
+ id: TagLevel.Invisible,
+ label: t('systemtags', 'Invisible'),
+ },
+]
+
+const getTagLevel = (userVisible: boolean, userAssignable: boolean): TagLevel => {
+ const matchLevel: Record<string, TagLevel> = {
+ [[true, true].join(',')]: TagLevel.Public,
+ [[true, false].join(',')]: TagLevel.Restricted,
+ [[false, false].join(',')]: TagLevel.Invisible,
+ }
+ return matchLevel[[userVisible, userAssignable].join(',')]
+}
+
+export default Vue.extend({
+ name: 'SystemTagForm',
+
+ components: {
+ NcButton,
+ NcLoadingIcon,
+ NcSelect,
+ NcSelectTags,
+ NcTextField,
+ },
+
+ props: {
+ tags: {
+ type: Array as PropType<TagWithId[]>,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ loading: false,
+ tagLevelOptions,
+ selectedTag: null as null | TagWithId,
+ errorMessage: '',
+ tagName: '',
+ tagLevel: TagLevel.Public,
+ }
+ },
+
+ watch: {
+ selectedTag(tag: null | TagWithId) {
+ this.tagName = tag ? tag.displayName : ''
+ this.tagLevel = tag ? getTagLevel(tag.userVisible, tag.userAssignable) : TagLevel.Public
+ },
+ },
+
+ computed: {
+ isCreating(): boolean {
+ return this.selectedTag === null
+ },
+
+ isCreateDisabled(): boolean {
+ return this.tagName === ''
+ },
+
+ isUpdateDisabled(): boolean {
+ return (
+ this.tagName === ''
+ || (
+ this.selectedTag?.displayName === this.tagName
+ && getTagLevel(this.selectedTag?.userVisible, this.selectedTag?.userAssignable) === this.tagLevel
+ )
+ )
+ },
+
+ isResetDisabled(): boolean {
+ if (this.isCreating) {
+ return this.tagName === '' && this.tagLevel === TagLevel.Public
+ }
+ return this.selectedTag === null
+ },
+
+ userVisible(): boolean {
+ const matchLevel: Record<TagLevel, boolean> = {
+ [TagLevel.Public]: true,
+ [TagLevel.Restricted]: true,
+ [TagLevel.Invisible]: false,
+ }
+ return matchLevel[this.tagLevel]
+ },
+
+ userAssignable(): boolean {
+ const matchLevel: Record<TagLevel, boolean> = {
+ [TagLevel.Public]: true,
+ [TagLevel.Restricted]: false,
+ [TagLevel.Invisible]: false,
+ }
+ return matchLevel[this.tagLevel]
+ },
+
+ tagProperties(): Omit<Tag, 'id' | 'canAssign'> {
+ return {
+ displayName: this.tagName,
+ userVisible: this.userVisible,
+ userAssignable: this.userAssignable,
+ }
+ },
+ },
+
+ methods: {
+ t,
+
+ async handleSubmit() {
+ if (this.isCreating) {
+ await this.create()
+ return
+ }
+ await this.update()
+ },
+
+ async create() {
+ const tag: Tag = { ...defaultBaseTag, ...this.tagProperties }
+ this.loading = true
+ try {
+ const id = await createTag(tag)
+ const createdTag: TagWithId = { ...tag, id }
+ this.$emit('tag:created', createdTag)
+ showSuccess(t('systemtags', 'Created tag'))
+ this.reset()
+ } catch (error) {
+ this.errorMessage = t('systemtags', 'Failed to create tag')
+ }
+ this.loading = false
+ },
+
+ async update() {
+ if (this.selectedTag === null) {
+ return
+ }
+ const tag: TagWithId = { ...this.selectedTag, ...this.tagProperties }
+ this.loading = true
+ try {
+ await updateTag(tag)
+ this.selectedTag = tag
+ this.$emit('tag:updated', tag)
+ showSuccess(t('systemtags', 'Updated tag'))
+ this.$refs.tagNameInput?.focus()
+ } catch (error) {
+ this.errorMessage = t('systemtags', 'Failed to update tag')
+ }
+ this.loading = false
+ },
+
+ async handleDelete() {
+ if (this.selectedTag === null) {
+ return
+ }
+ this.loading = true
+ try {
+ await deleteTag(this.selectedTag)
+ this.$emit('tag:deleted', this.selectedTag)
+ showSuccess(t('systemtags', 'Deleted tag'))
+ this.reset()
+ } catch (error) {
+ this.errorMessage = t('systemtags', 'Failed to delete tag')
+ }
+ this.loading = false
+ },
+
+ reset() {
+ this.selectedTag = null
+ this.errorMessage = ''
+ this.tagName = ''
+ this.tagLevel = TagLevel.Public
+ this.$refs.tagNameInput?.focus()
+ },
+ },
+})
+</script>
+
+<style lang="scss" scoped>
+.system-tag-form {
+ display: flex;
+ flex-direction: column;
+ max-width: 400px;
+ gap: 8px 0;
+
+ &__group {
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__row {
+ margin-top: 8px;
+ display: flex;
+ gap: 0 4px;
+ }
+}
+</style>
diff --git a/apps/systemtags/src/components/SystemTags.vue b/apps/systemtags/src/components/SystemTags.vue
index 9315d0eb6c0..9bd9619d773 100644
--- a/apps/systemtags/src/components/SystemTags.vue
+++ b/apps/systemtags/src/components/SystemTags.vue
@@ -59,22 +59,16 @@ import NcSelectTags from '@nextcloud/vue/dist/Components/NcSelectTags.js'
import { translate as t } from '@nextcloud/l10n'
import { showError } from '@nextcloud/dialogs'
+import { defaultBaseTag } from '../utils.js'
+import { fetchLastUsedTagIds, fetchTags } from '../services/api.js'
import {
- createTag,
- deleteTag,
- fetchLastUsedTagIds,
- fetchSelectedTags,
- fetchTags,
- selectTag,
-} from '../services/api.js'
-
-import type { BaseTag, Tag, TagWithId } from '../types.js'
-
-const defaultBaseTag: BaseTag = {
- userVisible: true,
- userAssignable: true,
- canAssign: true,
-}
+ createTagForFile,
+ deleteTagForFile,
+ fetchTagsForFile,
+ setTagForFile,
+} from '../services/files.js'
+
+import type { Tag, TagWithId } from '../types.js'
export default Vue.extend({
name: 'SystemTags',
@@ -133,7 +127,7 @@ export default Vue.extend({
async handler() {
this.loadingTags = true
try {
- this.selectedTags = await fetchSelectedTags(this.fileId)
+ this.selectedTags = await fetchTagsForFile(this.fileId)
this.$emit('has-tags', this.selectedTags.length > 0)
} catch (error) {
showError(t('systemtags', 'Failed to load selected tags'))
@@ -175,14 +169,15 @@ export default Vue.extend({
},
async handleSelect(tags: Tag[]) {
- const selectedTag = tags[tags.length - 1]
- if (!selectedTag.id) {
+ const lastTag = tags[tags.length - 1]
+ if (!lastTag.id) {
// Ignore created tags handled by `handleCreate()`
return
}
+ const selectedTag = lastTag as TagWithId
this.loading = true
try {
- await selectTag(this.fileId, selectedTag)
+ await setTagForFile(selectedTag, this.fileId)
const sortToFront = (a: TagWithId, b: TagWithId) => {
if (a.id === selectedTag.id) {
return -1
@@ -201,7 +196,7 @@ export default Vue.extend({
async handleCreate(tag: Tag) {
this.loading = true
try {
- const id = await createTag(this.fileId, tag)
+ const id = await createTagForFile(tag, this.fileId)
const createdTag = { ...tag, id }
this.sortedTags.unshift(createdTag)
this.selectedTags.push(createdTag)
@@ -211,10 +206,10 @@ export default Vue.extend({
this.loading = false
},
- async handleDeselect(tag: Tag) {
+ async handleDeselect(tag: TagWithId) {
this.loading = true
try {
- await deleteTag(this.fileId, tag)
+ await deleteTagForFile(tag, this.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to delete tag'))
}
diff --git a/apps/systemtags/src/services/api.ts b/apps/systemtags/src/services/api.ts
index 91393e0afe4..4872036fa92 100644
--- a/apps/systemtags/src/services/api.ts
+++ b/apps/systemtags/src/services/api.ts
@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
+
import type { FileStat, ResponseDataDetailed } from 'webdav'
import type { ServerTag, Tag, TagWithId } from '../types.js'
@@ -30,7 +31,7 @@ import { davClient } from './davClient.js'
import { formatTag, parseIdFromLocation, parseTags } from '../utils'
import { logger } from '../logger.js'
-const fetchTagsBody = `<?xml version="1.0"?>
+export const fetchTagsPayload = `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:id />
@@ -45,7 +46,7 @@ export const fetchTags = async (): Promise<TagWithId[]> => {
const path = '/systemtags'
try {
const { data: tags } = await davClient.getDirectoryContents(path, {
- data: fetchTagsBody,
+ data: fetchTagsPayload,
details: true,
glob: '/systemtags/*', // Filter out first empty tag
}) as ResponseDataDetailed<Required<FileStat>[]>
@@ -67,39 +68,10 @@ export const fetchLastUsedTagIds = async (): Promise<number[]> => {
}
}
-export const fetchSelectedTags = async (fileId: number): Promise<TagWithId[]> => {
- const path = '/systemtags-relations/files/' + fileId
- try {
- const { data: tags } = await davClient.getDirectoryContents(path, {
- data: fetchTagsBody,
- details: true,
- glob: '/systemtags-relations/files/*/*', // Filter out first empty tag
- }) as ResponseDataDetailed<Required<FileStat>[]>
- return parseTags(tags)
- } catch (error) {
- logger.error(t('systemtags', 'Failed to load selected tags'), { error })
- throw new Error(t('systemtags', 'Failed to load selected tags'))
- }
-}
-
-export const selectTag = async (fileId: number, tag: Tag | ServerTag): Promise<void> => {
- const path = '/systemtags-relations/files/' + fileId + '/' + tag.id
- const tagToPut = formatTag(tag)
- try {
- await davClient.customRequest(path, {
- method: 'PUT',
- data: tagToPut,
- })
- } catch (error) {
- logger.error(t('systemtags', 'Failed to select tag'), { error })
- throw new Error(t('systemtags', 'Failed to select tag'))
- }
-}
-
/**
* @return created tag id
*/
-export const createTag = async (fileId: number, tag: Tag): Promise<number> => {
+export const createTag = async (tag: Tag | ServerTag): Promise<number> => {
const path = '/systemtags'
const tagToPost = formatTag(tag)
try {
@@ -109,12 +81,7 @@ export const createTag = async (fileId: number, tag: Tag): Promise<number> => {
})
const contentLocation = headers.get('content-location')
if (contentLocation) {
- const tagToPut = {
- ...tagToPost,
- id: parseIdFromLocation(contentLocation),
- }
- await selectTag(fileId, tagToPut)
- return tagToPut.id
+ return parseIdFromLocation(contentLocation)
}
logger.error(t('systemtags', 'Missing "Content-Location" header'))
throw new Error(t('systemtags', 'Missing "Content-Location" header'))
@@ -124,8 +91,32 @@ export const createTag = async (fileId: number, tag: Tag): Promise<number> => {
}
}
-export const deleteTag = async (fileId: number, tag: Tag): Promise<void> => {
- const path = '/systemtags-relations/files/' + fileId + '/' + tag.id
+export const updateTag = async (tag: TagWithId): Promise<void> => {
+ const path = '/systemtags/' + tag.id
+ const data = `<?xml version="1.0"?>
+ <d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:set>
+ <d:prop>
+ <oc:display-name>${tag.displayName}</oc:display-name>
+ <oc:user-visible>${tag.userVisible}</oc:user-visible>
+ <oc:user-assignable>${tag.userAssignable}</oc:user-assignable>
+ </d:prop>
+ </d:set>
+ </d:propertyupdate>`
+
+ try {
+ await davClient.customRequest(path, {
+ method: 'PROPPATCH',
+ data,
+ })
+ } catch (error) {
+ logger.error(t('systemtags', 'Failed to update tag'), { error })
+ throw new Error(t('systemtags', 'Failed to update tag'))
+ }
+}
+
+export const deleteTag = async (tag: TagWithId): Promise<void> => {
+ const path = '/systemtags/' + tag.id
try {
await davClient.deleteFile(path)
} catch (error) {
diff --git a/apps/systemtags/src/services/files.ts b/apps/systemtags/src/services/files.ts
new file mode 100644
index 00000000000..35a087d04d1
--- /dev/null
+++ b/apps/systemtags/src/services/files.ts
@@ -0,0 +1,82 @@
+/**
+ * @copyright 2023 Christopher Ng <chrng8@gmail.com>
+ *
+ * @author Christopher Ng <chrng8@gmail.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 type { FileStat, ResponseDataDetailed } from 'webdav'
+import type { ServerTagWithId, Tag, TagWithId } from '../types.js'
+
+import { davClient } from './davClient.js'
+import { createTag, fetchTagsPayload } from './api.js'
+import { formatTag, parseTags } from '../utils.js'
+import { logger } from '../logger.js'
+
+export const fetchTagsForFile = async (fileId: number): Promise<TagWithId[]> => {
+ const path = '/systemtags-relations/files/' + fileId
+ try {
+ const { data: tags } = await davClient.getDirectoryContents(path, {
+ data: fetchTagsPayload,
+ details: true,
+ glob: '/systemtags-relations/files/*/*', // Filter out first empty tag
+ }) as ResponseDataDetailed<Required<FileStat>[]>
+ return parseTags(tags)
+ } catch (error) {
+ logger.error(t('systemtags', 'Failed to load tags for file'), { error })
+ throw new Error(t('systemtags', 'Failed to load tags for file'))
+ }
+}
+
+/**
+ * @return created tag id
+ */
+export const createTagForFile = async (tag: Tag, fileId: number): Promise<number> => {
+ const tagToCreate = formatTag(tag)
+ const tagId = await createTag(tagToCreate)
+ const tagToSet: ServerTagWithId = {
+ ...tagToCreate,
+ id: tagId,
+ }
+ await setTagForFile(tagToSet, fileId)
+ return tagToSet.id
+}
+
+export const setTagForFile = async (tag: TagWithId | ServerTagWithId, fileId: number): Promise<void> => {
+ const path = '/systemtags-relations/files/' + fileId + '/' + tag.id
+ const tagToPut = formatTag(tag)
+ try {
+ await davClient.customRequest(path, {
+ method: 'PUT',
+ data: tagToPut,
+ })
+ } catch (error) {
+ logger.error(t('systemtags', 'Failed to set tag for file'), { error })
+ throw new Error(t('systemtags', 'Failed to set tag for file'))
+ }
+}
+
+export const deleteTagForFile = async (tag: TagWithId, fileId: number): Promise<void> => {
+ const path = '/systemtags-relations/files/' + fileId + '/' + tag.id
+ try {
+ await davClient.deleteFile(path)
+ } catch (error) {
+ logger.error(t('systemtags', 'Failed to delete tag for file'), { error })
+ throw new Error(t('systemtags', 'Failed to delete tag for file'))
+ }
+}
diff --git a/apps/systemtags/src/types.ts b/apps/systemtags/src/types.ts
index c38a360c1fe..f2cff9b06c2 100644
--- a/apps/systemtags/src/types.ts
+++ b/apps/systemtags/src/types.ts
@@ -36,3 +36,5 @@ export type TagWithId = Required<Tag>
export type ServerTag = BaseTag & {
name: string
}
+
+export type ServerTagWithId = Required<ServerTag>
diff --git a/apps/systemtags/src/utils.ts b/apps/systemtags/src/utils.ts
index 4978459227f..602594199aa 100644
--- a/apps/systemtags/src/utils.ts
+++ b/apps/systemtags/src/utils.ts
@@ -24,12 +24,18 @@ import camelCase from 'camelcase'
import type { DAVResultResponseProps } from 'webdav'
-import type { ServerTag, Tag, TagWithId } from './types.js'
+import type { BaseTag, ServerTag, Tag, TagWithId } from './types.js'
+
+export const defaultBaseTag: BaseTag = {
+ userVisible: true,
+ userAssignable: true,
+ canAssign: true,
+}
export const parseTags = (tags: { props: DAVResultResponseProps }[]): TagWithId[] => {
return tags.map(({ props }) => Object.fromEntries(
Object.entries(props)
- .map(([key, value]) => [camelCase(key), value]),
+ .map(([key, value]) => [camelCase(key), camelCase(key) === 'displayName' ? String(value) : value]),
)) as TagWithId[]
}
diff --git a/apps/systemtags/src/views/SystemTagsSection.vue b/apps/systemtags/src/views/SystemTagsSection.vue
new file mode 100644
index 00000000000..235a9283e6f
--- /dev/null
+++ b/apps/systemtags/src/views/SystemTagsSection.vue
@@ -0,0 +1,99 @@
+<!--
+ - @copyright 2023 Christopher Ng <chrng8@gmail.com>
+ -
+ - @author Christopher Ng <chrng8@gmail.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/>.
+ -
+-->
+
+<template>
+ <NcSettingsSection :name="t('systemtags', 'Collaborative tags')"
+ :description="t('systemtags', 'Collaborative tags are available for all users. Restricted tags are visible to users but cannot be assigned by them. Invisible tags are for internal use, since users cannot see or assign them.')">
+ <NcLoadingIcon v-if="loadingTags"
+ :name="t('systemtags', 'Loading collaborative tags …')"
+ :size="32" />
+
+ <SystemTagForm v-else
+ :tags="tags"
+ @tag:created="handleCreate"
+ @tag:updated="handleUpdate"
+ @tag:deleted="handleDelete" />
+ </NcSettingsSection>
+</template>
+
+<script lang="ts">
+/* eslint-disable */
+import Vue from 'vue'
+
+import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
+import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
+
+import { translate as t } from '@nextcloud/l10n'
+import { showError } from '@nextcloud/dialogs'
+
+import SystemTagForm from '../components/SystemTagForm.vue'
+
+import { fetchTags } from '../services/api.js'
+
+import type { TagWithId } from '../types.js'
+
+export default Vue.extend({
+ name: 'SystemTagsSection',
+
+ components: {
+ NcLoadingIcon,
+ NcSettingsSection,
+ SystemTagForm,
+ },
+
+ data() {
+ return {
+ loadingTags: false,
+ tags: [] as TagWithId[],
+ }
+ },
+
+ async created() {
+ this.loadingTags = true
+ try {
+ this.tags = await fetchTags()
+ } catch (error) {
+ showError(t('systemtags', 'Failed to load tags'))
+ }
+ this.loadingTags = false
+ },
+
+ methods: {
+ t,
+
+ handleCreate(tag: TagWithId) {
+ this.tags.unshift(tag)
+ },
+
+ handleUpdate(tag: TagWithId) {
+ const tagIndex = this.tags.findIndex(currTag => currTag.id === tag.id)
+ this.tags.splice(tagIndex, 1)
+ this.tags.unshift(tag)
+ },
+
+ handleDelete(tag: TagWithId) {
+ const tagIndex = this.tags.findIndex(currTag => currTag.id === tag.id)
+ this.tags.splice(tagIndex, 1)
+ },
+ },
+})
+</script>
diff --git a/apps/systemtags/templates/admin.php b/apps/systemtags/templates/admin.php
index 881172944df..9358bf7fcda 100644
--- a/apps/systemtags/templates/admin.php
+++ b/apps/systemtags/templates/admin.php
@@ -18,43 +18,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-
-script('core', 'systemtags');
-
-script('systemtags', 'admin');
-style('systemtags', 'settings');
-
-/** @var \OCP\IL10N $l */
?>
-<form id="systemtags" class="section" data-systemtag-id="">
- <h2><?php p($l->t('Collaborative tags')); ?></h2>
- <p class="settings-hint"><?php p($l->t('Collaborative tags are available for all users. Restricted tags are visible to users but cannot be assigned by them. Invisible tags are for internal use, since users cannot see or assign them.')); ?></p>
-
- <input type="hidden" name="systemtag" id="systemtag" placeholder="<?php p($l->t('Select tag …')); ?>" />
-
- <h3 id="systemtag_create"><?php p($l->t('Create a new tag')); ?></h3>
-
- <div class="systemtag-input">
- <div class="systemtag-input--name">
- <label for="systemtag_name"><?php p($l->t('Tag name')); ?></label>
- <input type="text" id="systemtag_name" name="systemtag_name" placeholder="<?php p($l->t('Name')); ?>">
- </div>
-
- <div class="systemtag-input--level">
- <label for="systemtag_level"><?php p($l->t('Tag level')); ?></label>
- <select id="systemtag_level">
- <option value="3"><?php p($l->t('Public')); ?></option>
- <option value="2"><?php p($l->t('Restricted')); ?></option>
- <option value="0"><?php p($l->t('Invisible')); ?></option>
- </select>
- </div>
-
- <div class="systemtag-input--actions">
- <a id="systemtag_delete" class="hidden button systemtag-input--actions-button"><span><?php p($l->t('Delete')); ?></span></a>
- <a id="systemtag_reset" class="button systemtag-input--actions-button"><span><?php p($l->t('Reset')); ?></span></a>
- <a id="systemtag_submit" class="button systemtag-input--actions-button"><span><?php p($l->t('Create')); ?></span></a>
- </div>
- </div>
-
-</form>
+<div id="vue-admin-systemtags"></div>
diff --git a/apps/theming/l10n/gl.js b/apps/theming/l10n/gl.js
index 4c072ec0585..62b197db562 100644
--- a/apps/theming/l10n/gl.js
+++ b/apps/theming/l10n/gl.js
@@ -12,6 +12,9 @@ OC.L10N.register(
"The given color is invalid" : "A cor indicada é incorrecta",
"Disable-user-theming should be true or false" : "Disable-user-theming (desactivar o tema do usuario) debe ser verdadeiro ou falso",
"Saved" : "Gardado",
+ "Invalid app given" : "A aplicación non é válida",
+ "Invalid type for setting \"defaultApp\" given" : "O tipo non é válido para o axuste «defaultApp».",
+ "Invalid setting key" : "Chave de axuste incorrecta",
"The file was uploaded" : "O ficheiro foi enviado",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "O ficheiro enviado excede a directiva indicada por upload_max_filesize de php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "O ficheiro enviado excede da directiva MAX_FILE_SIZE especificada no formulario HTML",
@@ -76,6 +79,7 @@ OC.L10N.register(
"Disable all keyboard shortcuts" : "Desactivar todos os atallos de teclado",
"Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "Para nós o acceso universal é moi importante. Seguimos os estándares web e comprobamos que todo poida ser utilizado sen rato e software de axuda como os lectores de pantalla. O noso obxectivo é ter cumprir ás {guidelines}Directrices de accesibilidade ao contido web{linkend} 2.1 a nivel AA, co tema de alto contraste incluso a nivel AAA.",
"If you find any issues, do not hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Se atopa algúnha incidencia, non dubide en informalo no {issuetracker}noso seguidor de incidencias{linkend} E se quere involucrarse, únase ao {designteam}noso equipo de deseño {linkend}!",
+ "Current selected app: {app}, position {position} of {total}" : "Aplicación seleccionada actual: {app}, posición {position} de {total}",
"Move up" : "Mover cara arriba",
"Move down" : "Mover cara abaixo",
"Custom background" : "Fondo personalizado",
@@ -87,7 +91,20 @@ OC.L10N.register(
"No background has been selected" : "Non foi seleccionado ningún fondo",
"Theme selection is enforced" : "Imponse a selección de temas",
"Navigation bar settings" : "Axustes da barra de navegación",
+ "You can configure the app order used for the navigation bar. The first entry will be the default app, opened after login or when clicking on the logo." : "Pode configurar a orde das aplicacións utilizadas na barra de navegación. A primeira entrada será a aplicación predeterminada, abriráse tras de acceder ou ao premer no logotipo.",
+ "The default app can not be changed because it was configured by the administrator." : "A aplicación predeterminada non se pode cambiar porque foi configurada pola administración.",
+ "The app order was changed, to see it in action you have to reload the page." : "Cambiouse a orde das aplicacións, para vela en acción ten que cargar de novo a páxina.",
+ "Reset default app order" : "Restablecer a orde predeterminada das aplicacións",
+ "Could not set the app order" : "Non foi posíbel estabelecer a orde das aplicacións",
+ "Could not reset the app order" : "Non foi posíbel restabelecer a orde das aplicacións",
"Default app" : "Aplicación predeterminada",
+ "The default app is the app that is e.g. opened after login or when the logo in the menu is clicked." : "A aplicación predeterminada é a aplicación que se abre p. ex. após acceder ou cando se preme no logotipo do menú.",
+ "Use custom default app" : "Usar a aplicación predeterminada personalizada",
+ "Global default app" : "Aplicación predeterminada global",
+ "Global default apps" : "Aplicacións predeterminadas globais",
+ "Default app priority" : "Prioridade predeterminada da aplicación",
+ "If an app is not enabled for a user, the next app with lower priority is used." : "Se unha aplicación non está activada para un usuario, utilízase a seguinte aplicación con menor prioridade.",
+ "Could not set global default apps" : "Non foi posíbel estabelecer as aplicacións predeterminadas globais",
"Select a custom color" : "Seleccione unha cor personalizada",
"Reset to default" : "Restabelecer os valores predeterminados",
"Upload" : "Enviar",
diff --git a/apps/theming/l10n/gl.json b/apps/theming/l10n/gl.json
index 29894f16653..b8abd197b2c 100644
--- a/apps/theming/l10n/gl.json
+++ b/apps/theming/l10n/gl.json
@@ -10,6 +10,9 @@
"The given color is invalid" : "A cor indicada é incorrecta",
"Disable-user-theming should be true or false" : "Disable-user-theming (desactivar o tema do usuario) debe ser verdadeiro ou falso",
"Saved" : "Gardado",
+ "Invalid app given" : "A aplicación non é válida",
+ "Invalid type for setting \"defaultApp\" given" : "O tipo non é válido para o axuste «defaultApp».",
+ "Invalid setting key" : "Chave de axuste incorrecta",
"The file was uploaded" : "O ficheiro foi enviado",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" : "O ficheiro enviado excede a directiva indicada por upload_max_filesize de php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" : "O ficheiro enviado excede da directiva MAX_FILE_SIZE especificada no formulario HTML",
@@ -74,6 +77,7 @@
"Disable all keyboard shortcuts" : "Desactivar todos os atallos de teclado",
"Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "Para nós o acceso universal é moi importante. Seguimos os estándares web e comprobamos que todo poida ser utilizado sen rato e software de axuda como os lectores de pantalla. O noso obxectivo é ter cumprir ás {guidelines}Directrices de accesibilidade ao contido web{linkend} 2.1 a nivel AA, co tema de alto contraste incluso a nivel AAA.",
"If you find any issues, do not hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Se atopa algúnha incidencia, non dubide en informalo no {issuetracker}noso seguidor de incidencias{linkend} E se quere involucrarse, únase ao {designteam}noso equipo de deseño {linkend}!",
+ "Current selected app: {app}, position {position} of {total}" : "Aplicación seleccionada actual: {app}, posición {position} de {total}",
"Move up" : "Mover cara arriba",
"Move down" : "Mover cara abaixo",
"Custom background" : "Fondo personalizado",
@@ -85,7 +89,20 @@
"No background has been selected" : "Non foi seleccionado ningún fondo",
"Theme selection is enforced" : "Imponse a selección de temas",
"Navigation bar settings" : "Axustes da barra de navegación",
+ "You can configure the app order used for the navigation bar. The first entry will be the default app, opened after login or when clicking on the logo." : "Pode configurar a orde das aplicacións utilizadas na barra de navegación. A primeira entrada será a aplicación predeterminada, abriráse tras de acceder ou ao premer no logotipo.",
+ "The default app can not be changed because it was configured by the administrator." : "A aplicación predeterminada non se pode cambiar porque foi configurada pola administración.",
+ "The app order was changed, to see it in action you have to reload the page." : "Cambiouse a orde das aplicacións, para vela en acción ten que cargar de novo a páxina.",
+ "Reset default app order" : "Restablecer a orde predeterminada das aplicacións",
+ "Could not set the app order" : "Non foi posíbel estabelecer a orde das aplicacións",
+ "Could not reset the app order" : "Non foi posíbel restabelecer a orde das aplicacións",
"Default app" : "Aplicación predeterminada",
+ "The default app is the app that is e.g. opened after login or when the logo in the menu is clicked." : "A aplicación predeterminada é a aplicación que se abre p. ex. após acceder ou cando se preme no logotipo do menú.",
+ "Use custom default app" : "Usar a aplicación predeterminada personalizada",
+ "Global default app" : "Aplicación predeterminada global",
+ "Global default apps" : "Aplicacións predeterminadas globais",
+ "Default app priority" : "Prioridade predeterminada da aplicación",
+ "If an app is not enabled for a user, the next app with lower priority is used." : "Se unha aplicación non está activada para un usuario, utilízase a seguinte aplicación con menor prioridade.",
+ "Could not set global default apps" : "Non foi posíbel estabelecer as aplicacións predeterminadas globais",
"Select a custom color" : "Seleccione unha cor personalizada",
"Reset to default" : "Restabelecer os valores predeterminados",
"Upload" : "Enviar",
diff --git a/apps/user_status/lib/Db/UserStatus.php b/apps/user_status/lib/Db/UserStatus.php
index 92b3df740c2..f7742a4bbe9 100644
--- a/apps/user_status/lib/Db/UserStatus.php
+++ b/apps/user_status/lib/Db/UserStatus.php
@@ -50,7 +50,7 @@ use OCP\AppFramework\Db\Entity;
* @method void setCustomMessage(string|null $customMessage)
* @method int|null getClearAt()
* @method void setClearAt(int|null $clearAt)
- * @method setIsBackup(bool $true): void
+ * @method setIsBackup(bool $isBackup): void
* @method getIsBackup(): bool
* @method int getStatusMessageTimestamp()
* @method void setStatusMessageTimestamp(int $statusTimestamp)
diff --git a/apps/user_status/lib/Service/StatusService.php b/apps/user_status/lib/Service/StatusService.php
index 508d9287555..99fafaa6426 100644
--- a/apps/user_status/lib/Service/StatusService.php
+++ b/apps/user_status/lib/Service/StatusService.php
@@ -314,7 +314,13 @@ class StatusService {
$userStatus->setCustomIcon(null);
$userStatus->setCustomMessage($customMessage);
$userStatus->setClearAt(null);
- $userStatus->setStatusMessageTimestamp($this->timeFactory->now()->getTimestamp());
+ if ($this->predefinedStatusService->getTranslatedStatusForId($messageId) !== null
+ || ($customMessage !== null && $customMessage !== '')) {
+ // Only track status message ID if there is one
+ $userStatus->setStatusMessageTimestamp($this->timeFactory->now()->getTimestamp());
+ } else {
+ $userStatus->setStatusMessageTimestamp(0);
+ }
if ($userStatus->getId() !== null) {
return $this->mapper->update($userStatus);
diff --git a/apps/user_status/tests/Unit/Service/StatusServiceTest.php b/apps/user_status/tests/Unit/Service/StatusServiceTest.php
index bd150cd4258..d0742a105a3 100644
--- a/apps/user_status/tests/Unit/Service/StatusServiceTest.php
+++ b/apps/user_status/tests/Unit/Service/StatusServiceTest.php
@@ -1130,4 +1130,34 @@ EOF;
$this->assertEquals($status, $this->service->findByUserId('admin'));
}
+
+ public function testSetStatusWithoutMessage(): void {
+ $this->predefinedStatusService->expects(self::once())
+ ->method('isValidId')
+ ->with(IUserStatus::MESSAGE_AVAILABILITY)
+ ->willReturn(true);
+ $this->timeFactory
+ ->method('getTime')
+ ->willReturn(1234);
+ $status = new UserStatus();
+ $status->setUserId('admin');
+ $status->setStatusTimestamp(1234);
+ $status->setIsUserDefined(true);
+ $status->setStatus(IUserStatus::DND);
+ $status->setIsBackup(false);
+ $status->setMessageId(IUserStatus::MESSAGE_AVAILABILITY);
+ $this->mapper->expects(self::once())
+ ->method('insert')
+ ->with($this->equalTo($status))
+ ->willReturnArgument(0);
+
+ $result = $this->service->setUserStatus(
+ 'admin',
+ IUserStatus::DND,
+ IUserStatus::MESSAGE_AVAILABILITY,
+ true,
+ );
+
+ self::assertNotNull($result);
+ }
}
diff --git a/apps/workflowengine/lib/Controller/AWorkflowController.php b/apps/workflowengine/lib/Controller/AWorkflowController.php
index 77e50526092..2c3655743ff 100644
--- a/apps/workflowengine/lib/Controller/AWorkflowController.php
+++ b/apps/workflowengine/lib/Controller/AWorkflowController.php
@@ -30,6 +30,7 @@ namespace OCA\WorkflowEngine\Controller;
use Doctrine\DBAL\Exception;
use OCA\WorkflowEngine\Helper\ScopeContext;
use OCA\WorkflowEngine\Manager;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSException;
@@ -103,6 +104,7 @@ abstract class AWorkflowController extends OCSController {
* @throws OCSForbiddenException
* @throws OCSException
*/
+ #[PasswordConfirmationRequired]
public function create(
string $class,
string $name,
@@ -131,6 +133,7 @@ abstract class AWorkflowController extends OCSController {
* @throws OCSForbiddenException
* @throws OCSException
*/
+ #[PasswordConfirmationRequired]
public function update(
int $id,
string $name,
@@ -159,6 +162,7 @@ abstract class AWorkflowController extends OCSController {
* @throws OCSForbiddenException
* @throws OCSException
*/
+ #[PasswordConfirmationRequired]
public function destroy(int $id): DataResponse {
try {
$deleted = $this->manager->deleteOperation($id, $this->getScopeContext());
diff --git a/apps/workflowengine/lib/Controller/UserWorkflowsController.php b/apps/workflowengine/lib/Controller/UserWorkflowsController.php
index dd2457dd9e8..02c52deb9c7 100644
--- a/apps/workflowengine/lib/Controller/UserWorkflowsController.php
+++ b/apps/workflowengine/lib/Controller/UserWorkflowsController.php
@@ -29,6 +29,7 @@ namespace OCA\WorkflowEngine\Controller;
use OCA\WorkflowEngine\Helper\ScopeContext;
use OCA\WorkflowEngine\Manager;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSForbiddenException;
@@ -84,6 +85,7 @@ class UserWorkflowsController extends AWorkflowController {
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
+ #[PasswordConfirmationRequired]
public function create(string $class, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
return parent::create($class, $name, $checks, $operation, $entity, $events);
}
@@ -93,6 +95,7 @@ class UserWorkflowsController extends AWorkflowController {
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
+ #[PasswordConfirmationRequired]
public function update(int $id, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
return parent::update($id, $name, $checks, $operation, $entity, $events);
}
@@ -101,6 +104,7 @@ class UserWorkflowsController extends AWorkflowController {
* @NoAdminRequired
* @throws OCSForbiddenException
*/
+ #[PasswordConfirmationRequired]
public function destroy(int $id): DataResponse {
return parent::destroy($id);
}
diff --git a/apps/workflowengine/src/store.js b/apps/workflowengine/src/store.js
index 49c881e67b6..6f8905687cf 100644
--- a/apps/workflowengine/src/store.js
+++ b/apps/workflowengine/src/store.js
@@ -89,7 +89,8 @@ const store = new Store({
context.commit('addRule', rule)
})
},
- createNewRule(context, rule) {
+ async createNewRule(context, rule) {
+ await confirmPassword()
let entity = null
let events = []
if (rule.isComplex === false && rule.fixedEntity === '') {
@@ -120,9 +121,7 @@ const store = new Store({
context.commit('removeRule', rule)
},
async pushUpdateRule(context, rule) {
- if (context.state.scope === 0) {
- await confirmPassword()
- }
+ await confirmPassword()
let result
if (rule.id < 0) {
result = await axios.post(getApiUrl(''), rule)