yep, as everything in BTT is JSON in some way, it’s also possible to so this programmatically. I’ll post an example later today, need to leave for a bit now
Hi! Just following up about a programmatic example of Show/Choose From List. No rush though, as I understand if other things have priority! Thanks!
I'm currently working on it. While it would already be possible with current BTT versions, it would be a bit complex. I had long planned to add a common "simple" syntax to return basic items in menu style views . (e.g. for the context menu action, floating menus, this new choose from list action, menubar icons etc..)
This post finally pushed me to implement it. I hope to have it ready tomorrow.
I have not yet finalized the property names etc. but it will be a JSON similar to this:
[
{
"title": "",
"subtitle": "",
"iconBase64" : "",
"iconPath": "",
"iconSFSymbol": {
"name" :"",
"colors" : [],
"background": ""
},
"action": ""
}
]
It will also be possible to continue using the current, complex syntax for more complex/custom items.
Thanks for the update! I'll watch for it!
One quick question: Will it be possible to pass data to the action for each item? For my use case, I'm hoping to insert specific text depending on the item selected. The text itself could probably come from the title/subtitle, but it would be efficient to just reuse the same action for each item.
it will be possible. The action property can have various forms.
It can either be:
- an apple shortcut (with optional provided input)
- a UUID to trigger a configured action in BTT
- a named trigger configured in BTT.
Additionally every item can set a variable that can be accessed from the called action. I'll post some examples once it is done!
Sounds great! Thanks!
4.690 now contains a first version of this that works with the predefined actions "Custom Context Menu New" and "Choose From List".
Unfortunately I will need a few more hours to document this properly as it became quite powerful.
Here is what I started typing - it's probably not very useful yet - but I'm now on a day trip with the kids. Will finish the docs this evening.
You need to click the "Retrieve Content Dynamically Via Java Script" checkbox.
Then you can have a function like this that returns a JSON:
async function retrieveJSON() {
let items = [
{
"title": "some title",
"icon": "sfsymbol::star::color@@B53E93",
"action": `btt::paste_text@@{"text": "hallo welt"}`
}
];
return JSON.stringify(items);
}
Allowed keys:
title
supported properties color & size
Examples:
{
"title": "hello::size@@30::color@@#000000"
}
Alternative syntax:
{
"title": {
"text": "some title",
"size": 30,
"color": "#000000"
}
}
subtitle
supported properties color & size. Only works in the "choose from list action".
Examples:
{
"subtitle": "some subtitle::size@@30::color@@#000000"
}
Alternative syntax:
{
"subtitle": {
"text": "some subtitle",
"size": 30,
"color": "#000000"
}
}
subitems
Examples:
{
"title": "submenu",
"subitems": [
{
"title": "hello"
},
{
"title": "world"
}
]
}
setvariable
this allows to set a variable value before the action is executed
Examples:
{
"setvariable": "variablename@@variablevalue"
}
Alternative syntax:
{
"setvariable": {
"name": "variablename",
"variablevalue": "test"
}
}
icon
this allows to define an icon
Supported properties:
type (sfsymbol, path, base64)
Examples:
{
"icon": "sfsymbol::star"
}
Or with more options:
{
"icon": "sfsymbol::star::weight@@light::color@@#fefefe::color@@#000000::background@@#B53E93"
}
From file:
{
"icon": "path::~/Downloads/test.png"
}
From base64:
{
"icon": "base64::base64code"
}
Alternative syntax:
{
"icon": {
"type": "sfymbol",
"sfymbol": "star",
"weight": "light",
"colors" : ["#000000", "#fefefe"],
"background": "#B53E93",
"size" : 30
}
}
action
supported properties named, shortcut, js, keyboard, uuid, btt. Can also be an array of actions.
named
This will trigger a named trigger configured in BTT (see Reusable Named Triggers · GitBook )
{"title": "trigger named trigger", "action": "named::theNameOfTheNamedTrigger"},
Alternative syntax:
{
"title": "trigger named trigger alternative",
"action": {
"named": "helloworld"
},
"icon": "sfsymbol::star.leadinghalf.filled"
}
uuid
This will trigger any trigger configured in BTT using its UUID
{"title": "trigger via uuid", "action": "uuid::4ff033d2-8f67-490d-b281-3124d452ef07"},
Alternative syntax:
{
"title": "trigger via UUID alternative",
"action": {
"uuid": "4ff033d2-8f67-490d-b281-3124d452ef07"
}
}
shortcut
This will trigger a shortcut configured in Apple's shortcuts app
{"title": "trigger shortcut form shortcuts app", "action": "shortcut::theNameOfTheShortcut@@someOptionalInput"},
Alternative syntax:
{
"title": "trigger shortcut form shortcuts app alternative",
"action": {
"shortcut": "theNameOfTheShortcut",
"input": "some optional input"
}
}
js
This will run BTT Java Script which also allows to run shell scripts or apple scripts and trigger ny of BTT's scripting functions (Using Java Script (not JXA) · GitBook)
E.g. run some shell script:
{"title": "say hello", "action": "js::runShellScript({script: `say hello`})"},
Or run some arbitrary apple script:
{
title: "show some apple script dialog",
action: {
js: `(async () => {
// put the Apple Script into a string (back ticks are great for multi-line strings)
let appleScript = \`
set theDialogText to "The curent date and time is " & (current date) & "."
set result to display dialog theDialogText
return result
\`;
// this will execute the Apple Script and store the result in the result variable.
let result = await runAppleScript(appleScript);
// do whatever you want with the result
// at the end you always need to call returnToBTT to exit the script / return the value to BTT.
returnToBTT(result);
// it is important that this function self-executes ()
})()
`,
},
},
btt
This can run any of BTT's scripting functions (Using Apple Script or JXA · GitBook ). To see how to configure & trigger a specific action you can right-click the action in BTT and choose "copy java script to trigger action".
"action": `btt::paste_text@@{"text": "hello world"}`
Alternative syntax
{
"title": "paste item 1",
"action": {
"btt":"paste_text",
"args": {
"text": "ddasdas",
"insert_by_pasting": true
}
}
},
{
"title": "application expose",
"action": {
"btt": "trigger_action",
"args": {
"json": {
"BTTActionCategory": 0,
"BTTPredefinedActionType": 6,
"BTTPredefinedActionName": "Application Expose"
}
}
}
}
keyboard
This can trigger a keyboard shorcut
cmd, shift, opt, fn, ctrl and one key code at the end. A list of key codes can e.g. be found here: Complete list of AppleScript key codes
"action": "keyboard::shift,opt,0"
Examples
Get result from Apple Shortcut, e.g. some weather conditions:
async function retrieveJSON() {
let weather = await runAppleShortcut({name: "weather", "input": ""});
let items = [
{"title": weather},
{"title": "test item 2"},
{"title": "test item 3", "icon": "sfsymbol::star"}
];
return JSON.stringify(items);
}
Execute multiple actions from one item (example types abc)
async function retrieveJSON() {
let items = [
{
"title": "multiple actions (type abc)",
"action": [
"keyboard::0", //type a
"keyboard::11", //type b
"keyboard::8", //type c
],
"icon": "sfsymbol::keyboard"
},
];
return JSON.stringify(items);
}
Fetch menu from some server:
async function retrieveJSON() {
const response = await fetch('https://folivora.ai/various/test-menu.json');
const data = await response.json();
const jsonString = JSON.stringify(data);
return jsonString;
}
Works with custom menubar items as well when assigning the "Show Context Menu New" action:
Wow, this looks absolutely amazing!
I think it will work even better than what I had hoped for and will actually allow a few mor me things. I’ll play around and report back tomorrow. It gives me ideas for a few other tasks I’ve been thinking about too.
Thanks so much for working on this!
Enjoy your trip!
Fixed some crashes in 4.692 and moved some initial documentation here:
https://docs.folivora.ai/docs/1108_simple_format.html
In one of the next versions this format will also become available for floating menus.
Here is an example that populates such a list with all configured triggers that have a "Cheat Sheet Label" defined:
//see https://docs.folivora.ai/docs/1108_simple_format.html
async function retrieveJSON() {
let allTriggersJSONString = await get_triggers({
// maybe limit to specific category trigger_type: "BTTTriggerTypeKeyboardShortcut",
});
let allTriggersJSONArray = JSON.parse(allTriggersJSONString);
let items = [];
for (let trigger of allTriggersJSONArray) {
if (
trigger["BTTTriggerCheatSheetConfig"] &&
trigger["BTTTriggerCheatSheetConfig"]["BTTCheatSheetTriggerLabel"]
) {
items.push({
title:
trigger["BTTTriggerCheatSheetConfig"]["BTTCheatSheetTriggerLabel"],
action: `uuid::${trigger["BTTUUID"]}`,
});
}
}
return JSON.stringify(items);
}
Hey, I'm enjoying trying to use this to implement what I was thinking of. It's pretty intuitive over all. This is what I have so far:
I'm loading the references from a CSL-JSON file and then mapping them to BTT's JSON for the menu items.
I think I'm running into a few issues, however.
-
There seems to be some caching, at least when editing the JS file externally. I think it mostly updates fine if I edit the file in BTT itself, but I'm not sure.
-
I'm having trouble setting the icon color. The
Icon Background Color
option in the action config doesn't see to do anything, and I always end up with purple icons as in screenshot no matter what is selected. If I try to set each icon individually, it some times changes, but some times not? I'm also not sure if I can set the alpha programatically. Ultimately, I just want transparent icons that work with light/dark mode. -
I'm trying to set the action to insert text using the following code, but it seems like it just gets called immediately for every item, and then makes BTT hang when it completes (the function gets called to map each item... it's actually a lot more complex to set the title, but simplified here):
function mapItem(item) {
return {
id: item.id,
title: `${makeTitle(item)}`,
icon: "sfsymbol::book",
action: {
js: `(async () => {await paste_text({text: '@${item.id}'})})`
}
}
}
Thanks again for your help! I'm really impressed!
For the colors you can use either rgb (#RRGGBB) or rgba (#RRGGBBAA) hex, the only important part is that they are prefixed by a #. I plan to add more options to specify colors soon.
So for example with the "simple" syntax it would be:
"icon": "sfsymbol:square.and.arrow.up.trianglebadge.exclamationmark::color@@#DE3E93B5::color@@#ffffff::background@@#31C793"
To specify a clear background you can use #00000000
SFSymbols often support multiple colors, thus you can specify them one after each other.
For the paste for now I'd recommend the btt action approach instead of going through JS again - I'll check why that might block.
{
"title": "test",
"icon": "sfsymbol:square.and.arrow.up.trianglebadge.exclamationmark::color@@#DE3E93B5::color@@#ffffff::background@@#31C793",
"action": {
btt: "paste_text",
args: {
text: "hello world 2",
insert_by_pasting: true,
},
}
}
If you can share any crashlogs from the macOS console app, that would be helpful!
Thanks!
Both suggestions worked.
The programatic alpha problem actually was my fault: my brain though #RRGGBBFF should be transparent instead of #RRGGBB00.
And the action works as expected now.
Here it is now with a few additional adjustments:
Do you want me to post the log here or send them via email?
Best send via e-mail to andreas@folivora.ai! THank you!
Is there anyway to configure how the search matches items?
For example, I have an item with the title set to something like:
Croft, William. 2022. Morphosyntax: constructions of the world’s languages.
And I would like typing croft 2022
or croft morphosyntax
to match.
Is that possible?
currently not, at the moment it's just a simple "contains" check. This could definitely be improved.
Maybe as a first step I could split the search string into components and check whether the original string contains all of the components.
Thanks for the info!
Also, update on the JS version of the action: I just forgot to put ()
at the end to actually call the function. The following works now without any hanging or crashing.
action: {
js: `(async () => {await paste_text({text: '@${item.id}'})})()`
}
I think the hanging I saw previously must have been something else.
Ah good point, I think if the JS has some errors it might not return from some functions, causing BTT to hang. I'll check that and instead make it show some error in such cases!
That might work!