diff --git a/core/src/OC/msg.js b/core/src/OC/msg.js deleted file mode 100644 index 38708c6f60da5..0000000000000 --- a/core/src/OC/msg.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import $ from 'jquery' - -/** - * A little class to manage a status field for a "saving" process. - * It can be used to display a starting message (e.g. "Saving...") and then - * replace it with a green success message or a red error message. - * - * @namespace OC.msg - */ -export default { - /** - * Displayes a "Saving..." message in the given message placeholder - * - * @param {object} selector Placeholder to display the message in - */ - startSaving(selector) { - this.startAction(selector, t('core', 'Saving …')) - }, - - /** - * Displayes a custom message in the given message placeholder - * - * @param {object} selector Placeholder to display the message in - * @param {string} message Plain text message to display (no HTML allowed) - */ - startAction(selector, message) { - $(selector).text(message) - .removeClass('success') - .removeClass('error') - .stop(true, true) - .show() - }, - - /** - * Displayes an success/error message in the given selector - * - * @param {object} selector Placeholder to display the message in - * @param {object} response Response of the server - * @param {object} response.data Data of the servers response - * @param {string} response.data.message Plain text message to display (no HTML allowed) - * @param {string} response.status is being used to decide whether the message - * is displayed as an error/success - */ - finishedSaving(selector, response) { - this.finishedAction(selector, response) - }, - - /** - * Displayes an success/error message in the given selector - * - * @param {object} selector Placeholder to display the message in - * @param {object} response Response of the server - * @param {object} response.data Data of the servers response - * @param {string} response.data.message Plain text message to display (no HTML allowed) - * @param {string} response.status is being used to decide whether the message - * is displayed as an error/success - */ - finishedAction(selector, response) { - if (response.status === 'success') { - this.finishedSuccess(selector, response.data.message) - } else { - this.finishedError(selector, response.data.message) - } - }, - - /** - * Displayes an success message in the given selector - * - * @param {object} selector Placeholder to display the message in - * @param {string} message Plain text success message to display (no HTML allowed) - */ - finishedSuccess(selector, message) { - $(selector).text(message) - .addClass('success') - .removeClass('error') - .stop(true, true) - .delay(3000) - .fadeOut(900) - .show() - }, - - /** - * Displayes an error message in the given selector - * - * @param {object} selector Placeholder to display the message in - * @param {string} message Plain text error message to display (no HTML allowed) - */ - finishedError(selector, message) { - $(selector).text(message) - .addClass('error') - .removeClass('success') - .show() - }, -} diff --git a/core/src/OC/msg.spec.ts b/core/src/OC/msg.spec.ts new file mode 100644 index 0000000000000..c851b7094016c --- /dev/null +++ b/core/src/OC/msg.spec.ts @@ -0,0 +1,195 @@ +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { afterAll, describe, expect, it, test, vi } from 'vitest' +import msg from './msg.ts' + +describe('start action', () => { + it('sets the message text content', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '
' + + msg.startAction(selector, 'the message') + + const el = document.querySelector(selector) + expect(el).not.toBeNull() + expect(el!.textContent).toBe('the message') + }) + + it('removes old classes', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) + expect(el).not.toBeNull() + expect(el!.classList.contains('success')).toBe(true) + + msg.startAction(selector, 'the message') + expect(el!.classList.contains('success')).toBe(false) + }) + + it('sets element visible', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) as HTMLElement + expect(el).not.toBeNull() + expect(el.style.display).toBe('none') + + msg.startAction(selector, 'the message') + expect(el.style.display).toBe('block') + }) +}) + +test('start saving message', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + msg.startSaving(selector) + + const el = document.querySelector(selector) + expect(el).not.toBeNull() + expect(el!.textContent).toBe('Saving …') +}) + +describe('finish with error', () => { + it('sets the message text content', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) + msg.startSaving(selector) + msg.finishedError(selector, 'error message') + expect(el!.textContent).toBe('error message') + }) + + it('adds error class', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) + msg.startSaving(selector) + msg.finishedError(selector, 'error message') + expect(el!.classList.contains('error')).toBe(true) + }) + + it('removes old classes', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) + msg.startSaving(selector) + msg.finishedError(selector, 'error message') + expect(el!.classList.contains('success')).toBe(false) + }) + + it('sets element visible', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) as HTMLElement + msg.startSaving(selector) + msg.finishedError(selector, 'error message') + expect(el.style.display).toBe('block') + }) +}) + +describe('finish with success', () => { + afterAll(() => vi.useRealTimers()) + + it('sets the message text content', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) + msg.startSaving(selector) + msg.finishedSuccess(selector, 'success message') + expect(el!.textContent).toBe('success message') + }) + + it('adds success class', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) + msg.startSaving(selector) + msg.finishedSuccess(selector, 'success message') + expect(el!.classList.contains('success')).toBe(true) + }) + + it('removes old classes', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) + msg.startSaving(selector) + msg.finishedSuccess(selector, 'success message') + expect(el!.classList.contains('error')).toBe(false) + }) + + it('sets element visible', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) as HTMLElement + msg.startSaving(selector) + msg.finishedSuccess(selector, 'success message') + expect(el.style.display).toBe('block') + }) + + it('fades out element', () => { + vi.useFakeTimers() + + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const el = document.querySelector(selector) as HTMLElement + msg.startSaving(selector) + msg.finishedSuccess(selector, 'success message') + expect(el!.style.display).toBe('block') + + vi.advanceTimersByTime(3900) + + expect(el.style.display).toBe('none') + }) +}) + +describe('finished action', () => { + it('calls finishedSuccess on success response', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const finishedSuccessSpy = vi.spyOn(msg, 'finishedSuccess') + const response = { data: { message: 'all good' }, status: 'success' } + + msg.finishedAction(selector, response) + + expect(finishedSuccessSpy).toHaveBeenCalledWith(selector, 'all good') + }) + + it('calls finishedError on error response', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const finishedErrorSpy = vi.spyOn(msg, 'finishedError') + const response = { data: { message: 'something went wrong' }, status: 'error' } + + msg.finishedAction(selector, response) + + expect(finishedErrorSpy).toHaveBeenCalledWith(selector, 'something went wrong') + }) +}) + +test('finished saving delegates to finished action', () => { + const selector = '#msg-placeholder' + document.body.innerHTML = '' + + const finishedActionSpy = vi.spyOn(msg, 'finishedAction') + const response = { data: { message: 'done saving' }, status: 'success' } + + msg.finishedSaving(selector, response) + + expect(finishedActionSpy).toHaveBeenCalledWith(selector, response) +}) diff --git a/core/src/OC/msg.ts b/core/src/OC/msg.ts new file mode 100644 index 0000000000000..0bc6fa851b6eb --- /dev/null +++ b/core/src/OC/msg.ts @@ -0,0 +1,140 @@ +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { t } from '@nextcloud/l10n' + +/** + * A little class to manage a status field for a "saving" process. + * It can be used to display a starting message (e.g. "Saving...") and then + * replace it with a green success message or a red error message. + */ +export default { + /** + * Displayes a "Saving..." message in the given message placeholder + * + * @param selector - Query selectior for the element to display the message in + */ + startSaving(selector: string) { + this.startAction(selector, t('core', 'Saving …')) + }, + + /** + * Displayes a custom message in the given message placeholder + * + * @param selector - Query selectior for the element to display the message in + * @param message - Plain text message to display (no HTML allowed) + */ + startAction(selector: string, message: string) { + const el = document.querySelector(selector) + if (!el || !(el instanceof HTMLElement)) { + return + } + + el.textContent = message + el.classList.remove('success') + el.classList.remove('error') + el.getAnimations?.().forEach((animation) => animation.cancel()) + el.style.display = 'block' + }, + + /** + * Displayes an success/error message in the given selector + * + * @param selector - Query selectior for the element to display the message in + * @param response - Response of the server + * @param response.data - Data of the servers response + * @param response.data.message - Plain text message to display (no HTML allowed) + * @param response.status - is being used to decide whether the message is displayed as an error/success + */ + finishedSaving(selector: string, response: { data: { message: string }, status: string }) { + this.finishedAction(selector, response) + }, + + /** + * Displayes an success/error message in the given selector + * + * @param selector - Query selector for the element to display the message in + * @param response - Response of the server + * @param response.data - Data of the servers response + * @param response.data.message - Plain text message to display (no HTML allowed) + * @param response.status . Is being used to decide whether the message is displayed as an error/success + */ + finishedAction(selector: string, response: { data: { message: string }, status: string }) { + if (response.status === 'success') { + this.finishedSuccess(selector, response.data.message) + } else { + this.finishedError(selector, response.data.message) + } + }, + + /** + * Displayes an success message in the given selector + * + * @param selector - Query selector for the element to display the message in + * @param message - Plain text success message to display (no HTML allowed) + */ + finishedSuccess(selector: string, message: string) { + const el = document.querySelector(selector) + if (!el || !(el instanceof HTMLElement)) { + return + } + + el.textContent = message + el.classList.remove('error') + el.classList.add('success') + el.getAnimations?.().forEach((animation) => animation.cancel()) + + window.setTimeout(fadeOut, 3000) + el.style.display = 'block' + + /** + * Fades out the message element + */ + function fadeOut() { + if (!el || !(el instanceof HTMLElement)) { + return + } + + const animation = el.animate?.( + [ + { opacity: 1 }, + { opacity: 0 }, + ], + { + duration: 900, + fill: 'forwards', + }, + ) + + if (animation) { + animation.addEventListener('finish', () => { + el.style.display = 'none' + }) + } else { + window.setTimeout(() => { + el.style.display = 'none' + }, 900) + } + } + }, + + /** + * Displayes an error message in the given selector + * + * @param selector - Query selector for the element to display the message in + * @param message - Plain text error message to display (no HTML allowed) + */ + finishedError(selector: string, message: string) { + const el = document.querySelector(selector) + if (!el || !(el instanceof HTMLElement)) { + return + } + + el.textContent = message + el.classList.remove('success') + el.classList.add('error') + el.style.display = 'block' + }, +} diff --git a/dist/7883-7883.js b/dist/7883-7883.js index 24a35da3516ea..c46b457a15f54 100644 --- a/dist/7883-7883.js +++ b/dist/7883-7883.js @@ -1,2 +1,2 @@ -(globalThis.webpackChunknextcloud_ui_legacy=globalThis.webpackChunknextcloud_ui_legacy||[]).push([[7883],{322(e){"use strict";e.exports={CharacterClass:function(e){for(var u=e.node,r={},a=0;a