mirror of
https://github.com/formax68/memoChron.git
synced 2026-07-22 05:45:44 +00:00
Add CUTYPE filtering for attendees
- Filter attendees by calendar user type (Individual, Group, Resource, Room, Unknown) - Default to showing only actual people (Individual and unspecified attendees) - Exclude conference rooms, resources, and equipment by default - Add global settings with toggle controls for each attendee type - Support calendar-specific filtering overrides - Automatic calendar refresh when filter settings change - Full RFC 5545 CUTYPE compliance - Update README with feature documentation - Version bump to 1.8.5
This commit is contained in:
parent
95d5f28022
commit
ea492eef64
10 changed files with 162 additions and 17 deletions
46
README.md
46
README.md
|
|
@ -22,6 +22,7 @@ It showcases a list of your calendar events. When you click on an event, it crea
|
|||
- 🎨 **Customizable**: Configure note templates, locations, and naming conventions
|
||||
- 🌈 **Calendar Colors**: Visually distinguish between different calendar sources with an advanced color picker
|
||||
- 👥 **Attendee Links**: Automatically create wiki links for event attendees
|
||||
- 🔍 **Attendee Filtering**: Filter attendees by type (CUTYPE) to exclude rooms, resources, and other non-person entities
|
||||
- ⚙️ **Calendar-Specific Configuration**: Override default settings for individual calendars
|
||||
- 📝 **Embedded Views**: Embed calendar and agenda views directly in your notes using dataview-like code blocks
|
||||
|
||||
|
|
@ -397,6 +398,32 @@ This format follows [Obsidian's list property requirements](https://help.obsidia
|
|||
2. Enable "Create links for attendees"
|
||||
3. Links will be created as `[[Name]]` - Obsidian will find the notes regardless of their folder location
|
||||
|
||||
#### Attendee Type Filtering (CUTYPE)
|
||||
|
||||
Calendar files from companies often include non-person attendees like conference rooms, equipment, and resources. MemoChron can filter these out automatically based on the iCalendar CUTYPE parameter (RFC 5545).
|
||||
|
||||
**Available Attendee Types:**
|
||||
|
||||
- **Individual** - Actual people (included by default)
|
||||
- **Unspecified** - Attendees without a CUTYPE parameter, usually people (included by default)
|
||||
- **Group** - Distribution lists or groups (excluded by default)
|
||||
- **Resource** - Equipment or other resources (excluded by default)
|
||||
- **Room** - Conference rooms or meeting spaces (excluded by default)
|
||||
- **Unknown** - Unknown attendee types (excluded by default)
|
||||
|
||||
**Configuring Attendee Filtering:**
|
||||
|
||||
1. Go to Settings > MemoChron > Notes
|
||||
2. Find "Filter attendees by type" section
|
||||
3. Toggle each attendee type on/off based on what you want to include
|
||||
4. Calendar data refreshes automatically when you change settings
|
||||
|
||||
**Default Behavior:**
|
||||
By default, only actual people (Individual and Unspecified) are included in your notes. Conference rooms, resources, and other non-person entities are automatically filtered out.
|
||||
|
||||
**Calendar-Specific Filtering:**
|
||||
You can override the global attendee type filter for individual calendars. This is useful if some calendars need different filtering rules (e.g., including rooms for facility management calendars).
|
||||
|
||||
### Organizing Notes with Folder Templates
|
||||
|
||||
MemoChron supports flexible folder organization using customizable templates. You can organize your event notes into date-based subfolders automatically.
|
||||
|
|
@ -451,6 +478,7 @@ MemoChron supports flexible folder organization using customizable templates. Yo
|
|||
- **Tags**: Set default tags for event notes
|
||||
- **Enable Calendar Colors**: Toggle color differentiation for calendar sources
|
||||
- **Create Links for Attendees**: Automatically create wiki links for event attendees
|
||||
- **Filter Attendees by Type**: Choose which attendee types to include (Individual, Group, Resource, Room, etc.)
|
||||
- When enabled, each calendar gets a customizable color
|
||||
- Colors appear as dots in the calendar view and left borders in the agenda view
|
||||
- Click the color button to open an advanced visual color picker featuring:
|
||||
|
|
@ -467,6 +495,24 @@ MemoChron supports flexible folder organization using customizable templates. Yo
|
|||
- Basic calendar views (monthly with agenda)
|
||||
- Local ICS files are not automatically watched for changes (use manual refresh)
|
||||
|
||||
## What's New in v1.8.5
|
||||
|
||||
### 🔍 Attendee Type Filtering (CUTYPE)
|
||||
|
||||
- **Smart Filtering**: Automatically filter attendees by type to exclude rooms, resources, and other non-person entities
|
||||
- **RFC 5545 Compliance**: Full support for iCalendar CUTYPE parameter (Individual, Group, Resource, Room, Unknown)
|
||||
- **Customizable Settings**: Choose which attendee types to include globally or per-calendar
|
||||
- **Default Behavior**: Only actual people are included by default (Individual and Unspecified types)
|
||||
- **Calendar-Specific Overrides**: Apply different filtering rules to individual calendars
|
||||
- **Automatic Refresh**: Calendar data refreshes automatically when you change filter settings
|
||||
|
||||
**Why This Matters:**
|
||||
Corporate calendars often include conference rooms and equipment as attendees, cluttering your meeting notes with entries like "Conference Room A" and "Projector". Now you can filter these out automatically while keeping actual people in your attendee lists.
|
||||
|
||||
**Example Use Case:**
|
||||
Before filtering: `Attendees: John Doe, Conference Room A, Jane Smith, Projector`
|
||||
After filtering: `Attendees: John Doe, Jane Smith`
|
||||
|
||||
## What's New in v1.8.4
|
||||
|
||||
### 📋 YAML Properties Support for Attendees
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "memochron",
|
||||
"name": "MemoChron",
|
||||
"version": "1.8.4",
|
||||
"version": "1.8.5",
|
||||
"minAppVersion": "1.8.9",
|
||||
"description": "Calendar integration and note creation with support for public iCalendar URLs",
|
||||
"author": "Michalis Efstratiadis",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "memochron",
|
||||
"version": "1.8.4",
|
||||
"version": "1.8.5",
|
||||
"description": "Calendar integration and note creation plugin for Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -611,7 +611,7 @@ export class CalendarService {
|
|||
isAllDay: isAllDay,
|
||||
description: event.description,
|
||||
location: event.location,
|
||||
attendees: this.extractAttendees(vevent),
|
||||
attendees: this.extractAttendees(vevent, source),
|
||||
source: source.name,
|
||||
sourceId: source.url,
|
||||
color: source.color,
|
||||
|
|
@ -644,7 +644,7 @@ export class CalendarService {
|
|||
isAllDay: isAllDay,
|
||||
description: event.description,
|
||||
location: event.location,
|
||||
attendees: this.extractAttendees(event.component),
|
||||
attendees: this.extractAttendees(event.component, source),
|
||||
source: source.name,
|
||||
sourceId: source.url,
|
||||
color: source.color,
|
||||
|
|
@ -697,19 +697,36 @@ export class CalendarService {
|
|||
);
|
||||
}
|
||||
|
||||
private extractAttendees(vevent: Component): string[] {
|
||||
private extractAttendees(vevent: Component, source: CalendarSource): string[] {
|
||||
const attendees: string[] = [];
|
||||
|
||||
|
||||
if (!vevent.hasProperty("attendee")) {
|
||||
return attendees;
|
||||
}
|
||||
|
||||
|
||||
const attendeeProps = vevent.getAllProperties("attendee");
|
||||
|
||||
|
||||
// Get CUTYPE filter: calendar-specific override or global default
|
||||
const calendarSettings = source.notesSettings;
|
||||
const filteredCuTypes =
|
||||
(calendarSettings?.filteredCuTypes) ||
|
||||
this.plugin.settings.filteredCuTypes;
|
||||
|
||||
for (const prop of attendeeProps) {
|
||||
const value = prop.getFirstValue();
|
||||
const cn = prop.getParameter("cn"); // Common Name parameter
|
||||
|
||||
const cutype = prop.getParameter("cutype"); // CUTYPE parameter
|
||||
|
||||
// Normalize CUTYPE: undefined/null/empty treated as "" (unspecified)
|
||||
// RFC 5545: CUTYPE is case-insensitive, normalize to uppercase
|
||||
const normalizedCuType = (cutype || "").toUpperCase();
|
||||
|
||||
// Filter based on CUTYPE - skip if not in allowed list
|
||||
if (!filteredCuTypes.includes(normalizedCuType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract name or email
|
||||
if (cn) {
|
||||
attendees.push(cn);
|
||||
} else if (value) {
|
||||
|
|
@ -718,7 +735,7 @@ export class CalendarService {
|
|||
attendees.push(email);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return attendees;
|
||||
}
|
||||
|
||||
|
|
@ -749,7 +766,7 @@ export class CalendarService {
|
|||
isAllDay: isAllDay,
|
||||
description: exception.description,
|
||||
location: exception.location,
|
||||
attendees: this.extractAttendees(exception.component),
|
||||
attendees: this.extractAttendees(exception.component, source),
|
||||
source: source.name,
|
||||
sourceId: source.url,
|
||||
color: source.color,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ export class IcsImportService {
|
|||
* Parse ICS file content and validate it contains exactly one event
|
||||
* @throws Error if the file contains no events or multiple events
|
||||
*/
|
||||
static parseSingleEvent(icsContent: string): CalendarEvent {
|
||||
static parseSingleEvent(
|
||||
icsContent: string,
|
||||
filteredCuTypes?: string[]
|
||||
): CalendarEvent {
|
||||
try {
|
||||
const jcalData = parse(icsContent);
|
||||
const comp = new Component(jcalData);
|
||||
|
|
@ -80,7 +83,7 @@ export class IcsImportService {
|
|||
isAllDay: isAllDay,
|
||||
description: event.description,
|
||||
location: event.location,
|
||||
attendees: IcsImportService.extractAttendees(vevent),
|
||||
attendees: IcsImportService.extractAttendees(vevent, filteredCuTypes),
|
||||
source: "Imported",
|
||||
sourceId: "imported", // Special source ID for imported events
|
||||
};
|
||||
|
|
@ -108,9 +111,12 @@ export class IcsImportService {
|
|||
return false;
|
||||
}
|
||||
|
||||
private static extractAttendees(vevent: Component): string[] {
|
||||
private static extractAttendees(vevent: Component, filteredCuTypes?: string[]): string[] {
|
||||
const attendees: string[] = [];
|
||||
|
||||
// Use default if not provided
|
||||
const cuTypeFilter = filteredCuTypes || ["INDIVIDUAL", ""];
|
||||
|
||||
if (!vevent.hasProperty("attendee")) {
|
||||
return attendees;
|
||||
}
|
||||
|
|
@ -120,7 +126,18 @@ export class IcsImportService {
|
|||
for (const prop of attendeeProps) {
|
||||
const value = prop.getFirstValue();
|
||||
const cn = prop.getParameter("cn"); // Common Name parameter
|
||||
const cutype = prop.getParameter("cutype"); // CUTYPE parameter
|
||||
|
||||
// Normalize CUTYPE: undefined/null/empty treated as "" (unspecified)
|
||||
// RFC 5545: CUTYPE is case-insensitive, normalize to uppercase
|
||||
const normalizedCuType = (cutype || "").toUpperCase();
|
||||
|
||||
// Filter based on CUTYPE - skip if not in allowed list
|
||||
if (!cuTypeFilter.includes(normalizedCuType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract name or email
|
||||
if (cn) {
|
||||
attendees.push(cn);
|
||||
} else if (value) {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||
this.renderDefaultFrontmatter();
|
||||
this.renderNoteTemplate();
|
||||
this.renderAttendeeSettings();
|
||||
this.renderAttendeeFiltering();
|
||||
this.renderDefaultTags();
|
||||
}
|
||||
|
||||
|
|
@ -765,6 +766,51 @@ export class SettingsTab extends PluginSettingTab {
|
|||
);
|
||||
}
|
||||
|
||||
private renderAttendeeFiltering(): void {
|
||||
new Setting(this.containerEl)
|
||||
.setName("Filter attendees by type")
|
||||
.setDesc(
|
||||
"Select which types of attendees to include in event notes. Most calendars only mark rooms/resources with CUTYPE, leaving people as 'Individual' or unspecified."
|
||||
)
|
||||
.setHeading();
|
||||
|
||||
const cuTypeOptions = [
|
||||
{ value: "INDIVIDUAL", label: "Individual (people)", description: "Actual people attending the event" },
|
||||
{ value: "", label: "Unspecified", description: "No CUTYPE parameter (usually people)" },
|
||||
{ value: "GROUP", label: "Group", description: "Distribution lists or groups" },
|
||||
{ value: "RESOURCE", label: "Resource", description: "Equipment or other resources" },
|
||||
{ value: "ROOM", label: "Room", description: "Conference rooms or meeting spaces" },
|
||||
{ value: "UNKNOWN", label: "Unknown", description: "Unknown attendee type" },
|
||||
];
|
||||
|
||||
cuTypeOptions.forEach(({ value, label, description }) => {
|
||||
new Setting(this.containerEl)
|
||||
.setName(label)
|
||||
.setDesc(description)
|
||||
.addToggle((toggle) => {
|
||||
const isEnabled = this.plugin.settings.filteredCuTypes.includes(value);
|
||||
toggle
|
||||
.setValue(isEnabled)
|
||||
.onChange(async (enabled) => {
|
||||
if (enabled) {
|
||||
// Add to filter list if not already present
|
||||
if (!this.plugin.settings.filteredCuTypes.includes(value)) {
|
||||
this.plugin.settings.filteredCuTypes.push(value);
|
||||
}
|
||||
} else {
|
||||
// Remove from filter list
|
||||
this.plugin.settings.filteredCuTypes =
|
||||
this.plugin.settings.filteredCuTypes.filter(t => t !== value);
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
// Refresh calendar data to apply new filter
|
||||
this.plugin.calendarView?.refreshEvents();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private renderFolderPathTemplate(): void {
|
||||
const templateSetting = new Setting(this.containerEl)
|
||||
.setName("Folder path template")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
DEFAULT_FRONTMATTER,
|
||||
DEFAULT_TAGS,
|
||||
DEFAULT_FIRST_DAY_OF_WEEK,
|
||||
DEFAULT_FILTERED_CUTYPES,
|
||||
} from "../utils/constants";
|
||||
|
||||
export interface CalendarNotesSettings {
|
||||
|
|
@ -23,6 +24,7 @@ export interface CalendarNotesSettings {
|
|||
noteTemplate?: string; // Override default template
|
||||
folderPathTemplate?: string; // Override default folder path template
|
||||
enableAttendeeLinks?: boolean; // Override default attendee links setting
|
||||
filteredCuTypes?: string[]; // Override global CUTYPE filtering
|
||||
}
|
||||
|
||||
export interface CalendarSource {
|
||||
|
|
@ -51,6 +53,7 @@ export interface MemoChronSettings {
|
|||
showDailyNoteInAgenda: boolean; // Show daily note as an entry in the agenda
|
||||
dailyNoteColor?: string; // Color for daily note entry when calendar colors are enabled
|
||||
enableAttendeeLinks: boolean; // Create wiki links for attendees
|
||||
filteredCuTypes: string[]; // CUTYPE values to INCLUDE (default: ["INDIVIDUAL", ""])
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: MemoChronSettings = {
|
||||
|
|
@ -81,4 +84,5 @@ export const DEFAULT_SETTINGS: MemoChronSettings = {
|
|||
enableCalendarColors: false, // Disabled by default
|
||||
showDailyNoteInAgenda: false, // Disabled by default
|
||||
enableAttendeeLinks: false, // Disabled by default
|
||||
filteredCuTypes: DEFAULT_FILTERED_CUTYPES, // Include individuals and unspecified
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,4 +34,15 @@ export const CALENDAR_COLOR_PALETTE = [
|
|||
'var(--color-yellow)',
|
||||
'var(--color-pink)',
|
||||
'var(--color-cyan)',
|
||||
];
|
||||
];
|
||||
|
||||
// RFC 5545 CUTYPE values
|
||||
export const CUTYPE_INDIVIDUAL = "INDIVIDUAL";
|
||||
export const CUTYPE_GROUP = "GROUP";
|
||||
export const CUTYPE_RESOURCE = "RESOURCE";
|
||||
export const CUTYPE_ROOM = "ROOM";
|
||||
export const CUTYPE_UNKNOWN = "UNKNOWN";
|
||||
|
||||
// Default filtering: include individuals and unspecified
|
||||
// Per RFC 5545, INDIVIDUAL is the default when CUTYPE is not specified
|
||||
export const DEFAULT_FILTERED_CUTYPES: string[] = [CUTYPE_INDIVIDUAL, ""];
|
||||
|
|
@ -753,7 +753,10 @@ export class CalendarView extends ItemView {
|
|||
const content = await this.readFile(file);
|
||||
|
||||
// Parse and validate single event
|
||||
const event = IcsImportService.parseSingleEvent(content);
|
||||
const event = IcsImportService.parseSingleEvent(
|
||||
content,
|
||||
this.plugin.settings.filteredCuTypes
|
||||
);
|
||||
|
||||
// Create note from the event
|
||||
await this.createNoteFromImportedEvent(event);
|
||||
|
|
|
|||
|
|
@ -31,5 +31,6 @@
|
|||
"1.8.1": "1.8.9",
|
||||
"1.8.2": "1.8.9",
|
||||
"1.8.3": "1.8.9",
|
||||
"1.8.4": "1.8.9"
|
||||
"1.8.4": "1.8.9",
|
||||
"1.8.5": "1.8.9"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue