Compare commits

...

7 commits

6 changed files with 85 additions and 26 deletions

View file

@ -7,8 +7,9 @@ The Calendar Merger project is a web application that allows users to merge mult
- Add calendars
- Specify prefixes for each calendar
- Override event summaries if desired
- Search for and edit previously merged calendars
The application also generates a unique URL for the merged calendar and updates it every hour using a cron job.
The application also generates a unique URL for the merged calendar and updates it ever so often.
## Features
@ -16,7 +17,8 @@ The application also generates a unique URL for the merged calendar and updates
- Specify prefixes for each calendar
- Optionally override event summaries
- Generate a unique URL for the merged calendar
- Automatically update the merged calendar every hour
- Automatically update the merged calendar
- Search for and edit previously merged calendars
## Calender Directory
@ -38,7 +40,7 @@ The application also generates a unique URL for the merged calendar and updates
1. Clone the repository:
```bash
git clone git@forge.ftt.gmbh:ryanmwangi/CalMerger.git
git clone git@forge.ftt.gmbh:Progyssey/Calmerge.git
```
2. Install the dependencies:
@ -76,7 +78,7 @@ This maps the container's port `3012` to the host system's port `3012`. The appl
If you prefer to use Docker Compose, ensure you have a `docker-compose.yml` file in your project directory. Then, run:
```bash
docker-compose up -d
docker compose up -d
```
This will automatically build and start the container based on the configuration in the `docker-compose.yml` file.

View file

@ -9,6 +9,6 @@ services:
- NODE_ENV=production
- NODE_PORT=3012
volumes:
- ./calendar:/app/calendar
- ./logs:/app/logs
- ./calendar:/Calmerge/calendar
- ./logs:/Calmerge/logs
restart: unless-stopped

View file

@ -2,7 +2,7 @@
FROM node:18-alpine
# Set working directory inside the container
WORKDIR /usr/src/app
WORKDIR /Calmerge
# Copy package.json and package-lock.json for installing dependencies
COPY package*.json ./

View file

@ -10,21 +10,20 @@
<div class="container">
<h1>📅 Calendar Merger</h1>
<!-- Search Section -->
<div class="form-card search-section">
<h2>Find Existing Calendar</h2>
<div class="input-group">
<div class="search-container">
<input type="text" id="calendar-search" placeholder="Enter calendar name">
<button type="button" id="search-btn" class="button primary-btn">Search</button>
</div>
<div id="search-result"></div>
</div>
</div>
<!-- Merge Form -->
<div class="form-card">
<form id="merge-form">
<!-- Find Existing Calendar Section -->
<div class="input-group">
<h2>Find Existing Calendar</h2>
<div class="search-container">
<input type="text" id="calendar-search" placeholder="Enter calendar name">
<button type="button" id="search-btn" class="button primary-btn">Search</button>
</div>
<div id="search-result"></div>
</div>
<!-- Merge Calendars Section -->
<h2>Merge Calendars</h2>
<div class="input-group">
<input type="text"
@ -32,6 +31,14 @@
placeholder="Enter collection name"
class="input-field">
</div>
<!-- Global Settings -->
<div class="input-group">
<div class="checkbox-group">
<input type="checkbox" id="global-keep-properties" checked>
<label for="global-keep-properties">Keep all event properties (location, description, etc.) for all calendars</label>
</div>
</div>
<div id="calendars">
<div class="calendar-entry">
@ -41,6 +48,10 @@
<input type="checkbox" id="override-0">
<label for="override-0">Override</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="keep-properties-0" checked>
<label for="keep-properties-0">Keep properties</label>
</div>
<button type="button" class="remove-btn" title="Remove calendar"></button>
</div>
</div>
@ -57,10 +68,7 @@
</div>
<!-- Result Section -->
<div id="result-container">
<p class="result-info">After merging, your calendar URL will appear here.</p>
<div id="result"></div>
</div>
<div id="result"></div>
</div>

View file

