The @search
tag approach to saving searches has many advantages, particularly the sidebar listing of available perspectives.
It’s also easy to copy-paste @searches from one document to another (or use a script to gather and paste them)
There are, however, also various ways of keeping all named searches in one place, and using them in any document, without having to paste any @search
tags.
As an example, here is a pair of draft scripts
- searchMENU
- searchSAVE
searchMENUandSAVE.zip (11.3 KB)
which maintain and use a text file list of named searches, (by default, in the same folder as the scripts).
You could use them with launchers like FastScripts, Keyboard Maestro, etc, but if you simply install them in the TaskPaper Script folder, as described in:
http://guide.taskpaper.com/using_scripts.html
You will see something like:
and once you have used either of them, a text file will appear (with the icon of your default .txt file editor)
You can edit the text file directly by choosing it from the script menu, or test a new view in TaskPaper, and then save it directly, supplying a name for it, using searchSAVE.
searchMENU displays a menu like this:
and applies the chosen search to the current TaskPaper 3 document.
searchSAVE is just a shortcut to adding the currently active filter to the saved menu:
The initial contents of the automatically created text file look like this:
( If you edit it manually, just separate the name from the itemPath with any variant of ->, => or -->, leaving white space to left and right of your arrow)
( and no need to escape parentheses )
Source of searchMENU
// USE A SAVED MENU OF TASKPAPER 3 SEARCHES IN ANY DOCUMENT
// Displays a menu of saved searches for application to any document
// (@search tags not needed)
// Menu saved as a text file in the same folder as the script
// menu format (no backslashes needed for parentheses)
// name1 -> itemPath1
// name2 -> itemPath2
// etc.
// Once you have tested a new search in the TaskPaper 3 search panel,
// you can save it to the menu file with a sister script:
// (Other script: SAVE THE ACTIVE TASKPAPER 3 SEARCH (item path filter))
function run() {
'use strict';
// OPTIONS
var dctOptions = {
'menuFile': 'savedSearches.txt'
},
dctDefaultMenu = {
"completed": "@done",
"remaining": "(not @done)",
"projects": "@type=project",
"top level": "/*",
"two levels": "/*/*",
"three levels": "/*/*/*",
"soon": "@due <[d] now+14 days",
"overdue": "@due <[d] now"
}
// TASKPAPER CONTEXT
function TaskPaperContext(editor, options) {
editor.itemPathFilter = options.filter;
}
// JAVASCRIPT FOR AUTOMATION CONTEXT
// fileExists :: String -> Bool
function fileExists(strPath) {
var fm = $.NSFileManager.defaultManager,
error = $();
fm.attributesOfItemAtPathError(
ObjC.unwrap($(strPath)
.stringByStandardizingPath),
error
);
return error.code === undefined;
}
// readFile :: FilePath -> IO String
function readFile(strPath) {
return ObjC.unwrap(
$.NSString.stringWithContentsOfFileEncodingError(
strPath, $.NSUTF8StringEncoding, null
)
);
}
//writeFile :: FilePath -> String -> IO ()
function writeFile(strPath, strText) {
var ref = Ref();
$.NSString.alloc.initWithUTF8String(strText)
.writeToFileAtomicallyEncodingError(
strPath, false,
$.NSUTF8StringEncoding, ref
);
return ref;
}
// Menu text file: key '->' value lines (search name '->' search path)
// deserializeMenu :: String -> {key:value, }
function deserializeMenu(strLines) {
return strLines.split(/[\n\r]+/)
.reduce(function (a, x) {
var lstParts = x.split(/\s*[-=]+>\s*/);
if (lstParts.length > 1) {
var k = lstParts[0].trim(),
v = lstParts[1].trim();
if (k && v) a[
k.charAt(0)
.toUpperCase() + k.slice(1)
] = v;
}
return a;
}, {});
}
// Menu text file: key '->' value lines (search name '->' search path)
// serializeMenu :: {key:value, } -> String
function serializeMenu(dctMenu) {
var ks = Object.keys(dctMenu);
return (
ks.sort(),
ks.reduce(function (a, k) {
return a + k + ' => ' + dctMenu[k] + '\n';
}, '')
);
}
// MAIN
var tp3 = Application('com.hogbaysoftware.TaskPaper3'),
ds = tp3.documents,
d = ds.length ? ds[0] : undefined;
if (d) {
var a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a),
fm = $.NSFileManager.defaultManager,
strMenuFile = (dctOptions.menuFile || 'savedSearches.txt'),
strFolder = ObjC.unwrap(
$(sa.pathTo(this)
.toString())
.stringByDeletingLastPathComponent
);
// Find or create a TEXT FILE listing named searches
// format: name -> search
fm.changeCurrentDirectoryPath(
strFolder
);
if (!fileExists(strMenuFile)) {;
writeFile(
strFolder + '/' + strMenuFile,
serializeMenu(dctDefaultMenu)
);
}
// OFFER A MENU TO THE USER
var dctMenu = deserializeMenu(readFile(strMenuFile)),
lstMenu = Object.keys(dctMenu),
strDelim = '\t\t\t';
// format as two columns - name and itemPath
var lngChars = lstMenu.reduce(function (a, k) {
var lng = k.length;
return lng > a ? lng : a
}, 0),
lst = lstMenu.map(function (k) {
var strPad = Array(lngChars + 1)
.join(' ');
return (k + strPad)
.slice(0, lngChars) + strDelim + dctMenu[k];
});
// Use SystemUIServer as the dialog provider
// in case the script is running from /usr/bin/osascript
var su = Application("SystemUIServer"),
sua = (su.includeStandardAdditions = true, su),
docFile = d.file(),
strDocName = docFile ? (
ObjC.unwrap(
$(docFile.toString())
.stringByAbbreviatingWithTildeInPath
)
) : '(Untitled document ? not yet saved)';
sua.activate();
var mbChoice = sua.chooseFromList(lst, {
withTitle: 'Filter TaskPaper 3 document',
withPrompt: 'Apply search filter to:\n\n' +
strDocName + '\n\n' + 'Saved search:',
defaultItems: lst[0],
okButtonName: 'OK',
cancelButtonName: 'Cancel',
multipleSelectionsAllowed: false,
emptySelectionAllowed: true
});
if (mbChoice) {
tp3.activate();
var strChoice = (
mbChoice[0]
.split(strDelim)[0]
)
.trim(),
strFilter = dctMenu[strChoice];
d.evaluate({
script: TaskPaperContext.toString(),
withOptions: {
filter: strFilter
}
});
return sa.displayNotification(
'=> ' + strFilter, {
withTitle: 'Saved search applied',
subtitle: strChoice + ':',
soundName: 'default'
}
)
}
}
}
Source of searchSAVE
// SAVE THE ACTIVE TASKPAPER 3 SEARCH (item path filter)
// adding it to a text file which contains a menu of saved searches,
// and which is used by another script:
// (Other script: USE A SAVED MENU OF TASKPAPER 3 SEARCHES IN ANY DOCUMENT)
// Ver 0.01
function run() {
'use strict';
// OPTIONS
var dctOptions = {
// This file is saved, by default, in the same folder as the script
'menuFile': 'savedSearches.txt'
},
dctDefaultMenu = {
"completed": "@done",
"remaining": "(not @done)",
"projects": "@type=project",
"top level": "/*",
"two levels": "/*/*",
"three levels": "/*/*/*",
"soon": "@due <[d] now+14 days",
"overdue": "@due <[d] now"
}
// TASKPAPER CONTEXT
function TaskPaperContext(editor) {
// Read the active filter
return editor.itemPathFilter;
}
// JAVASCRIPT FOR AUTOMATION CONTEXT
// fileExists :: String -> Bool
function fileExists(strPath) {
var fm = $.NSFileManager.defaultManager,
error = $();
fm.attributesOfItemAtPathError(
ObjC.unwrap($(strPath)
.stringByStandardizingPath),
error
);
return error.code === undefined;
}
// readFile :: FilePath -> IO String
function readFile(strPath) {
return ObjC.unwrap(
$.NSString.stringWithContentsOfFileEncodingError(
strPath, $.NSUTF8StringEncoding, null
)
);
}
//writeFile :: FilePath -> String -> IO ()
function writeFile(strPath, strText) {
var ref = Ref();
$.NSString.alloc.initWithUTF8String(strText)
.writeToFileAtomicallyEncodingError(
strPath, true,
$.NSUTF8StringEncoding, ref
);
return ref;
}
// Menu text file: key '->' value lines (search name '->' search path)
// deserializeMenu :: String -> {key:value, }
function deserializeMenu(strLines) {
return strLines.split(/[\n\r]+/)
.reduce(function (a, x) {
var lstParts = x.split(/\s*[-=]+>\s*/);
if (lstParts.length > 1) {
var k = lstParts[0].trim(),
v = lstParts[1].trim();
if (k && v) a[
k.charAt(0)
.toUpperCase() + k.slice(1)
] = v;
}
return a;
}, {});
}
// Menu text file: key '->' value lines (search name '->' search path)
// serializeMenu :: {key:value, } -> String
function serializeMenu(dctMenu) {
var ks = Object.keys(dctMenu);
return (
ks.sort(),
ks.reduce(function (a, k) {
return a + k + ' => ' + dctMenu[k] + '\n';
}, '')
);
}
// MAIN
var tp3 = Application('com.hogbaysoftware.TaskPaper3'),
ds = tp3.documents,
d = ds.length ? ds[0] : undefined,
a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a);
// If a document is open in TaskPaper 3
if (d) {
// is there an active itemPath
var strItemPath = d.evaluate({
script: TaskPaperContext.toString()
})
if (strItemPath.length > 0) {
var fm = $.NSFileManager.defaultManager,
strMenuFile = (dctOptions.menuFile || 'savedSearches.txt'),
strFolder = ObjC.unwrap(
$(sa.pathTo(this)
.toString())
.stringByDeletingLastPathComponent
),
strMenuPath = strFolder + '/' + strMenuFile;
// Find or create a TEXT FILE listing named searches
// format: name -> search
fm.changeCurrentDirectoryPath(
strFolder
);
if (!fileExists(strMenuFile)) {
writeFile(
strMenuPath,
serializeMenu(dctDefaultMenu)
);
}
// OFFER A 'SAVE AS' DIALOG TO THE USER
var dctMenu = deserializeMenu(readFile(strMenuFile)),
// using SystemUIServer as the dialog provider
// in case the script is running from /usr/bin/osascript
su = Application("SystemUIServer"),
sua = (su.includeStandardAdditions = true, su);
sua.activate();
var dctResult = sua.displayDialog(
'SAVE: ' + strItemPath +
'\n\nadding it to menu file:\n\n' +
ObjC.unwrap(
$(strMenuPath)
.stringByAbbreviatingWithTildeInPath
) + '\n\nas:', {
defaultAnswer: 'name',
buttons: ['Cancel', 'Edit menu file', 'Save'],
defaultButton: 'Save',
cancelButton: 'Cancel',
withTitle: 'Save search',
withIcon: sua.pathToResource('TaskPaperAppIcon.icns', {
inBundle: 'Applications/TaskPaper.app'
}),
givingUpAfter: 45
});
var strName = dctResult.textReturned.trim(),
blnSave = false;
if (strName.length > 0) {
var strUName = strName.charAt(0)
.toUpperCase() + strName.slice(1);
if (dctResult.buttonReturned === 'Save' && dctMenu[strUName]) {
var dctConfirm = sua.displayDialog(
'Replace existing menu item:\n\n\t' + strUName +
'\n\nwith:\n\n\t' +
strItemPath, {
buttons: ['Cancel', 'OK'],
defaultButton: 'Cancel',
cancelButton: 'Cancel',
withTitle: 'Menu name exists',
withIcon: sua.pathToResource(
'TaskPaperAppIcon.icns', {
inBundle: 'Applications/TaskPaper.app'
}),
givingUpAfter: 45
})
if (dctConfirm.buttonReturned === 'OK') {
dctMenu[strUName] = strItemPath;
blnSave = true;
};
} else {
dctMenu[strUName] = strItemPath;
blnSave = (dctResult.buttonReturned === 'Save');
}
if (blnSave) {
writeFile(
strMenuPath,
serializeMenu(dctMenu)
);
sa.displayNotification(
'', {
withTitle: 'TaskPaper 3 search saved',
subtitle: strUName + ' -> ' + strItemPath,
soundName: 'default'
}
)
}
if (dctResult.buttonReturned === 'Edit menu file') {
var te = Application('TextEdit');
te.activate();
sa.doShellScript('open "' + strMenuPath + '"')
}
}
} else {
return sa.displayNotification(
'TaskPaper 3 saved searches', {
withTitle: 'No active search filter',
subtitle: '(nothing to save)',
soundName: 'default'
}
)
}
}
}