Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
6 changed files with 26 additions and 85 deletions
10
README.md
10
README.md
|
@ -7,9 +7,8 @@ The Calendar Merger project is a web application that allows users to merge mult
|
||||||
- Add calendars
|
- Add calendars
|
||||||
- Specify prefixes for each calendar
|
- Specify prefixes for each calendar
|
||||||
- Override event summaries if desired
|
- 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 ever so often.
|
The application also generates a unique URL for the merged calendar and updates it every hour using a cron job.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -17,8 +16,7 @@ The application also generates a unique URL for the merged calendar and updates
|
||||||
- Specify prefixes for each calendar
|
- Specify prefixes for each calendar
|
||||||
- Optionally override event summaries
|
- Optionally override event summaries
|
||||||
- Generate a unique URL for the merged calendar
|
- Generate a unique URL for the merged calendar
|
||||||
- Automatically update the merged calendar
|
- Automatically update the merged calendar every hour
|
||||||
- Search for and edit previously merged calendars
|
|
||||||
|
|
||||||
## Calender Directory
|
## Calender Directory
|
||||||
|
|
||||||
|
@ -40,7 +38,7 @@ The application also generates a unique URL for the merged calendar and updates
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
```bash
|
```bash
|
||||||
git clone git@forge.ftt.gmbh:Progyssey/Calmerge.git
|
git clone git@forge.ftt.gmbh:ryanmwangi/CalMerger.git
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install the dependencies:
|
2. Install the dependencies:
|
||||||
|
@ -78,7 +76,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:
|
If you prefer to use Docker Compose, ensure you have a `docker-compose.yml` file in your project directory. Then, run:
|
||||||
|
|
||||||
```bash
|
```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.
|
This will automatically build and start the container based on the configuration in the `docker-compose.yml` file.
|
||||||
|
|
|
@ -9,6 +9,6 @@ services:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- NODE_PORT=3012
|
- NODE_PORT=3012
|
||||||
volumes:
|
volumes:
|
||||||
- ./calendar:/Calmerge/calendar
|
- ./calendar:/app/calendar
|
||||||
- ./logs:/Calmerge/logs
|
- ./logs:/app/logs
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
FROM node:18-alpine
|
FROM node:18-alpine
|
||||||
|
|
||||||
# Set working directory inside the container
|
# Set working directory inside the container
|
||||||
WORKDIR /Calmerge
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
# Copy package.json and package-lock.json for installing dependencies
|
# Copy package.json and package-lock.json for installing dependencies
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
|
@ -10,20 +10,21 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>📅 Calendar Merger</h1>
|
<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 -->
|
<!-- Merge Form -->
|
||||||
<div class="form-card">
|
<div class="form-card">
|
||||||
<form id="merge-form">
|
<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>
|
<h2>Merge Calendars</h2>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
|
@ -31,14 +32,6 @@
|
||||||
placeholder="Enter collection name"
|
placeholder="Enter collection name"
|
||||||
class="input-field">
|
class="input-field">
|
||||||
</div>
|
</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 id="calendars">
|
||||||
<div class="calendar-entry">
|
<div class="calendar-entry">
|
||||||
|
@ -48,10 +41,6 @@
|
||||||
<input type="checkbox" id="override-0">
|
<input type="checkbox" id="override-0">
|
||||||
<label for="override-0">Override</label>
|
<label for="override-0">Override</label>
|
||||||
</div>
|
</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>
|
<button type="button" class="remove-btn" title="Remove calendar"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,7 +57,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Result Section -->
|
<!-- Result Section -->
|
||||||
<div id="result"></div>
|
<div id="result-container">
|
||||||
|
<p class="result-info">After merging, your calendar URL will appear here.</p>
|
||||||
|
<div id="result"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -129,10 +129,6 @@ function loadCalendarConfig(config, calendarName) {
|
||||||
<input type="checkbox" id="override-${calendarIndex}" ${calendar.override ? 'checked' : ''}>
|
<input type="checkbox" id="override-${calendarIndex}" ${calendar.override ? 'checked' : ''}>
|
||||||
<label for="override-${calendarIndex}">Override</label>
|
<label for="override-${calendarIndex}">Override</label>
|
||||||
</div>
|
</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>
|
<button type="button" class="remove-btn" title="Remove calendar"></button>
|
||||||
`;
|
`;
|
||||||
calendars.appendChild(newCalendar);
|
calendars.appendChild(newCalendar);
|
||||||
|
@ -154,10 +150,6 @@ addCalendarButton.addEventListener('click', () => {
|
||||||
<input type="checkbox" id="override-${calendarIndex}">
|
<input type="checkbox" id="override-${calendarIndex}">
|
||||||
<label for="override-${calendarIndex}">Override</label>
|
<label for="override-${calendarIndex}">Override</label>
|
||||||
</div>
|
</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>
|
<button type="button" class="remove-btn" title="Remove calendar"></button>
|
||||||
`;
|
`;
|
||||||
calendars.appendChild(newCalendar);
|
calendars.appendChild(newCalendar);
|
||||||
|
@ -167,7 +159,6 @@ addCalendarButton.addEventListener('click', () => {
|
||||||
// Event listener for form submission
|
// Event listener for form submission
|
||||||
form.addEventListener('submit', (event) => {
|
form.addEventListener('submit', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const globalKeepProperties = document.getElementById('global-keep-properties').checked;
|
|
||||||
const linkGroupName = document.getElementById('link-group-name').value;
|
const linkGroupName = document.getElementById('link-group-name').value;
|
||||||
const calendarsData = [];
|
const calendarsData = [];
|
||||||
let valid = true; // Flag to track URL validity
|
let valid = true; // Flag to track URL validity
|
||||||
|
@ -175,9 +166,8 @@ form.addEventListener('submit', (event) => {
|
||||||
for (let i = 0; i < calendarIndex; i++) {
|
for (let i = 0; i < calendarIndex; i++) {
|
||||||
const prefix = document.getElementById(`prefix-${i}`);
|
const prefix = document.getElementById(`prefix-${i}`);
|
||||||
const override = document.getElementById(`override-${i}`);
|
const override = document.getElementById(`override-${i}`);
|
||||||
const keepProperties = document.getElementById(`keep-properties-${i}`);
|
|
||||||
const url = document.getElementById(`url-${i}`);
|
const url = document.getElementById(`url-${i}`);
|
||||||
if (prefix && override && keepProperties && url && url.value) {
|
if (prefix && override && url && url.value) {
|
||||||
// Validate the URL
|
// Validate the URL
|
||||||
if (url.value.startsWith('http') && !isValidUrl(url.value)) {
|
if (url.value.startsWith('http') && !isValidUrl(url.value)) {
|
||||||
valid = false; // Set flag to false if any URL is invalid
|
valid = false; // Set flag to false if any URL is invalid
|
||||||
|
@ -186,8 +176,7 @@ form.addEventListener('submit', (event) => {
|
||||||
calendarsData.push({
|
calendarsData.push({
|
||||||
prefix: prefix.value,
|
prefix: prefix.value,
|
||||||
override: override.checked,
|
override: override.checked,
|
||||||
url: url.value,
|
url: url.value
|
||||||
keepProperties: globalKeepProperties || keepProperties.checked
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ export function addEventsToCalendar(newCalendar, calendars) {
|
||||||
|
|
||||||
calendars.forEach((calendarRaw) => {
|
calendars.forEach((calendarRaw) => {
|
||||||
try {
|
try {
|
||||||
const { data, prefix, override, keepProperties = false } = calendarRaw; // Extract prefix, override and keepProperties
|
const { data, prefix, override } = calendarRaw; // Extract prefix and override
|
||||||
const calendar = new ICAL.Component(ICAL.parse(calendarRaw.data));
|
const calendar = new ICAL.Component(ICAL.parse(calendarRaw.data));
|
||||||
|
|
||||||
// Extract METHOD from the parsed data (if available)
|
// Extract METHOD from the parsed data (if available)
|
||||||
|
@ -98,44 +98,6 @@ export function addEventsToCalendar(newCalendar, calendars) {
|
||||||
newEvent.summary = prefix || 'Busy';
|
newEvent.summary = prefix || 'Busy';
|
||||||
} else {
|
} else {
|
||||||
newEvent.summary = prefix ? `${prefix} ${event.summary}` : event.summary;
|
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;
|
if (event.location) newEvent.location = event.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue