diff --git a/css/fullcalendar.scss b/css/fullcalendar.scss index ad39863b13..0dd7ad4b03 100644 --- a/css/fullcalendar.scss +++ b/css/fullcalendar.scss @@ -194,19 +194,11 @@ } // Reminder icon on events with alarms set - .fc-event-nc-alarms { + &.fc-event-nc-alarms { .icon-event-reminder { - background-color: inherit; - background-position: right; position: absolute; top: 0; inset-inline-end: 0; - &--light { - background-image: var(--icon-calendar-reminder-fffffe) - } - &--dark { - background-image: var(--icon-calendar-reminder-000001) - } } } @@ -243,7 +235,10 @@ } svg { + width: 1em; margin-inline-end: 2px; + fill: var(--color-main-text); + vertical-align: middle; } @media only screen and (max-width: 767px) { @@ -373,4 +368,3 @@ [data-themes*="highcontrast"] { @include _fc-high-contrast-events; } - diff --git a/src/fullcalendar/eventSources/eventSourceFunction.js b/src/fullcalendar/eventSources/eventSourceFunction.js index dcfb7a1c7b..3a2086c74d 100644 --- a/src/fullcalendar/eventSources/eventSourceFunction.js +++ b/src/fullcalendar/eventSources/eventSourceFunction.js @@ -9,8 +9,6 @@ import useTasksStore from '../../store/unscheduledTasks.js' import { getAllObjectsInTimeRange } from '../../utils/calendarObject.js' import { getHexForColorName, - hexToRGB, - isLight, } from '../../utils/color.js' import logger from '../../utils/logger.js' /** @@ -170,7 +168,6 @@ export function eventSourceFunction(calendarObjects, calendar, start, end, timez calendarOrder: calendar.order, calendarName: calendar.displayName, calendarId: calendar.id, - darkText: isLight(hexToRGB(calendar.color)), objectType: object.name, percent: object.percent || null, davUrl: calendarObject.dav.url, diff --git a/src/fullcalendar/rendering/eventDidMount.js b/src/fullcalendar/rendering/eventDidMount.js index 49928ed6db..393e1ba0ca 100644 --- a/src/fullcalendar/rendering/eventDidMount.js +++ b/src/fullcalendar/rendering/eventDidMount.js @@ -1,3 +1,4 @@ +import { createSvgIconElement } from '../../utils/svg.ts' /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -16,11 +17,9 @@ export default errorCatch(function({ event, el }) { if (el.classList.contains('fc-event-nc-alarms')) { const notificationIcon = document.createElement('span') notificationIcon.classList.add('icon-event-reminder') - if (event.extendedProps.darkText) { - notificationIcon.classList.add('icon-event-reminder--dark') - } else { - notificationIcon.classList.add('icon-event-reminder--light') - } + // From node_modules/vue-material-design-icons/Bell.vue + const bellIcon = createSvgIconElement('M21,19V20H3V19L5,17V11C5,7.9 7.03,5.17 10,4.29C10,4.19 10,4.1 10,4A2,2 0 0,1 12,2A2,2 0 0,1 14,4C14,4.1 14,4.19 14,4.29C16.97,5.17 19,7.9 19,11V17L21,19M14,21A2,2 0 0,1 12,23A2,2 0 0,1 10,21') + notificationIcon.appendChild(bellIcon) el.firstChild.appendChild(notificationIcon) } @@ -185,11 +184,13 @@ export default errorCatch(function({ event, el }) { event.extendedProps.attendeeCount >= 1 && !el.classList.contains('fc-event-nc-task') ) { - prependTitleIcon(el, 'M40-160v-112q0-34 17.5-62.5T104-378q62-31 126-46.5T360-440q66 0 130 15.5T616-378q29 15 46.5 43.5T680-272v112H40Zm640 0v-112q0-51-26-95.5T586-441q51 6 98 20.5t84 35.5q36 20 57 44.5t21 52.5v112H680ZM360-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47Zm400-160q0 66-47 113t-113 47q-11 0-28-2.5t-28-5.5q27-32 41.5-71t14.5-81q0-42-14.5-81T544-792q14-5 28-6.5t28-1.5q66 0 113 47t47 113Z') + // From node_modules/vue-material-design-icons/AccountMultiple.vue + prependTitleIcon(el, 'M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z') } if (el.classList.contains('fc-event-nc-all-declined')) { - prependTitleIcon(el, 'm40-120 440-760 440 760H40Zm440-120q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm-40-120h80v-200h-80v200Z') + // From node_modules/vue-material-design-icons/Alert.vue + prependTitleIcon(el, 'M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z') } if (el.classList.contains('fc-event-nc-tentative')) { @@ -254,16 +255,7 @@ function prependTitleIcon(el, svgPath) { } } - const svgNS = 'http://www.w3.org/2000/svg' - const svgElement = document.createElementNS(svgNS, 'svg') - svgElement.setAttribute('viewBox', '0 -960 960 960') - const pathElement = document.createElementNS(svgNS, 'path') - pathElement.setAttribute('d', svgPath) - svgElement.appendChild(pathElement) - svgElement.style.fill = el.style.borderColor - svgElement.style.width = '1em' - svgElement.style.marginBottom = '0.2em' - svgElement.style.verticalAlign = 'middle' + const svgElement = createSvgIconElement(svgPath) titleElement.insertBefore(svgElement, titleElement.firstChild) } diff --git a/src/utils/color.js b/src/utils/color.js index ec11991a88..922519b7ed 100644 --- a/src/utils/color.js +++ b/src/utils/color.js @@ -21,16 +21,6 @@ export function isLight({ red, green, blue }) { return (brightness > 130) } -/** - * Get a text-color that's readable on a given background color - * - * @param {string} hexColor The hex color to get a text color for - * @return {string} the matching text color - */ -export function generateTextColorForHex(hexColor) { - return generateTextColorForRGB(hexToRGB(hexColor)) -} - /** * Get a text-color that's readable on a given background color * @@ -44,20 +34,6 @@ export function generateTextColorForRGB({ red, green, blue }) { return isLight({ red, green, blue }) ? '#000000' : '#FAFAFA' } -/** - * Convert hex string to RGB - * - * @param {string} hexColor The hex color to convert - * @return {string} the RGB result - */ -export function hexToRGB(hexColor) { - if (hexColor === null) { - return { red: 0, green: 0, blue: 0 } - } - const [red, green, blue] = convert.hex.rgb(hexColor.slice(1)) - return { red, green, blue } -} - /** * Generates a hex color based on RGB string * diff --git a/src/utils/svg.ts b/src/utils/svg.ts new file mode 100644 index 0000000000..3815c947ce --- /dev/null +++ b/src/utils/svg.ts @@ -0,0 +1,22 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Allocates a DOM element containing the given SVG icon + * It is recommended to always use SVG icons from vue-material-design-icons and + * to specify the original file from which SVG has been retrieved + * + * @param content SVG path data of the icon + * @return The element to append to the DOM + */ +export function createSvgIconElement(content: string): SVGSVGElement { + const svgNS = 'http://www.w3.org/2000/svg' + const svgElement = document.createElementNS(svgNS, 'svg') + svgElement.setAttribute('viewBox', '0 0 24 24') + const pathElement = document.createElementNS(svgNS, 'path') + pathElement.setAttribute('d', content) + svgElement.appendChild(pathElement) + return svgElement +} diff --git a/tests/javascript/unit/fullcalendar/eventSources/freeBusyResourceEventSourceFunction.test.js b/tests/javascript/unit/fullcalendar/eventSources/freeBusyResourceEventSourceFunction.test.js index ddc3a9b6f5..1129d0ecae 100644 --- a/tests/javascript/unit/fullcalendar/eventSources/freeBusyResourceEventSourceFunction.test.js +++ b/tests/javascript/unit/fullcalendar/eventSources/freeBusyResourceEventSourceFunction.test.js @@ -4,9 +4,7 @@ */ import { eventSourceFunction } from '../../../../../src/fullcalendar/eventSources/eventSourceFunction.js' import { - hexToRGB, isLight, - generateTextColorForHex, getHexForColorName, } from '../../../../../src/utils/color.js' import { translate } from '@nextcloud/l10n' @@ -22,7 +20,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { beforeEach(() => { translate.mockClear() getHexForColorName.mockClear() - generateTextColorForHex.mockClear() getAllObjectsInTimeRange.mockClear() setActivePinia(createPinia()) }) @@ -32,8 +29,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { .mockImplementation((app, str) => str) getHexForColorName .mockImplementation(() => '#ff0000') - generateTextColorForHex - .mockImplementation(() => '#eeeeee') isLight .mockImplementation(() => false) @@ -212,7 +207,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarId: 'Calendar id 456', calendarName: 'Calendar displayname', calendarOrder: 1337, - darkText: false, davUrl: 'url1', objectType: 'VEVENT', percent: null, @@ -236,7 +230,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarId: 'Calendar id 456', calendarName: 'Calendar displayname', calendarOrder: 1337, - darkText: false, davUrl: 'url1', objectType: 'VEVENT', percent: null, @@ -260,7 +253,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarId: 'Calendar id 456', calendarName: 'Calendar displayname', calendarOrder: 1337, - darkText: false, davUrl: 'url1', objectType: 'VEVENT', percent: null, @@ -284,7 +276,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarId: 'Calendar id 456', calendarName: 'Calendar displayname', calendarOrder: 1337, - darkText: false, davUrl: 'url2', objectType: 'VEVENT', percent: null, @@ -308,7 +299,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarId: 'Calendar id 456', calendarName: 'Calendar displayname', calendarOrder: 1337, - darkText: false, davUrl: 'url4', objectType: 'VEVENT', percent: null, @@ -361,8 +351,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { expect(getHexForColorName).toHaveBeenCalledTimes(1) expect(getHexForColorName).toHaveBeenNthCalledWith(1, 'red') - expect(generateTextColorForHex).toHaveBeenCalledTimes(0) - // Make sure the following dates have not been touched expect(event11Start.getFullYear()).toEqual(2020) expect(event11Start.getMonth()).toEqual(1) @@ -441,8 +429,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { .mockImplementation((app, str) => str) getHexForColorName .mockImplementation(() => '#ff0000') - generateTextColorForHex - .mockImplementation(() => '#eeeeee') isLight .mockImplementation(() => false) @@ -610,7 +596,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarName: 'Calendar displayname', calendarOrder: 1337, canModifyAllDay: false, - darkText: false, davUrl: 'url1', objectId: '1', vobjectId: '1', @@ -635,7 +620,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarName: 'Calendar displayname', calendarOrder: 1337, canModifyAllDay: false, - darkText: false, davUrl: 'url1', objectId: '1', vobjectId: '2', @@ -660,7 +644,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarName: 'Calendar displayname', calendarOrder: 1337, canModifyAllDay: false, - darkText: false, davUrl: 'url1', objectId: '1', vobjectId: '3', @@ -685,7 +668,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarName: 'Calendar displayname', calendarOrder: 1337, canModifyAllDay: false, - darkText: false, davUrl: 'url1', objectId: '1', vobjectId: '4', @@ -710,7 +692,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { calendarName: 'Calendar displayname', calendarOrder: 1337, canModifyAllDay: false, - darkText: false, davUrl: 'url1', objectId: '1', vobjectId: '5', @@ -756,7 +737,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => { expect(getAllObjectsInTimeRange).toHaveBeenNthCalledWith(1, calendarObjects[0], start, end) expect(getHexForColorName).toHaveBeenCalledTimes(0) - expect(generateTextColorForHex).toHaveBeenCalledTimes(0) }) it('should filter events by search query matching title, location, description, attendee, and organizer', () => { diff --git a/tests/javascript/unit/fullcalendar/rendering/eventDidMount.test.js b/tests/javascript/unit/fullcalendar/rendering/eventDidMount.test.js index 9f631777ef..bdb12cba90 100644 --- a/tests/javascript/unit/fullcalendar/rendering/eventDidMount.test.js +++ b/tests/javascript/unit/fullcalendar/rendering/eventDidMount.test.js @@ -37,7 +37,7 @@ describe('fullcalendar/eventDidMount test suite', () => { expect(el.dataset.recurrenceId).toEqual(undefined) }) - it('should add an alarm bell icon if event has an alarm - dark', () => { + it('should add an alarm bell icon if event has an alarm', () => { const fcTime = document.createElement('span') fcTime.classList.add('fc-time') fcTime.appendChild(document.createTextNode('2pm')) @@ -59,46 +59,14 @@ describe('fullcalendar/eventDidMount test suite', () => { extendedProps: { objectId: 'object123', recurrenceId: 'recurrence456', - darkText: true, percent: 100, }, } eventRender({ event, el }) - expect(el.outerHTML).toEqual('