/*{ "author": "Josh Hughes", "targets": ["omnifocus"], "type": "action", "identifier": "com.josh-hughes.omnifocusplugins.GenerateToggleTagPlugin", "image": "gearshape.2", "version": "1.0", "description": "Generates a plugin that contains Toggle Tag actions for every tag in your library", "label": "Generate Toggle Tag Plugin", "mediumLabel": "Generate Tag Plugin", "paletteLabel": "Generate Tag Plugin" }*/ (() => { const action = new PlugIn.Action(function () { const tagList = []; const buildTagStructure = (tagGroup = tags, prefix = '') => { if (tagGroup.length > 0) { for (const tag of tagGroup) { let fullPath = prefix + tag.name; tagList.push({ name: tag.name, fullPath: fullPath, id: tag.id.primaryKey, }); buildTagStructure(tag.tags, fullPath + ' : '); } } }; buildTagStructure(); if (tagList.length > 0) { let userSelectedTagKeys = []; if (document && document.windows && document.windows[0] && document.windows[0].selection && document.windows[0].selection.tags && document.windows[0].selection.tags.length > 0) { userSelectedTagKeys = document.windows[0].selection.tags.map(tag => tag.id.primaryKey); } let fieldIncludeTagsLabel = 'Include all tags by default'; if (userSelectedTagKeys.length > 0) { fieldIncludeTagsLabel = 'Include selected tags by default' } const toggleTagForm = new Form(); const fieldPluginName = new Form.Field.String('pluginName', 'Name of Plugin', 'Toggle Tag'); const fieldRemoveSiblingTags = new Form.Field.Checkbox('removeSiblingTags', 'Remove sibling tags when adding a tag', false); const fieldIncludeTags = new Form.Field.Checkbox('includeTags', fieldIncludeTagsLabel, true); toggleTagForm.addField(fieldPluginName); toggleTagForm.addField(fieldRemoveSiblingTags); toggleTagForm.addField(fieldIncludeTags); const toggleTagFormPromise = toggleTagForm.show('Choose a Plugin Name and Options', 'Select Tags'); toggleTagFormPromise.then((form) => { const toggleTagForm2 = new Form(), tagIndexes = [], tagLabels = [], tagSelected = []; for (let i = 0; i < tagList.length; i++) { tagIndexes.push(i); tagLabels.push(tagList[i].fullPath); if (form.values.includeTags && (userSelectedTagKeys.length == 0 || new Set(userSelectedTagKeys).has(tagList[i].id))) { tagSelected.push(i); } } const tagListField = new Form.Field.MultipleOptions('tagList', 'Tags to Include', tagIndexes, tagLabels, tagSelected); toggleTagForm2.addField(tagListField); const toggleTagForm2Promise = toggleTagForm2.show('Finalize Selection', 'Generate Plugin'); toggleTagForm2Promise.then((form2) => { const removeSiblingTags = form.values.removeSiblingTags ? 'true' : 'false'; if (form.values.pluginName.trim() == '') { form.values.pluginName = 'Toggle Tag'; } const pluginName = form.values.pluginName; const pluginIdentifier = form.values.pluginName.replace(/\s/g, ''); const selectedTagList = []; for (let i = 0; i < form2.values.tagList.length; i++) { selectedTagList.push(tagList[form2.values.tagList[i]]); } if (selectedTagList.length > 0) { const manifestDotStringsData = Data.fromString(`"com.josh-hughes.omnifocusplugins.` + pluginIdentifier + `" = "` + pluginName + `";`); const toggleTagLibraryDotJsData = Data.fromString(`(() => { const ToggleTagLibrary = new PlugIn.Library(new Version('1.0')); ToggleTagLibrary.tagableItems = (objects) => { return objects.filter(item => (item instanceof Task || item instanceof Project)); } ToggleTagLibrary.allSelectedItemsHaveTag = (selection, selectedTag) => { return selection.every(item => (item.tags && item.tags.includes(selectedTag))); } ToggleTagLibrary.findTagAndSiblings = (options, tagGroup) => { if (options && options.tagId) { if (typeof tagGroup === 'undefined') { tagGroup = tags; } if (tagGroup.length > 0) { for (testedTag of tagGroup) { if (testedTag.id && testedTag.id.primaryKey && testedTag.id.primaryKey === options.tagId) { return { 'selectedTag' : testedTag, 'siblingTags' : tagGroup.filter(tag => tag !== testedTag) }; } else { let childTest = ToggleTagLibrary.findTagAndSiblings(options, testedTag.tags); if (childTest) { return childTest; } } }; } } return false; } ToggleTagLibrary.toggleTag = (selection, options) => { const items = ToggleTagLibrary.tagableItems(selection.allObjects), tagAndSiblings = ToggleTagLibrary.findTagAndSiblings(options); if (items[0] && tagAndSiblings && tagAndSiblings.selectedTag) { if (ToggleTagLibrary.allSelectedItemsHaveTag(items, tagAndSiblings.selectedTag)) { items.forEach(item => item.removeTag(tagAndSiblings.selectedTag)); } else { items.forEach(item => { if (options.removeSiblingTags) { let newTags = []; let foundSiblingTag = false; if (item.tags) { newTags = item.tags.map(tag => { if (tagAndSiblings.siblingTags.includes(tag)) { // If we find an existing sibling tag, and haven't added the new tag yet, push that instead if (!foundSiblingTag) { foundSiblingTag = true; return tagAndSiblings.selectedTag; } } else { return tag; } }); } // If there we're no siblings, add the tag to the end if (!foundSiblingTag) { newTags.push(tagAndSiblings.selectedTag); } if (!item.tags.includes(tagAndSiblings.selectedTag) || foundSiblingTag) { item.clearTags(); item.addTags(newTags); } } else { if (!item.tags.includes(tagAndSiblings.selectedTag)) { item.addTag(tagAndSiblings.selectedTag); } } }); } } } ToggleTagLibrary.validateTag = (selection, options, sender) => { const items = ToggleTagLibrary.tagableItems(selection.allObjects), tagAndSiblings = ToggleTagLibrary.findTagAndSiblings(options); if (tagAndSiblings && tagAndSiblings.selectedTag && items[0]) { if (typeof sender !== 'undefined') { sender.checked = ToggleTagLibrary.allSelectedItemsHaveTag(items, tagAndSiblings.selectedTag); } return true; } if (!tagAndSiblings.selectedTag && typeof sender !== 'undefined') { if (sender.label && sender.label.slice(-12) !== ' (Not Found)') { sender.label += ' (Not Found)'; } } return false; }; return ToggleTagLibrary; })();`); let manifestDotStrings = FileWrapper.withContents('manifest.strings', manifestDotStringsData), toggleTagLibraryDotJs = FileWrapper.withContents('ToggleTagLibrary.js', toggleTagLibraryDotJsData), manifestDotJsonActions = [], resourcesFiles = [manifestDotStrings, toggleTagLibraryDotJs], enDotLprojFiles = []; let i = 1; selectedTagList.forEach((listedTag) => { let tagData = Data.fromString(`(() => { const options = { 'tagId' : '` + listedTag.id + `', // ` + listedTag.fullPath + ` 'removeSiblingTags' : ` + removeSiblingTags + ` } const action = new PlugIn.Action(function (selection) { this.ToggleTagLibrary.toggleTag(selection, options); }); action.validate = function (selection, sender) { return this.ToggleTagLibrary.validateTag(selection, options, sender); } return action; })();`); let tagDataStrings = Data.fromString(`"label" = "Tag: ` + listedTag.fullPath + `"; "shortLabel" = "Tag: ` +listedTag.name + `"; "mediumLabel" = "Tag: ` + listedTag.fullPath + `"; "longLabel" = "Tag: ` + listedTag.fullPath + `";`); manifestDotJsonActions.push(` { "identifier": "toggleTag_` + listedTag.id.replaceAll('-', '_dash_') + `", "image": "tag" },`); resourcesFiles.push(FileWrapper.withContents('toggleTag_' + listedTag.id.replaceAll('-', '_dash_') + '.js', tagData)); enDotLprojFiles.push(FileWrapper.withContents('toggleTag_' + listedTag.id.replaceAll('-', '_dash_') + '.strings', tagDataStrings)); i++; }); const manifestDotJsonData = Data.fromString(`{ "author": "Josh Hughes", "identifier": "com.josh-hughes.omnifocusplugins.` + pluginIdentifier + `", "version": "1.0", "description": "Toggle selected tags", "actions": [ ` + manifestDotJsonActions.join("\n") + ` ], "libraries": [ { "identifier": "ToggleTagLibrary" } ], "defaultLocale": "en" }`); const manifestDotJson = FileWrapper.withContents('manifest.json', manifestDotJsonData); const enDotLproj = FileWrapper.withChildren('en.lproj', enDotLprojFiles); resourcesFiles.push(enDotLproj); const resources = FileWrapper.withChildren('Resources', resourcesFiles); const plugin = FileWrapper.withChildren(form.values.pluginName.replace(/\s/g, '') + '.omnifocusjs', [manifestDotJson, resources]); const save = new FileSaver(); save.show(plugin); } else { const noTagAlert = new Alert('Error', 'Sorry, you must select at least one tag to generate a plugin.'); noTagAlert.show(); } }); }); } }); action.validate = function () { return tags.length > 0; }; return action; })();