@ -129,6 +129,10 @@ function loadCalendarConfig(config, calendarName) {
<input type="checkbox" id="override-${calendarIndex}" ${calendar.override ? 'checked' : ''}>
<label for="override-${calendarIndex}">Override</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="keep-properties-${calendarIndex}" ${calendar.keepProperties ? 'checked' : ''}>
<label for="keep-properties-${calendarIndex}">Keep properties</label>
</div>
<button type="button" class="remove-btn" title="Remove calendar"></button>
`;
calendars.appendChild(newCalendar);
@ -150,6 +154,10 @@ addCalendarButton.addEventListener('click', () => {
<input type="checkbox" id="override-${calendarIndex}">
<label for="override-${calendarIndex}">Override</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="keep-properties-${calendarIndex}" checked>
<label for="keep-properties-${calendarIndex}">Keep properties</label>
</div>
<button type="button" class="remove-btn" title="Remove calendar"></button>
`;
calendars.appendChild(newCalendar);
@ -159,6 +167,7 @@ addCalendarButton.addEventListener('click', () => {
// Event listener for form submission
form.addEventListener('submit', (event) => {
event.preventDefault();
const globalKeepProperties = document.getElementById('global-keep-properties').checked;
const linkGroupName = document.getElementById('link-group-name').value;
const calendarsData = [];
let valid = true; // Flag to track URL validity
@ -166,8 +175,9 @@ form.addEventListener('submit', (event) => {
for (let i = 0; i < calendarIndex; i++) {
const prefix = document.getElementById(`prefix-${i}`);
const override = document.getElementById(`override-${i}`);
const keepProperties = document.getElementById(`keep-properties-${i}`);
const url = document.getElementById(`url-${i}`);
if (prefix && override && url && url.value) {
if (prefix && override && keepProperties && url && url.value) {
// Validate the URL
if (url.value.startsWith('http') && !isValidUrl(url.value)) {
valid = false; // Set flag to false if any URL is invalid
@ -176,7 +186,8 @@ form.addEventListener('submit', (event) => {
calendarsData.push({
prefix: prefix.value,
override: override.checked,
url: url.value
url: url.value,
keepProperties: globalKeepProperties || keepProperties.checked
});
}
}

View file

@ -47,7 +47,7 @@ export function addEventsToCalendar(newCalendar, calendars) {
calendars.forEach((calendarRaw) => {
try {
const { data, prefix, override } = calendarRaw; // Extract prefix and override
const { data, prefix, override, keepProperties = false } = calendarRaw; // Extract prefix, override and keepProperties
const calendar = new ICAL.Component(ICAL.parse(calendarRaw.data));
// Extract METHOD from the parsed data (if available)
@ -98,6 +98,44 @@ export function addEventsToCalendar(newCalendar, calendars) {
newEvent.summary = prefix || 'Busy';
} else {
newEvent.summary = prefix ? `${prefix} ${event.summary}` : event.summary;
}
// Handle all properties based on keepProperties setting
if (keepProperties) {
// Preserve all available properties
if (event.location) newEvent.location = event.location;
if (event.description) newEvent.description = event.description;
// Copy additional properties from the original component
const originalComponent = vevent;
// Preserve URL (for call links)
const url = originalComponent.getFirstPropertyValue('url');
if (url) newEvent.component.updatePropertyWithValue('url', url);
// Preserve attendees
const attendees = originalComponent.getAllProperties('attendee');
attendees.forEach(attendee => {
newEvent.component.addProperty(attendee);
});
// Preserve categories
const categories = originalComponent.getFirstPropertyValue('categories');
if (categories) newEvent.component.updatePropertyWithValue('categories', categories);
// Preserve organizer
const organizer = originalComponent.getFirstPropertyValue('organizer');
if (organizer) newEvent.component.updatePropertyWithValue('organizer', organizer);
// Preserve status
const status = originalComponent.getFirstPropertyValue('status');
if (status) newEvent.component.updatePropertyWithValue('status', status);
// Preserve class (privacy)
const eventClass = originalComponent.getFirstPropertyValue('class');
if (eventClass) newEvent.component.updatePropertyWithValue('class', eventClass);
} else if (!override) {
// Backward compatibility: only preserve location when not overriding
if (event.location) newEvent.location = event.location;
}