chore: Enable ESLint for apps and fix all errors

Nevertheless this causes a huge amount of new warnings.
Previously the shell script for directories to lint was wrong it was generating all app names to lint,
but was missing the `apps/` prefix. Causing only `core` to be linted.

Co-authored-by: Grigorii K. Shartsev <me@shgk.me>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2024-06-25 00:00:31 +02:00
Родитель 3a97dbf248
Коммит 691f570237
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 45FAE7268762B400
88 изменённых файлов: 807 добавлений и 778 удалений

Просмотреть файл

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

3
__tests__/mock-window.js Normal file
Просмотреть файл

@ -0,0 +1,3 @@
window.OC = { ...window.OC }
window.OCA = { ...window.OCA }
window.OCP = { ...window.OCP }

Просмотреть файл

@ -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

Просмотреть файл

@ -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)
})

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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

Просмотреть файл

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

Просмотреть файл

@ -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',
})
}),
)
}

Просмотреть файл

@ -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'),
}

Просмотреть файл

@ -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()

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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>

Просмотреть файл

@ -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)

Просмотреть файл

@ -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)

Просмотреть файл

@ -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'

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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()

Просмотреть файл

@ -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"

Просмотреть файл

@ -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() {

Просмотреть файл

@ -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)

Просмотреть файл

@ -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

Просмотреть файл

@ -19,9 +19,9 @@ __webpack_nonce__ = btoa(getRequestToken())
declare global {
interface Window {
OC: any;
OCA: any;
OCP: any;
OC: Nextcloud.v28.OC;
OCA: Record<string, unknown>;
OCP: Nextcloud.v28.OCP;
}
}

Просмотреть файл

@ -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',

Просмотреть файл

@ -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' })
}

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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)

Просмотреть файл

@ -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],
},

Просмотреть файл

@ -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', {

Просмотреть файл

@ -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

Просмотреть файл

@ -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), {

Просмотреть файл

@ -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' }

Просмотреть файл

@ -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

Просмотреть файл

@ -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'))
})

Просмотреть файл

@ -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: '',
}
/**

Просмотреть файл

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

Просмотреть файл

@ -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'

Просмотреть файл

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

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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)
})

Просмотреть файл

@ -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() {

Просмотреть файл

@ -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>

Просмотреть файл

@ -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')

Просмотреть файл

@ -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'))

Просмотреть файл

@ -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,

Просмотреть файл

@ -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}`
}
},
},
},

Просмотреть файл

@ -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',

Просмотреть файл

@ -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

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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)
})

Просмотреть файл

@ -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
*

Просмотреть файл

@ -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
*

Просмотреть файл

@ -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(),

Просмотреть файл

@ -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 = () => {

Просмотреть файл

@ -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 }) {

Просмотреть файл

@ -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'
}

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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> => {

Просмотреть файл

@ -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

Просмотреть файл

@ -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> => {

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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')

Просмотреть файл

@ -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>

Просмотреть файл

@ -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"

Просмотреть файл

@ -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">

Просмотреть файл

@ -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'))

Просмотреть файл

@ -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)
},

Просмотреть файл

@ -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)

Просмотреть файл

@ -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
[

Просмотреть файл

@ -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
*/

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

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

Просмотреть файл

@ -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'),
) || ''
}

Просмотреть файл

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

Просмотреть файл

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

2
core/src/jquery/octemplate.js поставляемый
Просмотреть файл

@ -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)

Просмотреть файл

@ -52,8 +52,8 @@ const getInterval = () => {
24 * 3600,
Math.max(
60,
isNaN(interval) ? 900 : interval
)
isNaN(interval) ? 900 : interval,
),
)
}

Просмотреть файл

