Skip to content

Commit 7e34b80

Browse files
Merge pull request #8863 from nextcloud/backport/8853/stable6.5
[stable6.5] fix: bell icon for events with reminders
2 parents 15673ba + c5705ef commit 7e34b80

8 files changed

Lines changed: 38 additions & 122 deletions

File tree

css/fullcalendar.scss

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,19 +194,11 @@
194194
}
195195

196196
// Reminder icon on events with alarms set
197-
.fc-event-nc-alarms {
197+
&.fc-event-nc-alarms {
198198
.icon-event-reminder {
199-
background-color: inherit;
200-
background-position: right;
201199
position: absolute;
202200
top: 0;
203201
inset-inline-end: 0;
204-
&--light {
205-
background-image: var(--icon-calendar-reminder-fffffe)
206-
}
207-
&--dark {
208-
background-image: var(--icon-calendar-reminder-000001)
209-
}
210202
}
211203
}
212204

@@ -243,7 +235,10 @@
243235
}
244236

245237
svg {
238+
width: 1em;
246239
margin-inline-end: 2px;
240+
fill: var(--color-main-text);
241+
vertical-align: middle;
247242
}
248243

249244
@media only screen and (max-width: 767px) {
@@ -373,4 +368,3 @@
373368
[data-themes*="highcontrast"] {
374369
@include _fc-high-contrast-events;
375370
}
376-

src/fullcalendar/eventSources/eventSourceFunction.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import useTasksStore from '../../store/unscheduledTasks.js'
99
import { getAllObjectsInTimeRange } from '../../utils/calendarObject.js'
1010
import {
1111
getHexForColorName,
12-
hexToRGB,
13-
isLight,
1412
} from '../../utils/color.js'
1513
import logger from '../../utils/logger.js'
1614
/**
@@ -170,7 +168,6 @@ export function eventSourceFunction(calendarObjects, calendar, start, end, timez
170168
calendarOrder: calendar.order,
171169
calendarName: calendar.displayName,
172170
calendarId: calendar.id,
173-
darkText: isLight(hexToRGB(calendar.color)),
174171
objectType: object.name,
175172
percent: object.percent || null,
176173
davUrl: calendarObject.dav.url,

src/fullcalendar/rendering/eventDidMount.js

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createSvgIconElement } from '../../utils/svg.ts'
12
/**
23
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
34
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -16,11 +17,9 @@ export default errorCatch(function({ event, el }) {
1617
if (el.classList.contains('fc-event-nc-alarms')) {
1718
const notificationIcon = document.createElement('span')
1819
notificationIcon.classList.add('icon-event-reminder')
19-
if (event.extendedProps.darkText) {
20-
notificationIcon.classList.add('icon-event-reminder--dark')
21-
} else {
22-
notificationIcon.classList.add('icon-event-reminder--light')
23-
}
20+
// From node_modules/vue-material-design-icons/Bell.vue
21+
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')
22+
notificationIcon.appendChild(bellIcon)
2423
el.firstChild.appendChild(notificationIcon)
2524
}
2625

@@ -185,11 +184,13 @@ export default errorCatch(function({ event, el }) {
185184
event.extendedProps.attendeeCount >= 1
186185
&& !el.classList.contains('fc-event-nc-task')
187186
) {
188-
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')
187+
// From node_modules/vue-material-design-icons/AccountMultiple.vue
188+
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')
189189
}
190190

191191
if (el.classList.contains('fc-event-nc-all-declined')) {
192-
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')
192+
// From node_modules/vue-material-design-icons/Alert.vue
193+
prependTitleIcon(el, 'M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z')
193194
}
194195

195196
if (el.classList.contains('fc-event-nc-tentative')) {
@@ -254,16 +255,7 @@ function prependTitleIcon(el, svgPath) {
254255
}
255256
}
256257

257-
const svgNS = 'http://www.w3.org/2000/svg'
258-
const svgElement = document.createElementNS(svgNS, 'svg')
259-
svgElement.setAttribute('viewBox', '0 -960 960 960')
260-
const pathElement = document.createElementNS(svgNS, 'path')
261-
pathElement.setAttribute('d', svgPath)
262-
svgElement.appendChild(pathElement)
263-
svgElement.style.fill = el.style.borderColor
264-
svgElement.style.width = '1em'
265-
svgElement.style.marginBottom = '0.2em'
266-
svgElement.style.verticalAlign = 'middle'
258+
const svgElement = createSvgIconElement(svgPath)
267259
titleElement.insertBefore(svgElement, titleElement.firstChild)
268260
}
269261

src/utils/color.js

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,6 @@ export function isLight({ red, green, blue }) {
2121
return (brightness > 130)
2222
}
2323

24-
/**
25-
* Get a text-color that's readable on a given background color
26-
*
27-
* @param {string} hexColor The hex color to get a text color for
28-
* @return {string} the matching text color
29-
*/
30-
export function generateTextColorForHex(hexColor) {
31-
return generateTextColorForRGB(hexToRGB(hexColor))
32-
}
33-
3424
/**
3525
* Get a text-color that's readable on a given background color
3626
*
@@ -44,20 +34,6 @@ export function generateTextColorForRGB({ red, green, blue }) {
4434
return isLight({ red, green, blue }) ? '#000000' : '#FAFAFA'
4535
}
4636

47-
/**
48-
* Convert hex string to RGB
49-
*
50-
* @param {string} hexColor The hex color to convert
51-
* @return {string} the RGB result
52-
*/
53-
export function hexToRGB(hexColor) {
54-
if (hexColor === null) {
55-
return { red: 0, green: 0, blue: 0 }
56-
}
57-
const [red, green, blue] = convert.hex.rgb(hexColor.slice(1))
58-
return { red, green, blue }
59-
}
60-
6137
/**
6238
* Generates a hex color based on RGB string
6339
*

src/utils/svg.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
/**
7+
* Allocates a DOM element containing the given SVG icon
8+
* It is recommended to always use SVG icons from vue-material-design-icons and
9+
* to specify the original file from which SVG has been retrieved
10+
*
11+
* @param content SVG path data of the icon
12+
* @return The element to append to the DOM
13+
*/
14+
export function createSvgIconElement(content: string): SVGSVGElement {
15+
const svgNS = 'http://www.w3.org/2000/svg'
16+
const svgElement = document.createElementNS(svgNS, 'svg')
17+
svgElement.setAttribute('viewBox', '0 0 24 24')
18+
const pathElement = document.createElementNS(svgNS, 'path')
19+
pathElement.setAttribute('d', content)
20+
svgElement.appendChild(pathElement)
21+
return svgElement
22+
}

tests/javascript/unit/fullcalendar/eventSources/freeBusyResourceEventSourceFunction.test.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
*/
55
import { eventSourceFunction } from '../../../../../src/fullcalendar/eventSources/eventSourceFunction.js'
66
import {
7-
hexToRGB,
87
isLight,
9-
generateTextColorForHex,
108
getHexForColorName,
119
} from '../../../../../src/utils/color.js'
1210
import { translate } from '@nextcloud/l10n'
@@ -22,7 +20,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
2220
beforeEach(() => {
2321
translate.mockClear()
2422
getHexForColorName.mockClear()
25-
generateTextColorForHex.mockClear()
2623
getAllObjectsInTimeRange.mockClear()
2724
setActivePinia(createPinia())
2825
})
@@ -32,8 +29,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
3229
.mockImplementation((app, str) => str)
3330
getHexForColorName
3431
.mockImplementation(() => '#ff0000')
35-
generateTextColorForHex
36-
.mockImplementation(() => '#eeeeee')
3732
isLight
3833
.mockImplementation(() => false)
3934

@@ -212,7 +207,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
212207
calendarId: 'Calendar id 456',
213208
calendarName: 'Calendar displayname',
214209
calendarOrder: 1337,
215-
darkText: false,
216210
davUrl: 'url1',
217211
objectType: 'VEVENT',
218212
percent: null,
@@ -236,7 +230,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
236230
calendarId: 'Calendar id 456',
237231
calendarName: 'Calendar displayname',
238232
calendarOrder: 1337,
239-
darkText: false,
240233
davUrl: 'url1',
241234
objectType: 'VEVENT',
242235
percent: null,
@@ -260,7 +253,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
260253
calendarId: 'Calendar id 456',
261254
calendarName: 'Calendar displayname',
262255
calendarOrder: 1337,
263-
darkText: false,
264256
davUrl: 'url1',
265257
objectType: 'VEVENT',
266258
percent: null,
@@ -284,7 +276,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
284276
calendarId: 'Calendar id 456',
285277
calendarName: 'Calendar displayname',
286278
calendarOrder: 1337,
287-
darkText: false,
288279
davUrl: 'url2',
289280
objectType: 'VEVENT',
290281
percent: null,
@@ -308,7 +299,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
308299
calendarId: 'Calendar id 456',
309300
calendarName: 'Calendar displayname',
310301
calendarOrder: 1337,
311-
darkText: false,
312302
davUrl: 'url4',
313303
objectType: 'VEVENT',
314304
percent: null,
@@ -361,8 +351,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
361351
expect(getHexForColorName).toHaveBeenCalledTimes(1)
362352
expect(getHexForColorName).toHaveBeenNthCalledWith(1, 'red')
363353

364-
expect(generateTextColorForHex).toHaveBeenCalledTimes(0)
365-
366354
// Make sure the following dates have not been touched
367355
expect(event11Start.getFullYear()).toEqual(2020)
368356
expect(event11Start.getMonth()).toEqual(1)
@@ -441,8 +429,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
441429
.mockImplementation((app, str) => str)
442430
getHexForColorName
443431
.mockImplementation(() => '#ff0000')
444-
generateTextColorForHex
445-
.mockImplementation(() => '#eeeeee')
446432
isLight
447433
.mockImplementation(() => false)
448434

@@ -610,7 +596,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
610596
calendarName: 'Calendar displayname',
611597
calendarOrder: 1337,
612598
canModifyAllDay: false,
613-
darkText: false,
614599
davUrl: 'url1',
615600
objectId: '1',
616601
vobjectId: '1',
@@ -635,7 +620,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
635620
calendarName: 'Calendar displayname',
636621
calendarOrder: 1337,
637622
canModifyAllDay: false,
638-
darkText: false,
639623
davUrl: 'url1',
640624
objectId: '1',
641625
vobjectId: '2',
@@ -660,7 +644,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
660644
calendarName: 'Calendar displayname',
661645
calendarOrder: 1337,
662646
canModifyAllDay: false,
663-
darkText: false,
664647
davUrl: 'url1',
665648
objectId: '1',
666649
vobjectId: '3',
@@ -685,7 +668,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
685668
calendarName: 'Calendar displayname',
686669
calendarOrder: 1337,
687670
canModifyAllDay: false,
688-
darkText: false,
689671
davUrl: 'url1',
690672
objectId: '1',
691673
vobjectId: '4',
@@ -710,7 +692,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
710692
calendarName: 'Calendar displayname',
711693
calendarOrder: 1337,
712694
canModifyAllDay: false,
713-
darkText: false,
714695
davUrl: 'url1',
715696
objectId: '1',
716697
vobjectId: '5',
@@ -756,7 +737,6 @@ describe('fullcalendar/freeBusyResourceEventSourceFunction test suite', () => {
756737
expect(getAllObjectsInTimeRange).toHaveBeenNthCalledWith(1, calendarObjects[0], start, end)
757738

758739
expect(getHexForColorName).toHaveBeenCalledTimes(0)
759-
expect(generateTextColorForHex).toHaveBeenCalledTimes(0)
760740
})
761741

762742
it('should filter events by search query matching title, location, description, attendee, and organizer', () => {

tests/javascript/unit/fullcalendar/rendering/eventDidMount.test.js

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('fullcalendar/eventDidMount test suite', () => {
3737
expect(el.dataset.recurrenceId).toEqual(undefined)
3838
})
3939

40-
it('should add an alarm bell icon if event has an alarm - dark', () => {
40+
it('should add an alarm bell icon if event has an alarm', () => {
4141
const fcTime = document.createElement('span')
4242
fcTime.classList.add('fc-time')
4343
fcTime.appendChild(document.createTextNode('2pm'))
@@ -59,46 +59,14 @@ describe('fullcalendar/eventDidMount test suite', () => {
5959
extendedProps: {
6060
objectId: 'object123',
6161
recurrenceId: 'recurrence456',
62-
darkText: true,
6362
percent: 100,
6463
},
6564
}
6665

6766
eventRender({ event, el })
6867

69-
expect(el.outerHTML).toEqual('<div class="fc-event-nc-alarms" data-object-id="object123" data-recurrence-id="recurrence456"><div class="fc-content"><span class="fc-time">2pm</span><span class="fc-title">Title 123</span><span class="icon-event-reminder icon-event-reminder--dark"></span></div></div>')
70-
})
71-
72-
it('should add an alarm bell icon if event has an alarm - light', () => {
73-
const fcTime = document.createElement('span')
74-
fcTime.classList.add('fc-time')
75-
fcTime.appendChild(document.createTextNode('2pm'))
76-
const fcTitle = document.createElement('span')
77-
fcTitle.classList.add('fc-title')
78-
fcTitle.appendChild(document.createTextNode('Title 123'))
79-
80-
const fcContent = document.createElement('div')
81-
fcContent.classList.add('fc-content')
82-
fcContent.appendChild(fcTime)
83-
fcContent.appendChild(fcTitle)
84-
85-
const el = document.createElement('div')
86-
el.classList.add('fc-event-nc-alarms')
87-
el.appendChild(fcContent)
88-
89-
const event = {
90-
source: {},
91-
extendedProps: {
92-
objectId: 'object123',
93-
recurrenceId: 'recurrence456',
94-
darkText: false,
95-
percent: 100,
96-
},
97-
}
98-
99-
eventRender({ event, el })
100-
101-
expect(el.outerHTML).toEqual('<div class="fc-event-nc-alarms" data-object-id="object123" data-recurrence-id="recurrence456"><div class="fc-content"><span class="fc-time">2pm</span><span class="fc-title">Title 123</span><span class="icon-event-reminder icon-event-reminder--light"></span></div></div>')
68+
expect(el.outerHTML).toContain('<div class="fc-event-nc-alarms" data-object-id="object123" data-recurrence-id="recurrence456">')
69+
expect(el.outerHTML).toContain('<span class="icon-event-reminder"><svg viewBox="0 0 24 24">')
10270
})
10371

10472
// TODO: fix me later

tests/javascript/unit/utils/color.test.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55
import {
6-
generateTextColorForHex,
76
generateTextColorForRGB,
8-
hexToRGB,
97
isLight,
108
uidToHexColor,
119
detectColor,
@@ -15,13 +13,6 @@ import {
1513

1614
describe('utils/color test suite', () => {
1715

18-
it('should provide a function to generate a text-color from an rgb string', () => {
19-
expect(generateTextColorForHex('#fff')).toEqual('#000000')
20-
expect(generateTextColorForHex('#000')).toEqual('#FAFAFA')
21-
expect(generateTextColorForHex('#FF00FF')).toEqual('#FAFAFA')
22-
expect(generateTextColorForHex('#00FF00')).toEqual('#000000')
23-
})
24-
2516
it('should provide a function to generate a text-color from rgb values', () => {
2617
expect(generateTextColorForRGB({ red: 255, green: 255, blue: 255 })).toEqual('#000000')
2718
expect(generateTextColorForRGB({ red: 0, green: 0, blue: 0 })).toEqual('#FAFAFA')
@@ -34,10 +25,6 @@ describe('utils/color test suite', () => {
3425
expect(isLight({ red: 0, green: 0, blue: 0 })).toEqual(false);
3526
})
3627

37-
it('should provide a RGB object for a hex string', () => {
38-
expect(hexToRGB('#C98879')).toEqual({ red: 201, green: 136, blue: 121 })
39-
})
40-
4128
it('should provide a HEX string for a UID', () => {
4229
expect(uidToHexColor('uid123')).toEqual('#C98879')
4330
expect(uidToHexColor('')).toEqual('#0082C9')

0 commit comments

Comments
 (0)