Merge pull request #46082 from nextcloud/chore/enable-eslint

chore: Enable ESLint for apps and fix all errors
This commit is contained in:
Ferdinand Thiessen 2024-07-09 19:04:25 +02:00 committed by GitHub
commit 6d8a7a147c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
255 changed files with 1093 additions and 1281 deletions

View file

@ -36,4 +36,13 @@ module.exports = {
mode: 'typescript',
},
},
overrides: [
// Allow any in tests
{
files: ['**/*.spec.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
},
}
],
}

8
__tests__/mock-window.js Normal file
View file

@ -0,0 +1,8 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
window.OC = { ...window.OC }
window.OCA = { ...window.OCA }
window.OCP = { ...window.OCP }

View file

@ -12,12 +12,12 @@ const client = createClient(getRootPath())
// set CSRF token header
const setHeaders = (token) => {
client.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
client.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
}
// refresh headers when request token changes

View file

@ -60,7 +60,7 @@ const getDirectoryFiles = function(
// Map all items to a consistent output structure (results)
return responseItems.map(item => {
// Each item should contain a stat object
const props = item.propstat!.prop!;
const props = item.propstat!.prop!
return prepareFileFromProps(props, props.id!.toString(), isDetailed)
})

View file

@ -22,7 +22,7 @@ const cancelableRequest = function(request) {
const fetch = async function(url, options) {
const response = await request(
url,
Object.assign({ signal }, options)
Object.assign({ signal }, options),
)
return response
}

View file

@ -26,8 +26,7 @@
:clear-search-on-blur="() => false"
:user-select="true"
:options="options"
@search="asyncFind"
>
@search="asyncFind">
<template #no-options="{ search }">
{{ search ?$t('dav', 'No results.') : $t('dav', 'Start typing.') }}
</template>
@ -51,21 +50,21 @@
</template>
<script>
import { getCurrentUser } from '@nextcloud/auth'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { generateOcsUrl } from '@nextcloud/router'
import { ShareType } from '@nextcloud/sharing'
import { formatDateAsYMD } from '../utils/date.js'
import axios from '@nextcloud/axios'
import debounce from 'debounce'
import logger from '../service/logger.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import debounce from 'debounce'
import axios from '@nextcloud/axios'
import { formatDateAsYMD } from '../utils/date.js'
import { loadState } from '@nextcloud/initial-state'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { Type as ShareTypes } from '@nextcloud/sharing'
import logger from '../service/logger.js'
export default {
name: 'AbsenceForm',
@ -74,17 +73,17 @@ export default {
NcTextField,
NcTextArea,
NcDateTimePickerNative,
NcSelect
NcSelect,
},
data() {
const { firstDay, lastDay, status, message ,replacementUserId ,replacementUserDisplayName } = loadState('dav', 'absence', {})
const { firstDay, lastDay, status, message, replacementUserId, replacementUserDisplayName } = loadState('dav', 'absence', {})
return {
loading: false,
status: status ?? '',
message: message ?? '',
firstDay: firstDay ? new Date(firstDay) : new Date(),
lastDay: lastDay ? new Date(lastDay) : null,
replacementUserId: replacementUserId ,
replacementUserId,
replacementUser: replacementUserId ? { user: replacementUserId, displayName: replacementUserDisplayName } : null,
searchLoading: false,
options: [],
@ -126,10 +125,10 @@ export default {
return {
user: result.uuid || result.value.shareWith,
displayName: result.name || result.label,
subtitle: result.dsc | ''
subtitle: result.dsc | '',
}
},
async asyncFind(query) {
this.searchLoading = true
await this.debounceGetSuggestions(query.trim())
@ -142,7 +141,7 @@ export default {
async getSuggestions(search) {
const shareType = [
ShareTypes.SHARE_TYPE_USER,
ShareType.SHARE_TYPE_USER,
]
let request = null

View file

@ -27,5 +27,5 @@ export const getClient = memoize((service) => {
onRequestTokenUpdate(setHeaders)
setHeaders(getRequestToken())
return client;
return client
})

View file

@ -17,7 +17,7 @@ export async function enableUserStatusAutomation() {
}),
{
configValue: 'yes',
}
},
)
}
@ -29,6 +29,6 @@ export async function disableUserStatusAutomation() {
generateOcsUrl('/apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', {
appId: 'dav',
configKey: 'user_status_automation',
})
}),
)
}

View file

@ -17,12 +17,12 @@ const CalDavSettingsView = new View({
sendInvitations: loadState('dav', 'sendInvitations'),
generateBirthdayCalendar: loadState(
'dav',
'generateBirthdayCalendar'
'generateBirthdayCalendar',
),
sendEventReminders: loadState('dav', 'sendEventReminders'),
sendEventRemindersToSharedUsers: loadState(
'dav',
'sendEventRemindersToSharedUsers'
'sendEventRemindersToSharedUsers',
),
sendEventRemindersPush: loadState('dav', 'sendEventRemindersPush'),
}

View file

@ -54,27 +54,27 @@ describe('CalDavSettings', () => {
},
Vue => {
Vue.prototype.$t = jest.fn((app, text) => text)
}
},
)
expect(TLUtils.container).toMatchSnapshot()
const sendInvitations = TLUtils.getByLabelText(
'Send invitations to attendees'
'Send invitations to attendees',
)
expect(sendInvitations).toBeChecked()
const generateBirthdayCalendar = TLUtils.getByLabelText(
'Automatically generate a birthday calendar'
'Automatically generate a birthday calendar',
)
expect(generateBirthdayCalendar).toBeChecked()
const sendEventReminders = TLUtils.getByLabelText(
'Send notifications for events'
'Send notifications for events',
)
expect(sendEventReminders).toBeChecked()
const sendEventRemindersToSharedUsers = TLUtils.getByLabelText(
'Send reminder notifications to calendar sharees as well'
'Send reminder notifications to calendar sharees as well',
)
expect(sendEventRemindersToSharedUsers).toBeChecked()
const sendEventRemindersPush = TLUtils.getByLabelText(
'Enable notifications for events via push'
'Enable notifications for events via push',
)
expect(sendEventRemindersPush).toBeChecked()

View file

@ -128,7 +128,7 @@ export default {
OCP.AppConfig.setValue(
'dav',
'sendInvitations',
value ? 'yes' : 'no'
value ? 'yes' : 'no',
)
},
sendEventReminders(value) {
@ -138,7 +138,7 @@ export default {
OCP.AppConfig.setValue(
'dav',
'sendEventRemindersToSharedUsers',
value ? 'yes' : 'no'
value ? 'yes' : 'no',
)
},
sendEventRemindersPush(value) {

View file

@ -47,13 +47,14 @@
</template>
<script>
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import axios from '@nextcloud/axios'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
import '@nextcloud/password-confirmation/dist/style.css'
export default {

View file

@ -44,8 +44,8 @@
xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M502 197q-96 0-96.5 1.5t-1.5 137-1.5 138-2 2.5T266 432.5 132.5 390t-30 94T74 578l232 77q21 8 21 10t-79.5 117.5T168 899t79.5 56.5T328 1011t81-110 82-110 41 55l83 115q43 60 44 60t79.5-58 79-59-76-112.5-76-113.5T795 632.5t129.5-44-28-94T867 400t-128 42-128.5 43-2.5-7.5-1-38.5l-3-108q-4-133-5-133.5t-97-.5z" /></svg>
</template>
</NcButton>
<NcButton @click="showHtml = !showHtml"
class="social-button__website-button">
<NcButton class="social-button__website-button"
@click="showHtml = !showHtml">
<template #icon>
<Web :size="20" />
</template>

View file

@ -109,7 +109,7 @@ const processIncomingShareFromUrl = function() {
password,
},
).done(function(data) {
if (data.hasOwnProperty('legacyMount')) {
if (Object.hasOwn(data, 'legacyMount')) {
reloadFilesList()
} else {
window.OC.Notification.showTemporary(data.message)

View file

@ -191,6 +191,7 @@ describe('Delete action execute tests', () => {
jest.spyOn(eventBus, 'emit')
const confirmMock = jest.fn()
// @ts-expect-error We only mock what needed
window.OC = { dialogs: { confirmDestructive: confirmMock } }
const file1 = new File({

View file

@ -126,7 +126,7 @@ export const action = new FileAction({
.every(permission => (permission & Permission.DELETE) !== 0)
},
async exec(node: Node, view: View, dir: string) {
async exec(node: Node) {
try {
await axios.delete(node.encodedSource)

View file

@ -20,7 +20,8 @@ const favoriteView = {
name: 'Favorites',
} as View
global.window.OC = {
window.OC = {
...window.OC,
TAG_FAVORITE: '_$!<Favorite>!$_',
}

View file

@ -101,6 +101,7 @@ describe('Open folder action enabled tests', () => {
describe('Open folder action execute tests', () => {
test('Open folder', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const folder = new Folder({
@ -114,11 +115,12 @@ describe('Open folder action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(goToRouteMock).toBeCalledTimes(1)
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { dir: '/FooBar' })
expect(goToRouteMock).toBeCalledWith(null, { fileid: '1', view: 'files' }, { dir: '/FooBar' })
})
test('Open folder fails without node', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const exec = await action.exec(null as unknown as Node, view, '/')
@ -128,6 +130,7 @@ describe('Open folder action execute tests', () => {
test('Open folder fails without Folder', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const file = new File({

View file

@ -38,7 +38,7 @@ export const action = new FileAction({
window.OCP.Files.Router.goToRoute(
null,
{ view: view.id, fileid: node.fileid },
{ view: view.id, fileid: String(node.fileid) },
{ dir: node.path },
)
return null

View file

@ -43,6 +43,7 @@ describe('Open in files action enabled tests', () => {
describe('Open in files action execute tests', () => {
test('Open in files', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const file = new File({
@ -59,11 +60,12 @@ describe('Open in files action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(goToRouteMock).toBeCalledTimes(1)
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { dir: '/Foo', openfile: 'true' })
expect(goToRouteMock).toBeCalledWith(null, { fileid: '1', view: 'files' }, { dir: '/Foo', openfile: 'true' })
})
test('Open in files with folder', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const file = new Folder({
@ -79,6 +81,6 @@ describe('Open in files action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(goToRouteMock).toBeCalledTimes(1)
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { dir: '/Foo/Bar', openfile: 'true' })
expect(goToRouteMock).toBeCalledWith(null, { fileid: '1', view: 'files' }, { dir: '/Foo/Bar', openfile: 'true' })
})
})

View file

@ -24,7 +24,7 @@ export const action = new FileAction({
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: node.fileid },
{ view: 'files', fileid: String(node.fileid) },
{ dir, openfile: 'true' },
)
return null

View file

@ -110,6 +110,7 @@ describe('Open sidebar action exec tests', () => {
const openMock = jest.fn()
window.OCA = { Files: { Sidebar: { open: openMock } } }
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const file = new File({
@ -125,7 +126,7 @@ describe('Open sidebar action exec tests', () => {
expect(openMock).toBeCalledWith('/foobar.txt')
expect(goToRouteMock).toBeCalledWith(
null,
{ view: view.id, fileid: 1 },
{ view: view.id, fileid: '1' },
{ dir: '/' },
true,
)

View file

@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { Permission, type Node, View, FileAction, FileType } from '@nextcloud/files'
import { Permission, type Node, View, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import InformationSvg from '@mdi/svg/svg/information-variant.svg?raw'
@ -42,7 +42,7 @@ export const action = new FileAction({
// Silently update current fileid
window.OCP.Files.Router.goToRoute(
null,
{ view: view.id, fileid: node.fileid },
{ view: view.id, fileid: String(node.fileid) },
{ ...window.OCP.Files.Router.query, dir },
true,
)

View file

@ -114,6 +114,7 @@ describe('View in folder action enabled tests', () => {
describe('View in folder action execute tests', () => {
test('View in folder', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const file = new File({
@ -128,11 +129,12 @@ describe('View in folder action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(goToRouteMock).toBeCalledTimes(1)
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { dir: '/' })
expect(goToRouteMock).toBeCalledWith(null, { fileid: '1', view: 'files' }, { dir: '/' })
})
test('View in (sub) folder', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const file = new File({
@ -148,11 +150,12 @@ describe('View in folder action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(goToRouteMock).toBeCalledTimes(1)
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { dir: '/Foo/Bar' })
expect(goToRouteMock).toBeCalledWith(null, { fileid: '1', view: 'files' }, { dir: '/Foo/Bar' })
})
test('View in folder fails without node', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const exec = await action.exec(null as unknown as Node, view, '/')
@ -162,6 +165,7 @@ describe('View in folder action execute tests', () => {
test('View in folder fails without File', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const folder = new Folder({

View file

@ -44,7 +44,7 @@ export const action = new FileAction({
window.OCP.Files.Router.goToRoute(
null,
{ view: 'files', fileid: node.fileid },
{ view: 'files', fileid: String(node.fileid) },
{ dir: node.dirname },
)
return null

View file

@ -7,7 +7,7 @@ import type { ComponentPublicInstance, PropType } from 'vue'
import type { FileSource } from '../types.ts'
import { showError } from '@nextcloud/dialogs'
import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node, View } from '@nextcloud/files'
import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { vOnClickOutside } from '@vueuse/components'
@ -179,6 +179,8 @@ export default defineComponent({
/**
* When the source changes, reset the preview
* and fetch the new one.
* @param a
* @param b
*/
source(a: Node, b: Node) {
if (a.source !== b.source) {

View file

@ -134,7 +134,7 @@ export default defineComponent({
},
fileId() {
return parseInt(this.$route.params.fileid) || null
return Number.parseInt(this.$route.params.fileid ?? '0') || null
},
/**

View file

@ -94,12 +94,12 @@ export default {
mounted() {
// If the user has a quota set, warn if the available account storage is <=0
//
// NOTE: This doesn't catch situations where actual *server*
// NOTE: This doesn't catch situations where actual *server*
// disk (non-quota) space is low, but those should probably
// be handled differently anyway since a regular user can't
// can't do much about them (If we did want to indicate server disk
// can't do much about them (If we did want to indicate server disk
// space matters to users, we'd probably want to use a warning
// specific to that situation anyhow. So this covers warning covers
// specific to that situation anyhow. So this covers warning covers
// our primary day-to-day concern (individual account quota usage).
//
if (this.storageStats?.quota > 0 && this.storageStats?.free <= 0) {
@ -121,7 +121,7 @@ export default {
* Update the storage stats
* Throttled at max 1 refresh per minute
*
* @param {Event} [event = null] if user interaction
* @param {Event} [event] if user interaction
*/
async updateStorageStats(event = null) {
if (this.loadingStorageStats) {
@ -135,7 +135,7 @@ export default {
throw new Error('Invalid storage stats')
}
// Warn the user if the available account storage changed from > 0 to 0
// Warn the user if the available account storage changed from > 0 to 0
// (unless only because quota was intentionally set to 0 by admin in the interim)
if (this.storageStats?.free > 0 && response.data.data?.free <= 0 && response.data.data?.quota > 0) {
this.showStorageFullWarning()

View file

@ -9,7 +9,7 @@
<form @submit.prevent="submit">
<p class="transfer-select-row">
<span>{{ readableDirectory }}</span>
<NcButton v-if="directory === undefined"
<NcButton v-if="directory === undefined"
class="transfer-select-row__choose_button"
@click.prevent="start">
{{ t('files', 'Choose file or folder to transfer') }}
@ -22,8 +22,8 @@
<label for="targetUser">
<span>{{ t('files', 'New owner') }}</span>
</label>
<NcSelect input-id="targetUser"
v-model="selectedUser"
<NcSelect v-model="selectedUser"
input-id="targetUser"
:options="formatedUserSuggestions"
:multiple="false"
:loading="loadingUsers"

View file

@ -62,6 +62,10 @@ interface RecycledPoolItem {
item: Node,
}
type DataSource = File | Folder
type DataSourceKey = keyof DataSource
export default Vue.extend({
name: 'VirtualList',
@ -73,11 +77,11 @@ export default Vue.extend({
required: true,
},
dataKey: {
type: String,
type: String as PropType<DataSourceKey>,
required: true,
},
dataSources: {
type: Array as PropType<(File | Folder)[]>,
type: Array as PropType<DataSource[]>,
required: true,
},
extraProps: {
@ -260,7 +264,7 @@ export default Vue.extend({
// Adding scroll listener AFTER the initial scroll to index
this.$el.addEventListener('scroll', this.onScroll, { passive: true })
this.$_recycledPool = {} as Record<string, any>
this.$_recycledPool = {} as Record<string, DataSource[DataSourceKey]>
},
beforeDestroy() {

View file

@ -3,13 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { beforeEach, describe, expect, it, jest } from '@jest/globals'
import { Navigation, View } from '@nextcloud/files'
import nextcloudFiles, { Navigation, View } from '@nextcloud/files'
import { mount } from '@vue/test-utils'
import { defineComponent, nextTick } from 'vue'
import { defineComponent } from 'vue'
import { useNavigation } from './useNavigation'
import nextcloudFiles from '@nextcloud/files'
// Just a wrapper so we can test the composable
const TestComponent = defineComponent({
template: '<div></div>',
@ -38,7 +36,7 @@ describe('Composables: useNavigation', () => {
})
it('should return already active navigation', async () => {
const view = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
const view = new View({ getContents: () => Promise.reject(new Error()), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
navigation.register(view)
navigation.setActive(view)
// Now the navigation is already set it should take the active navigation
@ -47,7 +45,7 @@ describe('Composables: useNavigation', () => {
})
it('should be reactive on updating active navigation', async () => {
const view = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
const view = new View({ getContents: () => Promise.reject(new Error()), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
navigation.register(view)
const wrapper = mount(TestComponent)
@ -72,7 +70,7 @@ describe('Composables: useNavigation', () => {
})
it('should return already registered views', () => {
const view = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
const view = new View({ getContents: () => Promise.reject(new Error()), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
// register before mount
navigation.register(view)
// now mount and check that the view is listed
@ -81,8 +79,8 @@ describe('Composables: useNavigation', () => {
})
it('should be reactive on registering new views', () => {
const view = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
const view2 = new View({ getContents: () => Promise.reject(), icon: '<svg></svg>', id: 'view-2', name: 'My View 2', order: 1 })
const view = new View({ getContents: () => Promise.reject(new Error()), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
const view2 = new View({ getContents: () => Promise.reject(new Error()), icon: '<svg></svg>', id: 'view-2', name: 'My View 2', order: 1 })
// register before mount
navigation.register(view)

View file

@ -24,7 +24,6 @@ import registerPersonalFilesView from './views/personal-files'
import registerFilesView from './views/files'
import registerPreviewServiceWorker from './services/ServiceWorker.js'
import { initLivePhotos } from './services/LivePhotos'
// Register file actions

View file

@ -19,9 +19,10 @@ __webpack_nonce__ = btoa(getRequestToken())
declare global {
interface Window {
OC: any;
OCA: any;
OCP: any;
OC: Nextcloud.v29.OC
OCP: Nextcloud.v29.OCP
// eslint-disable-next-line @typescript-eslint/no-explicit-any
OCA: Record<string, any>
}
}

View file

@ -5,7 +5,7 @@
import type { Node } from '@nextcloud/files'
import { emit } from '@nextcloud/event-bus'
import { getFilePickerBuilder } from '@nextcloud/dialogs';
import { getFilePickerBuilder } from '@nextcloud/dialogs'
import { imagePath } from '@nextcloud/router'
import { translate as t } from '@nextcloud/l10n'
import logger from '../../logger'
@ -19,7 +19,7 @@ function init() {
return
}
logger.info('Initializing unified search plugin: folder search from files app');
logger.info('Initializing unified search plugin: folder search from files app')
OCA.UnifiedSearch.registerFilterAction({
id: 'files',
appId: 'files',

View file

@ -4,6 +4,9 @@
*/
import { Node, registerDavProperty } from '@nextcloud/files'
/**
*
*/
export function initLivePhotos(): void {
registerDavProperty('nc:metadata-files-live-photo', { nc: 'http://nextcloud.org/ns' })
}

View file

@ -8,6 +8,7 @@ const SWCacheName = 'previews'
/**
* Check if the preview is already cached by the service worker
* @param previewUrl
*/
export const isCachedPreview = function(previewUrl: string): Promise<boolean> {
if (!window?.caches?.open) {

View file

@ -14,6 +14,7 @@ export const useDragAndDropStore = defineStore('dragging', {
actions: {
/**
* Set the selection of fileIds
* @param selection
*/
set(selection = [] as FileSource[]) {
Vue.set(this, 'dragging', selection)

View file

@ -34,12 +34,14 @@ export const useFilesStore = function(...args) {
getters: {
/**
* Get a file or folder by its source
* @param state
*/
getNode: (state) => (source: FileSource): Node|undefined => state.files[source],
/**
* Get a list of files or folders by their IDs
* Note: does not return undefined values
* @param state
*/
getNodes: (state) => (sources: FileSource[]): Node[] => sources
.map(source => state.files[source])
@ -49,11 +51,13 @@ export const useFilesStore = function(...args) {
* Get files or folders by their file ID
* Multiple nodes can have the same file ID but different sources
* (e.g. in a shared context)
* @param state
*/
getNodesById: (state) => (fileId: number): Node[] => Object.values(state.files).filter(node => node.fileid === fileId),
/**
* Get the root folder of a service
* @param state
*/
getRoot: (state) => (service: Service): Folder|undefined => state.roots[service],
},

View file

@ -9,6 +9,7 @@ import Vue from 'vue'
* Observe various events and save the current
* special keys states. Useful for checking the
* current status of a key when executing a method.
* @param {...any} args
*/
export const useKeyboardStore = function(...args) {
const store = defineStore('keyboard', {

View file

@ -16,6 +16,7 @@ export const useSelectionStore = defineStore('selection', {
actions: {
/**
* Set the selection of fileIds
* @param selection
*/
set(selection = [] as FileSource[]) {
Vue.set(this, 'selected', [...new Set(selection)])
@ -23,6 +24,7 @@ export const useSelectionStore = defineStore('selection', {
/**
* Set the last selected index
* @param lastSelectedIndex
*/
setLastIndex(lastSelectedIndex = null as number | null) {
// Update the last selection if we provided a new selection starting point

View file

@ -27,6 +27,8 @@ export const useUserConfigStore = function(...args) {
actions: {
/**
* Update the user config local store
* @param key
* @param value
*/
onUpdate(key: string, value: boolean) {
Vue.set(this.userConfig, key, value)
@ -34,6 +36,8 @@ export const useUserConfigStore = function(...args) {
/**
* Update the user config local store AND on server side
* @param key
* @param value
*/
async update(key: string, value: boolean) {
await axios.put(generateUrl('/apps/files/api/v1/config/' + key), {

View file

@ -26,6 +26,9 @@ export const useViewConfigStore = function(...args) {
actions: {
/**
* Update the view config local store
* @param view
* @param key
* @param value
*/
onUpdate(view: ViewId, key: string, value: string | number | boolean) {
if (!this.viewConfig[view]) {
@ -36,6 +39,9 @@ export const useViewConfigStore = function(...args) {
/**
* Update the view config local store AND on server side
* @param view
* @param key
* @param value
*/
async update(view: ViewId, key: string, value: string | number | boolean) {
axios.put(generateUrl(`/apps/files/api/v1/views/${view}/${key}`), {
@ -49,6 +55,8 @@ export const useViewConfigStore = function(...args) {
* Set the sorting key AND sort by ASC
* The key param must be a valid key of a File object
* If not found, will be searched within the File attributes
* @param key
* @param view
*/
setSortingBy(key = 'basename', view = 'files') {
// Save new config
@ -58,6 +66,7 @@ export const useViewConfigStore = function(...args) {
/**
* Toggle the sorting direction
* @param view
*/
toggleSortingDirection(view = 'files') {
const config = this.getConfig(view) || { sorting_direction: 'asc' }

View file

@ -18,7 +18,8 @@ jest.mock('webdav/dist/node/request.js', () => ({
request: jest.fn(),
}))
global.window.OC = {
window.OC = {
...window.OC,
TAG_FAVORITE: '_$!<Favorite>!$_',
}

View file

@ -30,9 +30,10 @@ export const action = new FileAction({
/**
* Use this function to check the storage availability
* We then update the node attributes directly.
* @param node
*/
async renderInline(node: Node) {
let config = null as any as StorageConfig
let config = null as unknown as StorageConfig
try {
const response = await getStatus(node.attributes.id, node.attributes.scope === 'system')
config = response.data

View file

@ -74,6 +74,7 @@ describe('Open in files action enabled tests', () => {
describe('Open in files action execute tests', () => {
test('Open in files', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const storage = new Folder({
@ -98,6 +99,7 @@ describe('Open in files action execute tests', () => {
test('Open in files broken storage', async () => {
const confirmMock = jest.fn()
// @ts-expect-error We only mock what is needed
window.OC = { dialogs: { confirm: confirmMock } }
const storage = new Folder({

View file

@ -20,7 +20,7 @@ describe('OCA.Files_External.App tests', function() {
+ '<div id="app-content-extstoragemounts" class="hidden">'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>',
)
fileList = App.initList($('#app-content-extstoragemounts'))
})

View file

@ -31,7 +31,7 @@ const laterToday: ReminderOption = {
label: t('files_reminders', 'Later today'),
ariaLabel: t('files_reminders', 'Set reminder for later today'),
dateString: '',
verboseDateString: ''
verboseDateString: '',
}
const tomorrow: ReminderOption = {
@ -39,7 +39,7 @@ const tomorrow: ReminderOption = {
label: t('files_reminders', 'Tomorrow'),
ariaLabel: t('files_reminders', 'Set reminder for tomorrow'),
dateString: '',
verboseDateString: ''
verboseDateString: '',
}
const thisWeekend: ReminderOption = {
@ -47,7 +47,7 @@ const thisWeekend: ReminderOption = {
label: t('files_reminders', 'This weekend'),
ariaLabel: t('files_reminders', 'Set reminder for this weekend'),
dateString: '',
verboseDateString: ''
verboseDateString: '',
}
const nextWeek: ReminderOption = {
@ -55,7 +55,7 @@ const nextWeek: ReminderOption = {
label: t('files_reminders', 'Next week'),
ariaLabel: t('files_reminders', 'Set reminder for next week'),
dateString: '',
verboseDateString: ''
verboseDateString: '',
}
/**

View file

@ -4,7 +4,7 @@
*/
export interface FileAttributes {
[key: string]: any
[key: string]: unknown
id: number
name: string
}

View file

@ -5,6 +5,7 @@
import { action } from './acceptShareAction'
import { expect } from '@jest/globals'
import { File, Permission, View, FileAction } from '@nextcloud/files'
import { ShareType } from '@nextcloud/sharing'
import eventBus from '@nextcloud/event-bus'
import axios from '@nextcloud/axios'
import '../main'
@ -103,7 +104,7 @@ describe('Accept share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -130,7 +131,7 @@ describe('Accept share action execute tests', () => {
attributes: {
id: 123,
remote: 3,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -156,7 +157,7 @@ describe('Accept share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -168,7 +169,7 @@ describe('Accept share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 456,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -195,7 +196,7 @@ describe('Accept share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})

View file

@ -57,6 +57,7 @@ describe('Open in files action enabled tests', () => {
describe('Open in files action execute tests', () => {
test('Open in files', async () => {
const goToRouteMock = jest.fn()
// @ts-expect-error We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const file = new File({
@ -72,6 +73,6 @@ describe('Open in files action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(goToRouteMock).toBeCalledTimes(1)
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { dir: '/Foo', openfile: 'true' })
expect(goToRouteMock).toBeCalledWith(null, { fileid: '1', view: 'files' }, { dir: '/Foo', openfile: 'true' })
})
})

View file

@ -26,7 +26,7 @@ export const action = new FileAction({
async exec(node: Node) {
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: node.fileid },
{ view: 'files', fileid: String(node.fileid) },
{ dir: node.dirname, openfile: 'true' },
)
return null

View file

@ -5,6 +5,7 @@
import { action } from './rejectShareAction'
import { expect } from '@jest/globals'
import { File, Folder, Permission, View, FileAction } from '@nextcloud/files'
import { ShareType } from '@nextcloud/sharing'
import eventBus from '@nextcloud/event-bus'
import axios from '@nextcloud/axios'
import '../main'
@ -96,7 +97,7 @@ describe('Reject share action enabled tests', () => {
owner: 'admin',
permissions: Permission.READ,
attributes: {
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
const folder2 = new Folder({
@ -106,7 +107,7 @@ describe('Reject share action enabled tests', () => {
permissions: Permission.READ,
attributes: {
remote_id: 1,
share_type: window.OC.Share.SHARE_TYPE_REMOTE_GROUP,
share_type: ShareType.RemoteGroup,
},
})
@ -130,7 +131,7 @@ describe('Reject share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -157,7 +158,7 @@ describe('Reject share action execute tests', () => {
attributes: {
id: 123,
remote: 3,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -183,7 +184,7 @@ describe('Reject share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -195,7 +196,7 @@ describe('Reject share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 456,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -222,7 +223,7 @@ describe('Reject share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})

View file

@ -8,6 +8,7 @@ import { emit } from '@nextcloud/event-bus'
import { generateOcsUrl } from '@nextcloud/router'
import { registerFileAction, FileAction } from '@nextcloud/files'
import { translatePlural as n } from '@nextcloud/l10n'
import { ShareType } from '@nextcloud/sharing'
import axios from '@nextcloud/axios'
import CloseSvg from '@mdi/svg/svg/close.svg?raw'
@ -30,7 +31,7 @@ export const action = new FileAction({
// disable rejecting group shares from the pending list because they anyway
// land back into that same list after rejecting them
if (nodes.some(node => node.attributes.remote_id
&& node.attributes.share_type === window.OC.Share.SHARE_TYPE_REMOTE_GROUP)) {
&& node.attributes.share_type === ShareType.RemoteGroup)) {
return false
}

View file

@ -5,6 +5,7 @@
import { action } from './restoreShareAction'
import { expect } from '@jest/globals'
import { File, Permission, View, FileAction } from '@nextcloud/files'
import { ShareType } from '@nextcloud/sharing'
import eventBus from '@nextcloud/event-bus'
import axios from '@nextcloud/axios'
import '../main'
@ -103,7 +104,7 @@ describe('Restore share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -129,7 +130,7 @@ describe('Restore share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -141,7 +142,7 @@ describe('Restore share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 456,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})
@ -168,7 +169,7 @@ describe('Restore share action execute tests', () => {
permissions: Permission.READ,
attributes: {
id: 123,
share_type: window.OC.Share.SHARE_TYPE_USER,
share_type: ShareType.User,
},
})

View file

@ -27,15 +27,14 @@
</template>
<script>
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { getCapabilities } from '@nextcloud/capabilities'
import { generateOcsUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import debounce from 'debounce'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import Config from '../services/ConfigService.js'
import GeneratePassword from '../utils/GeneratePassword.js'
import Share from '../models/Share.js'
import ShareRequests from '../mixins/ShareRequests.js'
import ShareTypes from '../mixins/ShareTypes.js'

View file

@ -4,10 +4,6 @@
*/
// register default shares types
if (!window.OC) {
window.OC = {}
}
Object.assign(window.OC, {
Share: {
SHARE_TYPE_USER: 0,

View file

@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import Share from '../models/Share.js'
import Config from '../services/ConfigService.js'

View file

@ -12,7 +12,8 @@ import { getContents } from './SharingService'
import { File, Folder } from '@nextcloud/files'
import logger from './logger'
global.window.OC = {
window.OC = {
...window.OC,
TAG_FAVORITE: '_$!<Favorite>!$_',
}

View file

@ -2,7 +2,9 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/* eslint-disable camelcase, n/no-extraneous-import */
// TODO: Fix this instead of disabling ESLint!!!
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { AxiosPromise } from '@nextcloud/axios'
import type { OCSResponse } from '@nextcloud/typings/ocs'
@ -71,7 +73,7 @@ const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | nu
'owner-id': ocsEntry?.uid_owner,
'owner-display-name': ocsEntry?.displayname_owner,
'share-types': ocsEntry?.share_type,
favorite: ocsEntry?.tags?.includes(window.OC.TAG_FAVORITE) ? 1 : 0,
favorite: ocsEntry?.tags?.includes((window.OC as Nextcloud.v28.OC & { TAG_FAVORITE: string }).TAG_FAVORITE) ? 1 : 0,
},
})
} catch (error) {
@ -80,12 +82,12 @@ const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | nu
}
}
const getShares = function(shared_with_me = false): AxiosPromise<OCSResponse<any>> {
const getShares = function(shareWithMe = false): AxiosPromise<OCSResponse<any>> {
const url = generateOcsUrl('apps/files_sharing/api/v1/shares')
return axios.get(url, {
headers,
params: {
shared_with_me,
shared_with_me: shareWithMe,
include_tags: true,
},
})
@ -142,6 +144,8 @@ const getDeletedShares = function(): AxiosPromise<OCSResponse<any>> {
/**
* Group an array of objects (here Nodes) by a key
* and return an array of arrays of them.
* @param nodes
* @param key
*/
const groupBy = function(nodes: (Folder | File)[], key: string) {
return Object.values(nodes.reduce(function(acc, curr) {

View file

@ -4,6 +4,7 @@
*/
import { translate as t } from '@nextcloud/l10n'
import { View, getNavigation } from '@nextcloud/files'
import { ShareType } from '@nextcloud/sharing'
import AccountClockSvg from '@mdi/svg/svg/account-clock.svg?raw'
import AccountGroupSvg from '@mdi/svg/svg/account-group.svg?raw'
import AccountPlusSvg from '@mdi/svg/svg/account-plus.svg?raw'
@ -86,7 +87,7 @@ export default () => {
columns: [],
getContents: () => getContents(false, true, false, false, [window.OC.Share.SHARE_TYPE_LINK]),
getContents: () => getContents(false, true, false, false, [ShareType.Link]),
}))
Navigation.register(new View({

View file

@ -14,12 +14,12 @@ const client = createClient(rootUrl)
// set CSRF token header
const setHeaders = (token: string | null) => {
client.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
client.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
}
// refresh headers when request token changes

View file

@ -14,16 +14,16 @@ const client = createClient(remote)
// set CSRF token header
const setHeaders = (token) => {
client.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
client.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
}
// refresh headers when request token changes
onRequestTokenUpdate(setHeaders)
setHeaders(getRequestToken())
export default client
export default client

View file

@ -102,7 +102,7 @@ export default {
methods: {
deleteClient(id) {
axios.delete(generateUrl('apps/oauth2/clients/{id}', { id }))
.then((response) => {
.then(() => {
// eslint-disable-next-line vue/no-mutating-props
this.clients = this.clients.filter(client => client.id !== id)
})

View file

@ -14,7 +14,7 @@
:aria-label="toggleAriaLabel"
@click="toggleSecret">
<template #icon>
<EyeOutline :size="20"/>
<EyeOutline :size="20" />
</template>
</NcButton>
</div>
@ -72,9 +72,9 @@ export default {
toggleAriaLabel() {
if (!this.renderSecret) {
return t('oauth2', 'Show client secret')
}
}
return t('oauth2', 'Hide client secret')
}
},
},
methods: {
toggleSecret() {

View file

@ -62,18 +62,24 @@
<label>{{ t('settings', 'Limit sharing based on groups') }}</label>
<div class="sharing__sub-section">
<NcCheckboxRadioSwitch :checked.sync="settings.excludeGroups"
name="excludeGroups" value="no"
type="radio" @update:checked="onUpdateExcludeGroups">
name="excludeGroups"
value="no"
type="radio"
@update:checked="onUpdateExcludeGroups">
{{ t('settings', 'Allow sharing for everyone (default)') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch :checked.sync="settings.excludeGroups"
name="excludeGroups" value="yes"
type="radio" @update:checked="onUpdateExcludeGroups">
name="excludeGroups"
value="yes"
type="radio"
@update:checked="onUpdateExcludeGroups">
{{ t('settings', 'Exclude some groups from sharing') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch :checked.sync="settings.excludeGroups"
name="excludeGroups" value="allow"
type="radio" @update:checked="onUpdateExcludeGroups">
name="excludeGroups"
value="allow"
type="radio"
@update:checked="onUpdateExcludeGroups">
{{ t('settings', 'Limit sharing to some groups') }}
</NcCheckboxRadioSwitch>
<div v-show="settings.excludeGroups !== 'no'" class="sharing__labeled-entry sharing__input">
@ -305,7 +311,7 @@ export default defineComponent({
onUpdateExcludeGroups: debounce(function(value: string) {
window.OCP.AppConfig.setValue('core', 'excludeGroups', value)
this.settings.excludeGroups = value
}, 500) as (v?: string) => void
}, 500) as (v?: string) => void,
},
})
</script>

View file

@ -63,13 +63,15 @@
<script>
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import axios from '@nextcloud/axios'
import moment from '@nextcloud/moment'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import moment from '@nextcloud/moment'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import '@nextcloud/password-confirmation/dist/style.css'
const lastCron = loadState('settings', 'lastCron')

View file

@ -3,14 +3,13 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<NcSettingsSection
class="declarative-settings-section"
<NcSettingsSection class="declarative-settings-section"
:name="t(formApp, form.title)"
:description="t(formApp, form.description)"
:doc-url="form.doc_url || ''">
<div v-for="formField in formFields"
:key="formField.id"
class="declarative-form-field"
:key="formField.id"
class="declarative-form-field"
:aria-label="t('settings', '{app}\'s declarative setting field: {name}', { app: formApp, name: t(formApp, formField.title) })"
:class="{
'declarative-form-field-text': isTextFormField(formField),
@ -20,16 +19,14 @@
'declarative-form-field-multi_checkbox': formField.type === 'multi-checkbox',
'declarative-form-field-radio': formField.type === 'radio'
}">
<template v-if="isTextFormField(formField)">
<div class="input-wrapper">
<NcInputField
:type="formField.type"
<NcInputField :type="formField.type"
:label="t(formApp, formField.title)"
:value.sync="formFieldsData[formField.id].value"
:placeholder="t(formApp, formField.placeholder)"
@update:value="onChangeDebounced(formField)"
@submit="updateDeclarativeSettingsValue(formField)"/>
@submit="updateDeclarativeSettingsValue(formField)" />
</div>
<span class="hint">{{ t(formApp, formField.description) }}</span>
</template>
@ -37,13 +34,12 @@
<template v-if="formField.type === 'select'">
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label>
<div class="input-wrapper">
<NcSelect
:id="formField.id + '_field'"
<NcSelect :id="formField.id + '_field'"
:options="formField.options"
:placeholder="t(formApp, formField.placeholder)"
:label-outside="true"
:value="formFieldsData[formField.id].value"
@input="(value) => updateFormFieldDataValue(value, formField, true)"/>
@input="(value) => updateFormFieldDataValue(value, formField, true)" />
</div>
<span class="hint">{{ t(formApp, formField.description) }}</span>
</template>
@ -51,8 +47,7 @@
<template v-if="formField.type === 'multi-select'">
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label>
<div class="input-wrapper">
<NcSelect
:id="formField.id + '_field'"
<NcSelect :id="formField.id + '_field'"
:options="formField.options"
:placeholder="t(formApp, formField.placeholder)"
:multiple="true"
@ -62,21 +57,20 @@
formFieldsData[formField.id].value = value
updateDeclarativeSettingsValue(formField, JSON.stringify(formFieldsData[formField.id].value))
}
"/>
" />
</div>
<span class="hint">{{ t(formApp, formField.description) }}</span>
</template>
<template v-if="formField.type === 'checkbox'">
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label>
<NcCheckboxRadioSwitch
:id="formField.id + '_field'"
<NcCheckboxRadioSwitch :id="formField.id + '_field'"
:checked="Boolean(formFieldsData[formField.id].value)"
@update:checked="(value) => {
formField.value = value
updateFormFieldDataValue(+value, formField, true)
}
">
">
{{ t(formApp, formField.label) }}
</NcCheckboxRadioSwitch>
<span class="hint">{{ t(formApp, formField.description) }}</span>
@ -84,8 +78,7 @@
<template v-if="formField.type === 'multi-checkbox'">
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label>
<NcCheckboxRadioSwitch
v-for="option in formField.options"
<NcCheckboxRadioSwitch v-for="option in formField.options"
:id="formField.id + '_field_' + option.value"
:key="option.value"
:checked="formFieldsData[formField.id].value[option.value]"
@ -94,7 +87,7 @@
// Update without re-generating initial formFieldsData.value object as the link to components are lost
updateDeclarativeSettingsValue(formField, JSON.stringify(formFieldsData[formField.id].value))
}
">
">
{{ t(formApp, option.name) }}
</NcCheckboxRadioSwitch>
<span class="hint">{{ t(formApp, formField.description) }}</span>
@ -102,8 +95,7 @@
<template v-if="formField.type === 'radio'">
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label>
<NcCheckboxRadioSwitch
v-for="option in formField.options"
<NcCheckboxRadioSwitch v-for="option in formField.options"
:key="option.value"
:value="option.value"
type="radio"
@ -146,9 +138,6 @@ export default {
formFieldsData: {},
}
},
beforeMount() {
this.initFormFieldsData()
},
computed: {
formApp() {
return this.form.app || ''
@ -157,6 +146,9 @@ export default {
return this.form.fields || []
},
},
beforeMount() {
this.initFormFieldsData()
},
methods: {
initFormFieldsData() {
this.form.fields.forEach((formField) => {
@ -175,7 +167,7 @@ export default {
this.$set(formField, 'value', JSON.parse(formField.value))
// Merge possible new options
formField.options.forEach(option => {
if (!formField.value.hasOwnProperty(option.value)) {
if (!Object.prototype.hasOwnProperty.call(formField.value, option.value)) {
this.$set(formField.value, option.value, false)
}
})
@ -216,7 +208,7 @@ export default {
formId: this.form.id.replace(this.formApp + '_', ''), // Remove app prefix to send clean form id
fieldId: formField.id,
value: value === null ? this.formFieldsData[formField.id].value : value,
});
})
} catch (err) {
console.debug(err)
showError(t('settings', 'Failed to save setting'))

View file

@ -59,22 +59,18 @@
</template>
<script>
import { showError } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import axios from '@nextcloud/axios'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
import { loadState } from '@nextcloud/initial-state'
import { getLoggerBuilder } from '@nextcloud/logger'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import logger from '../logger'
import '@nextcloud/password-confirmation/dist/style.css'
import { showError } from '@nextcloud/dialogs'
const logger = getLoggerBuilder()
.setApp('settings')
.detectUser()
.build()
export default {
name: 'Encryption',
@ -122,7 +118,7 @@ export default {
try {
const { data } = await axios.post(url, {
value: value,
value,
})
this.handleResponse({
status: data.ocs?.meta?.status,

View file

@ -23,15 +23,15 @@
</template>
<script>
import HeaderBar from './shared/HeaderBar.vue'
import AccountPropertySection from './shared/AccountPropertySection.vue'
import { loadState } from '@nextcloud/initial-state'
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
import { NcDateTimePickerNative } from '@nextcloud/vue'
import debounce from 'debounce'
import { savePrimaryAccountProperty } from '../../service/PersonalInfo/PersonalInfoService'
import { handleError } from '../../utils/handlers'
import AlertCircle from 'vue-material-design-icons/AlertCircleOutline.vue'
import { loadState } from '@nextcloud/initial-state'
import debounce from 'debounce'
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
import HeaderBar from './shared/HeaderBar.vue'
const { birthdate } = loadState('settings', 'personalInfoParameters', {})
@ -39,8 +39,6 @@ export default {
name: 'BirthdaySection',
components: {
AlertCircle,
AccountPropertySection,
NcDateTimePickerNative,
HeaderBar,
},
@ -74,7 +72,7 @@ export default {
const month = (value.getMonth() + 1).toString().padStart(2, '0')
const year = value.getFullYear()
this.birthdate.value = `${year}-${month}-${day}`
}
},
},
},

View file

@ -288,7 +288,7 @@ import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import UserRowActions from './UserRowActions.vue'
import UserRowMixin from '../../mixins/UserRowMixin.js'
import { isObfuscated, unlimitedQuota } from '../../utils/userUtils.ts';
import { isObfuscated, unlimitedQuota } from '../../utils/userUtils.ts'
export default {
name: 'UserRow',

View file

@ -59,6 +59,8 @@ import {
finishRegistration,
} from '../../service/WebAuthnRegistrationSerice.ts'
import '@nextcloud/password-confirmation/dist/style.css'
const logAndPass = (text) => (data) => {
logger.debug(text)
return data

View file

@ -38,7 +38,6 @@
import { browserSupportsWebAuthn } from '@simplewebauthn/browser'
import { confirmPassword } from '@nextcloud/password-confirmation'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import '@nextcloud/password-confirmation/dist/style.css'
import sortBy from 'lodash/fp/sortBy.js'
import AddDevice from './AddDevice.vue'
@ -46,6 +45,8 @@ import Device from './Device.vue'
import logger from '../../logger.ts'
import { removeRegistration } from '../../service/WebAuthnRegistrationSerice.js'
import '@nextcloud/password-confirmation/dist/style.css'
const sortByName = sortBy('name')
export default {

View file

@ -2,10 +2,12 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import Vue from 'vue';
import { loadState } from '@nextcloud/initial-state';
import { translate as t, translatePlural as n } from '@nextcloud/l10n';
import DeclarativeSection from './components/DeclarativeSettings/DeclarativeSection.vue';
import type { ComponentInstance } from 'vue'
import { loadState } from '@nextcloud/initial-state'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import Vue from 'vue'
import DeclarativeSection from './components/DeclarativeSettings/DeclarativeSection.vue'
interface DeclarativeFormField {
id: string,
@ -14,9 +16,9 @@ interface DeclarativeFormField {
type: string,
placeholder: string,
label: string,
options: Array<any>|null,
value: any,
default: any,
options: Array<unknown>|null,
value: unknown,
default: unknown,
}
interface DeclarativeForm {
@ -32,23 +34,28 @@ interface DeclarativeForm {
fields: Array<DeclarativeFormField>,
}
const forms = loadState('settings', 'declarative-settings-forms', []) as Array<DeclarativeForm>;
console.debug('Loaded declarative forms:', forms);
const forms = loadState('settings', 'declarative-settings-forms', []) as Array<DeclarativeForm>
console.debug('Loaded declarative forms:', forms)
function renderDeclarativeSettingsSections(forms: Array<DeclarativeForm>): void {
/**
*
* @param forms
*/
function renderDeclarativeSettingsSections(forms: Array<DeclarativeForm>): ComponentInstance[] {
Vue.mixin({ methods: { t, n } })
const DeclarativeSettingsSection = Vue.extend(<any>DeclarativeSection);
for (const form of forms) {
const DeclarativeSettingsSection = Vue.extend(DeclarativeSection as never)
return forms.map((form) => {
const el = `#${form.app}_${form.id}`
new DeclarativeSettingsSection({
el: el,
return new DeclarativeSettingsSection({
el,
propsData: {
form,
},
})
}
})
}
document.addEventListener('DOMContentLoaded', () => {
renderDeclarativeSettingsSections(forms);
});
renderDeclarativeSettingsSections(forms)
})

View file

@ -3,14 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import axios from '@nextcloud/axios'
import { getCurrentUser } from '@nextcloud/auth'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import '@nextcloud/password-confirmation/dist/style.css'
import axios from '@nextcloud/axios'
import { ACCOUNT_PROPERTY_ENUM, SCOPE_SUFFIX } from '../../constants/AccountPropertyConstants.js'
import '@nextcloud/password-confirmation/dist/style.css'
/**
* Save the primary email of the user
*

View file

@ -3,14 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import axios from '@nextcloud/axios'
import { getCurrentUser } from '@nextcloud/auth'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import '@nextcloud/password-confirmation/dist/style.css'
import axios from '@nextcloud/axios'
import { SCOPE_SUFFIX } from '../../constants/AccountPropertyConstants.js'
import '@nextcloud/password-confirmation/dist/style.css'
/**
* Save the primary account property value for the user
*

View file

@ -184,7 +184,7 @@ const actions = {
showInfo(
t(
'settings',
'The app has been enabled but needs to be updated. You will be redirected to the update page in 5 seconds.'
'The app has been enabled but needs to be updated. You will be redirected to the update page in 5 seconds.',
),
{
onClick: () => window.location.reload(),

View file

@ -12,6 +12,8 @@ import { defineStore } from 'pinia'
import axios from '@nextcloud/axios'
import logger from '../logger'
import '@nextcloud/password-confirmation/dist/style.css'
const BASE_URL = generateUrl('/settings/personal/authtokens')
const confirm = () => {

View file

@ -390,6 +390,7 @@ const actions = {
* @param {object} options destructuring object
* @param {number} options.offset List offset to request
* @param {number} options.limit List number to return from offset
* @param options.search
* @return {Promise<number>}
*/
async getDisabledUsers(context, { offset, limit, search }) {

View file

@ -3,6 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { translate as t } from '@nextcloud/l10n'
export const unlimitedQuota = {
id: 'none',
label: t('settings', 'Unlimited'),
@ -19,7 +21,7 @@ export const defaultQuota = {
* @param user
* @param user.id
*/
export const isObfuscated = (user: { id: string, [key: string]: any }) => {
export const isObfuscated = (user: { id: string, [key: string]: unknown }) => {
const keys = Object.keys(user)
return keys.length === 1 && keys.at(0) === 'id'
}

View file

@ -21,13 +21,14 @@
</template>
<script>
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import axios from '@nextcloud/axios'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
import '@nextcloud/password-confirmation/dist/style.css'
export default {

View file

@ -52,6 +52,7 @@ export const fetchLastUsedTagIds = async (): Promise<number[]> => {
}
/**
* @param tag
* @return created tag id
*/
export const createTag = async (tag: Tag | ServerTag): Promise<number> => {

View file

@ -13,12 +13,12 @@ export const davClient = createClient(rootUrl)
// set CSRF token header
const setHeaders = (token: string | null) => {
davClient.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
davClient.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
}
// refresh headers when request token changes

View file

@ -27,6 +27,8 @@ export const fetchTagsForFile = async (fileId: number): Promise<TagWithId[]> =>
}
/**
* @param tag
* @param fileId
* @return created tag id
*/
export const createTagForFile = async (tag: Tag, fileId: number): Promise<number> => {

View file

@ -45,12 +45,13 @@ export const parseIdFromLocation = (url: string): number => {
}
export const formatTag = (initialTag: Tag | ServerTag): ServerTag => {
const tag: any = { ...initialTag }
if (tag.name && !tag.displayName) {
return tag
if ('name' in initialTag && !('displayName' in initialTag)) {
return { ...initialTag }
}
const tag: Record<string, unknown> = { ...initialTag }
tag.name = tag.displayName
delete tag.displayName
return tag
return tag as unknown as ServerTag
}

View file

@ -96,7 +96,7 @@ const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
const backgroundImage = loadState('theming', 'userBackgroundImage')
const {
backgroundImage: defaultBackgroundImage,
backgroundColor: defaultBackgroundColor,
// backgroundColor: defaultBackgroundColor,
backgroundMime: defaultBackgroundMime,
defaultShippedBackground,
} = loadState('theming', 'themingDefaults')

View file

@ -7,7 +7,9 @@
<div class="theming__preview-image" :style="{ backgroundImage: 'url(' + img + ')' }" @click="onToggle" />
<div class="theming__preview-description">
<h3>{{ theme.title }}</h3>
<p class="theming__preview-explanation">{{ theme.description }}</p>
<p class="theming__preview-explanation">
{{ theme.description }}
</p>
<span v-if="enforced" class="theming__preview-warning" role="note">
{{ t('theming', 'Theme selection is enforced') }}
</span>

View file

@ -7,15 +7,17 @@
<div class="field">
<label :for="id">{{ displayName }}</label>
<div class="field__row">
<NcCheckboxRadioSwitch type="switch"
:id="id"
<NcCheckboxRadioSwitch :id="id"
type="switch"
:checked.sync="localValue"
@update:checked="save">
{{ label }}
</NcCheckboxRadioSwitch>
</div>
<p class="field__description">{{ description }}</p>
<p class="field__description">
{{ description }}
</p>
<NcNoteCard v-if="errorMessage"
type="error"

View file

@ -7,8 +7,8 @@
<div class="field">
<label :for="id">{{ displayName }}</label>
<div class="field__row">
<NcButton type="secondary"
:id="id"
<NcButton :id="id"
type="secondary"
:aria-label="ariaLabel"
data-admin-theming-setting-file-picker
@click="activateLocalFilePicker">

View file

@ -49,9 +49,10 @@
<script>
import { confirmPassword } from '@nextcloud/password-confirmation'
import '@nextcloud/password-confirmation/dist/style.css'
import { print } from '../service/PrintService.js'
import '@nextcloud/password-confirmation/dist/style.css'
export default {
name: 'PersonalSettings',
data() {
@ -97,7 +98,7 @@ export default {
// Hide old codes
this.generatingCodes = true
this.$store.dispatch('generate').then(data => {
this.$store.dispatch('generate').then(() => {
this.generatingCodes = false
}).catch(err => {
OC.Notification.showTemporary(t('twofactor_backupcodes', 'An error occurred while generating your backup codes'))

View file

@ -132,7 +132,7 @@ export default {
this.$set(this.rule, 'operation', operation)
await this.updateRule()
},
validate(state) {
validate(/* state */) {
this.error = null
this.$store.dispatch('updateRule', this.rule)
},

View file

@ -6,10 +6,11 @@
import Vue from 'vue'
import Vuex, { Store } from 'vuex'
import axios from '@nextcloud/axios'
import { getApiUrl } from './helpers/api.js'
import { confirmPassword } from '@nextcloud/password-confirmation'
import '@nextcloud/password-confirmation/dist/style.css'
import { loadState } from '@nextcloud/initial-state'
import { getApiUrl } from './helpers/api.js'
import '@nextcloud/password-confirmation/dist/style.css'
Vue.use(Vuex)

View file

@ -5,7 +5,7 @@
module.exports = {
plugins: [
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-transform-class-properties',
// We need the bundler entry not the web one
// Jest will otherwise resolve the wrong one
[

View file

@ -104,7 +104,7 @@ export const hideMenus = function(complete) {
/**
* Shows a given element as menu
*
* @param {object} [$toggle=null] menu toggle
* @param {object} [$toggle] menu toggle
* @param {object} $menuEl menu element
* @param {Function} complete callback when the showing animation is done
*/

View file

@ -76,7 +76,7 @@ export default {
* @param {string} html Message to display
* @param {object} [options] options
* @param {string} [options.type] notification type
* @param {number} [options.timeout=0] timeout value, defaults to 0 (permanent)
* @param {number} [options.timeout] timeout value, defaults to 0 (permanent)
* @return {jQuery} jQuery element for notification row
* @deprecated 17.0.0 use the `@nextcloud/dialogs` package
*/
@ -95,7 +95,7 @@ export default {
* @param {string} text Message to display
* @param {object} [options] options
* @param {string} [options.type] notification type
* @param {number} [options.timeout=0] timeout value, defaults to 0 (permanent)
* @param {number} [options.timeout] timeout value, defaults to 0 (permanent)
* @return {jQuery} jQuery element for notification row
* @deprecated 17.0.0 use the `@nextcloud/dialogs` package
*/
@ -138,8 +138,8 @@ export default {
*
* @param {string} text Message to show
* @param {Array} [options] options array
* @param {number} [options.timeout=7] timeout in seconds, if this is 0 it will show the message permanently
* @param {boolean} [options.isHTML=false] an indicator for HTML notifications (true) or text (false)
* @param {number} [options.timeout] timeout in seconds, if this is 0 it will show the message permanently
* @param {boolean} [options.isHTML] an indicator for HTML notifications (true) or text (false)
* @param {string} [options.type] notification type
* @return {JQuery} the toast element
* @deprecated 17.0.0 use the `@nextcloud/dialogs` package

View file

@ -27,7 +27,7 @@ export default {
* or a map
* @param {string} [url] URL to be used, otherwise the current URL will be used,
* using the params as query string
* @param {boolean} [replace=false] whether to replace instead of pushing
* @param {boolean} [replace] whether to replace instead of pushing
*/
_pushState(params, url, replace) {
let strParams

View file

@ -47,7 +47,7 @@ export const processAjaxError = xhr => {
OC.reload()
}
timer++
}, 1000 // 1 second interval
}, 1000, // 1 second interval
)
// only call reload once

View file

@ -9,6 +9,6 @@
export default function getURLParameter(name) {
return decodeURIComponent(
// eslint-disable-next-line no-sparse-arrays
(new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [, ''])[1].replace(/\+/g, '%20')
(new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [, ''])[1].replace(/\+/g, '%20'),
) || ''
}

View file

@ -78,7 +78,7 @@ export default {
}
}
return undefined
}
},
},
}
</script>

View file

@ -111,7 +111,7 @@ export default {
loadingApps: true,
loadingAppsError: false,
apps: [],
defaultPageUrl: loadState('core', 'defaultPageUrl')
defaultPageUrl: loadState('core', 'defaultPageUrl'),
}
},
computed: {

View file

@ -89,7 +89,7 @@ const Template = {
function(a, b) {
const r = o[b]
return typeof r === 'string' || typeof r === 'number' ? r : a
}
},
)
} catch (e) {
console.error(e, 'data:', data)

Some files were not shown because too many files have changed in this diff Show more