@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-or-later
* @param OC
*/
(function(OC) {

Просмотреть файл

@ -17,19 +17,19 @@ describe('Contact', function() {
topAction: {
title: 'Mail',
icon: 'icon-mail',
hyperlink: 'mailto:deboraoliver%40centrexin.com'
hyperlink: 'mailto:deboraoliver%40centrexin.com',
},
emailAddresses: [],
actions: [
{
title: 'Mail',
icon: 'icon-mail',
hyperlink: 'mailto:mathisholland%40virxo.com'
hyperlink: 'mailto:mathisholland%40virxo.com',
},
{
title: 'Details',
icon: 'icon-info',
hyperlink: 'https://localhost/index.php/apps/contacts'
hyperlink: 'https://localhost/index.php/apps/contacts',
},
],
lastMessage: '',

Просмотреть файл

@ -82,19 +82,19 @@ describe('ContactsMenu', function() {
topAction: {
title: 'Mail',
icon: 'icon-mail',
hyperlink: 'mailto:deboraoliver%40centrexin.com'
hyperlink: 'mailto:deboraoliver%40centrexin.com',
},
actions: [
{
title: 'Mail',
icon: 'icon-mail',
hyperlink: 'mailto:mathisholland%40virxo.com'
hyperlink: 'mailto:mathisholland%40virxo.com',
},
{
title: 'Details',
icon: 'icon-info',
hyperlink: 'https://localhost/index.php/apps/contacts'
}
hyperlink: 'https://localhost/index.php/apps/contacts',
},
],
lastMessage: '',
emailAddresses: [],
@ -105,23 +105,23 @@ describe('ContactsMenu', function() {
topAction: {
title: 'Mail',
icon: 'icon-mail',
hyperlink: 'mailto:ceciliasoto%40essensia.com'
hyperlink: 'mailto:ceciliasoto%40essensia.com',
},
actions: [
{
title: 'Mail',
icon: 'icon-mail',
hyperlink: 'mailto:pearliesellers%40inventure.com'
hyperlink: 'mailto:pearliesellers%40inventure.com',
},
{
title: 'Details',
icon: 'icon-info',
hyperlink: 'https://localhost/index.php/apps/contacts'
}
hyperlink: 'https://localhost/index.php/apps/contacts',
},
],
lastMessage: 'cu',
emailAddresses: [],
}
},
],
contactsAppEnabled: true,
},

Просмотреть файл

@ -8,7 +8,7 @@ import { getRootUrl } from '@nextcloud/router'
/**
*
* @param {string} url the URL to check
* @returns {boolean}
* @return {boolean}
*/
const isRelativeUrl = (url) => {
return !url.startsWith('https://') && !url.startsWith('http://')

Просмотреть файл

@ -13,14 +13,14 @@
</template>
<div class="contactsmenu__menu">
<div class="contactsmenu__menu__input-wrapper">
<NcTextField :value.sync="searchTerm"
trailing-button-icon="close"
<NcTextField id="contactsmenu__menu__search"
ref="contactsMenuInput"
:value.sync="searchTerm"
trailing-button-icon="close"
:label="t('core', 'Search contacts')"
:trailing-button-label="t('core','Reset search')"
:show-trailing-button="searchTerm !== ''"
:placeholder="t('core', 'Search contacts …')"
id="contactsmenu__menu__search"
class="contactsmenu__menu__search"
@input="onInputDebounced"
@trailing-button-click="onReset" />

Просмотреть файл

@ -38,7 +38,10 @@ const config: Config = {
testMatch: ['<rootDir>/**/*.(spec|test).(ts|js)'],
clearMocks: true,
setupFilesAfterEnv: ['<rootDir>/__tests__/jest-setup.ts'],
setupFilesAfterEnv: [
'<rootDir>/__tests__/jest-setup.ts',
'<rootDir>/__tests__/mock-window.js',
],
testEnvironment: 'jest-environment-jsdom',
preset: 'ts-jest/presets/js-with-ts',

1006
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -13,8 +13,8 @@
"postbuild": "build/npm-post-build.sh",
"dev": "webpack --node-env development --progress",
"watch": "webpack --node-env development --progress --watch",
"lint": "eslint $(for appdir in $(ls apps); do if ! $(git check-ignore -q $appdir); then printf \"$appdir \"; fi; done) core --no-error-on-unmatched-pattern",
"lint:fix": "eslint $(for appdir in $(ls apps); do if ! $(git check-ignore -q $appdir); then printf \"$appdir \"; fi; done) core --no-error-on-unmatched-pattern --fix",
"lint": "eslint $(for appdir in $(ls apps); do if ! $(git check-ignore -q $appdir); then printf \"apps/$appdir \"; fi; done) core --no-error-on-unmatched-pattern",
"lint:fix": "eslint $(for appdir in $(ls apps); do if ! $(git check-ignore -q $appdir); then printf \"apps/$appdir \"; fi; done) core --no-error-on-unmatched-pattern --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
@ -56,7 +56,7 @@
"@nextcloud/password-confirmation": "^5.1.1",
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^3.0.0",
"@nextcloud/sharing": "^0.1.0",
"@nextcloud/sharing": "^0.2.2",
"@nextcloud/upload": "^1.4.1",
"@nextcloud/vue": "^8.14.0",
"@simplewebauthn/browser": "^10.0.0",
@ -116,18 +116,18 @@
"webdav": "^5.6.0"
},
"devDependencies": {
"@babel/node": "^7.22.10",
"@babel/preset-typescript": "^7.24.1",
"@cypress/vue2": "^2.1.0",
"@cypress/webpack-preprocessor": "^6.0.1",
"@babel/node": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@cypress/vue2": "^2.1.1",
"@cypress/webpack-preprocessor": "^6.0.2",
"@jest/globals": "^29.7.0",
"@nextcloud/babel-config": "^1.0.0",
"@nextcloud/babel-config": "^1.2.0",
"@nextcloud/cypress": "^1.0.0-beta.8",
"@nextcloud/eslint-config": "^v8.4.1",
"@nextcloud/eslint-config": "^8.4.1",
"@nextcloud/stylelint-config": "^3.0.1",
"@nextcloud/typings": "^1.8.0",
"@nextcloud/typings": "^1.9.1",
"@nextcloud/webpack-vue-config": "^6.0.1",
"@pinia/testing": "^0.1.2",
"@pinia/testing": "^0.1.3",
"@simplewebauthn/types": "^10.0.0",
"@testing-library/cypress": "^10.0.2",
"@testing-library/jest-dom": "^6.4.6",