Pretty sure there are a few hold overs here from back in the LifeBalance days in the late 90’s If so then here’s some plugin code for you.
This plugin will allow you to tag a subset of tasks with a “place” where that place has open and close hours (defined in the plugin code) when you run the code it will tag and untag the task with “open” or “closed” based on the current time of the day.
This will work on both iOS and MacOs as I have included the URL to call it from a shortcut on iOS.
My use for this is to have 1 set of perspectives that don’t show me tasks that I don’t want to do during certain hours of the day.
The plugin will also create the tags for you so you don’t have to worry about typos between your tags and the code.
The one thing it can’t handle currently is having a place tagged with multiple places; as that can cause things to be flagged with both open and closed.
There is a second plug-in that will toggle everything to “open” on the first run; and on the second run it will toggle them back to the previous status.
OmniFocus Plugins Documentation
Plugin 1: Update Tags Based on Places
Overview
This plugin updates tasks with open
or closed
tags based on their Places
tags and the current time. It ensures tasks are tagged appropriately depending on whether the associated Places
tag is ‘open’ or ‘closed’.
Code
/*{
"author": "Bob",
"targets": ["omnifocus"],
"type": "action",
"identifier": "com.codedaptive.place-hours",
"version": "1.0",
"description": "A plug-in that updates tasks with open/closed tags based on place tags and current time.",
"label": "Update Tags Based on Places 1.0",
"mediumLabel": "Open-Close 1.0",
"longLabel": "Update Tags Based on Places 1.0",
"paletteLabel": "Open-Close 1.0"
}*/
(() => {
var action = new PlugIn.Action(function(selection) {
console.log("Invoked Update Tags Based on Places");
// Define open and close times for each place
const placeTimes = {
"office": {
default: [
{ open: "08:00", close: "17:00" }
],
overrides: {
Monday: [
{ open: "08:00", close: "17:00" },
{ open: "16:30", close: "17:00" } // Overlap period
],
Tuesday: [
{ open: "08:00", close: "17:00" },
{ open: "16:30", close: "17:00" } // Overlap period
],
Wednesday: [
{ open: "08:00", close: "17:00" },
{ open: "16:30", close: "17:00" } // Overlap period
],
Thursday: [
{ open: "08:00", close: "17:00" },
{ open: "16:30", close: "17:00" } // Overlap period
],
Friday: [
{ open: "08:00", close: "17:00" },
{ open: "16:30", close: "17:00" } // Overlap period
]
}
},
"home": {
default: [
{ open: "05:00", close: "08:00" },
{ open: "17:00", close: "23:30" }
],
overrides: {
Monday: [
{ open: "05:00", close: "08:00" },
{ open: "16:30", close: "23:30" } // Includes overlap period
],
Tuesday: [
{ open: "05:00", close: "08:00" },
{ open: "16:30", close: "23:30" }
],
Wednesday: [
{ open: "05:00", close: "08:00" },
{ open: "16:30", close: "23:30" }
],
Thursday: [
{ open: "05:00", close: "08:00" },
{ open: "16:30", close: "23:30" }
],
Friday: [
{ open: "05:00", close: "08:00" },
{ open: "16:30", close: "23:30" }
],
Saturday: [{ open: "19:00", close: "23:30" }],
Sunday: [{ open: "19:00", close: "23:30" }]
}
},
"errands": {
default: [
{ open: "07:00", close: "08:00" },
{ open: "11:30", close: "12:30" },
{ open: "17:00", close: "19:00" }
],
overrides: {
Saturday: [{ open: "07:00", close: "19:00" }],
Sunday: [{ open: "07:00", close: "19:00" }]
}
},
"heb": {
default: [
{ open: "07:00", close: "23:00" }
],
overrides: {
Saturday: [{ open: "07:00", close: "23:00" }],
Sunday: [{ open: "07:00", close: "23:00" }]
}
},
"anywhere": {
default: [
{ open: "07:00", close: "23:59" }
]
}
};
console.log("Place times initialized.");
// Utility: Parse time strings into timestamps
const parseTime = (timeString) => {
const [hours, minutes] = timeString.split(":").map(Number);
const now = new Date();
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, 0, 0).getTime();
};
// Get the current day and current time
const now = new Date();
const currentDay = now.toLocaleString("en-US", { weekday: "long" });
const currentTime = now.getTime();
console.log(`Current day: ${currentDay}, Current time: ${now.toTimeString()}`);
// Ensure a tag exists, creating it if necessary
const ensureTag = (name, parent = null) => {
let tag = parent
? parent.children.find(t => t.name === name)
: flattenedTags.find(t => t.name === name);
if (!tag) {
tag = parent
? new Tag(name, parent) // Create subtag under parent
: new Tag(name); // Create root-level tag
console.log(
parent
? `Created subtag: ${name} under parent: ${parent.name}`
: `Created root-level tag: ${name}`
);
}
return tag;
};
// Ensure "Places" and "Status" hierarchies exist
const placesTag = ensureTag("Places");
const statusTag = ensureTag("Status");
const openTag = ensureTag("open", statusTag);
const closedTag = ensureTag("closed", statusTag);
console.log("Tag hierarchies ensured.");
// Create or retrieve all place tags under "Places"
const placeTags = {};
for (const place of Object.keys(placeTimes)) {
const placeTag = ensureTag(place, placesTag);
placeTags[place] = placeTag;
}
console.log("All place tags ensured.");
// Function: Get active intervals for a place
const getActiveIntervals = (place) => {
const { default: defaultIntervals, overrides } = placeTimes[place];
if (overrides && overrides[currentDay] !== undefined) {
return overrides[currentDay];
}
return defaultIntervals;
};
// Function: Determine if current time is within any interval
const isOpenAtCurrentTime = (timeIntervals) => {
return timeIntervals.some(interval => {
const openTime = parseTime(interval.open);
const closeTime = parseTime(interval.close);
return currentTime >= openTime && currentTime <= closeTime;
});
};
// Process each task
console.log("Starting task processing...");
flattenedTasks.forEach(task => {
const taskTags = task.tags;
console.log(`Processing task: ${task.name}, Tags: ${taskTags.map(t => t.name).join(", ")}`);
// Check if the task has any of the place tags
for (const [placeName, placeTag] of Object.entries(placeTags)) {
if (taskTags.includes(placeTag)) {
const activeIntervals = getActiveIntervals(placeName);
const isOpen = isOpenAtCurrentTime(activeIntervals);
if (isOpen) {
console.log(`Task '${task.name}' place '${placeName}' is OPEN.`);
if (!taskTags.includes(openTag)) {
task.addTag(openTag);
console.log(`Added 'open' tag to task: ${task.name}`);
}
task.removeTag(closedTag);
} else {
console.log(`Task '${task.name}' place '${placeName}' is CLOSED.`);
if (!taskTags.includes(closedTag)) {
task.addTag(closedTag);
console.log(`Added 'closed' tag to task: ${task.name}`);
}
task.removeTag(openTag);
}
}
}
});
console.log("Task processing complete.");
});
return action;
})();
Installation
- Save the code above as a file named
update-tags-based-on-places.omnijs
. - Drag the file into OmniFocus on macOS or save it to iCloud for iOS sync.
- The plugin will appear under OmniFocus’s Plug-In preferences.
Shortcuts for macOS and iOS
To run the plugin via Shortcuts, use the following URL:
omnifocus:///omnijs-run?script=PlugIn.find%28%27com.codedaptive.place-hours%27%29.actions%5B0%5D.perform%28%29%3B
Plugin 2: Toggle All Open
Overview
This plugin toggles tasks associated with Places
tags between the open
and closed
states. It saves the original state of tasks in their notes to allow for restoration.
Code
/*{
"author": "Bob",
"targets": ["omnifocus"],
"type": "action",
"identifier": "com.codedaptive.toggle-all-open",
"version": "1.2",
"description": "Toggle all tasks between 'open' and their previous states.",
"label": "Toggle All Open",
"mediumLabel": "Toggle Open",
"longLabel": "Toggle All Open or Restore Previous State",
"paletteLabel": "Toggle Open"
}*/
(() => {
var action = new PlugIn.Action(function(selection) {
console.log("Starting toggle between 'all open' and 'restore state'...");
// Ensure required root tags exist
let placesTag = flattenedTags.find(t => t.name === "Places");
if (!placesTag) {
console.error('Root tag "Places" not found. Aborting.');
return;
}
let statusTag = flattenedTags.find(t => t.name === "Status");
if (!statusTag) {
statusTag = new Tag("Status");
console.log('Created root tag: "Status".');
}
let openTag = statusTag.children.find(t => t.name === "open");
if (!openTag) {
openTag = new Tag("open", statusTag);
console.log('Created tag: "open" under "Status".');
}
let closedTag = statusTag.children.find(t => t.name === "closed");
if (!closedTag) {
closedTag = new Tag("closed", statusTag);
console.log('Created tag: "closed" under "Status".');
}
// Check for the restore marker
const RESTORE_MARKER = "[State Saved]";
let hasRestoreMarker = false;
placesTag.children.forEach(placeTag => {
console.log(`Processing place: "${placeTag.name}"`);
placeTag.tasks.forEach(task => {
if (task.note.includes(RESTORE_MARKER)) {
hasRestoreMarker = true;
}
});
});
if (hasRestoreMarker) {
// Restore tasks to their original state
console.log("Restoring tasks to their previous state...");
placesTag.children.forEach(placeTag => {
placeTag.tasks.forEach(task => {
if (task.note.includes(RESTORE_MARKER)) {
let originalState = task.note.match(/\[State: (.*?)\]/)[1];
task.note = task.note.replace(`${RESTORE_MARKER} [State: ${originalState}]`, "");
// Clear current status
task.removeTag(openTag);
task.removeTag(closedTag);
// Restore original status
if (originalState === "open") {
task.addTag(openTag);
} else if (originalState === "closed") {
task.addTag(closedTag);
}
console.log(`Restored task: "${task.name}" to "${originalState}".`);
}
});
});
} else {
// Save the current state and set all tasks to "open"
console.log("Saving current state and setting all tasks to 'open'...");
placesTag.children.forEach(placeTag => {
placeTag.tasks.forEach(task => {
let currentState = "none";
if (task.tags.includes(openTag)) currentState = "open";
if (task.tags.includes(closedTag)) currentState = "closed";
// Save current state in the task note
task.note += ` ${RESTORE_MARKER} [State: ${currentState}]`;
// Set task to "open"
task.removeTag(closedTag);
task.addTag(openTag);
console.log(`Task "${task.name}" set to "open" with state saved.`);
});
});
}
console.log("Toggle operation complete.");
});
action.validate = function(selection) {
return true; // Always valid
};
return action;
})();
Installation
- Save the code above as a file named
toggle-all-open.omnijs
. - Drag the file into OmniFocus on macOS or save it to iCloud for iOS sync.
- The plugin will appear under OmniFocus’s Plug-In preferences.
Shortcuts for macOS and iOS
To run the plugin via Shortcuts, use the following URL:
omnifocus:///omnijs-run?script=PlugIn.find%28%27com.codedaptive.toggle-all-open%27%29.actions%5B0%5D.perform%28%29%3B