Safari menu item with changing variable

I use to use the Sessions extension in Safari but it no longer works in version 12. Next best thing I can seem to come up with is the Safari menu item Bookmarks > Add Bookmarks for these X tabs..."

However, macOS's own keyboard shortcut creator won't work to make a shortcut because the X in that menu item is a variable that changes with each window to reflect the number of tabs in that window. Attempting to try Keyboard Maestro to make one but the Menu item is defaulting to the text it shows in a one tab window of "Add Bookmarks for These Tabs..." so I need to add some kind of variable placeholder there to use whatever that number is for the current window.

use application "System Events"

property process : a reference to process "Safari"

set _M to a reference to (menu items ¬
	of menus ¬
	of menu bar items ¬
	of menu bars ¬
	of my process ¬
	whose name starts with ¬
	"Add Bookmarks For These")

if _M exists then set [M] to _M
click M
1 Like

The Select or Show a Menu Item action can take regular expression which allow for such changeable menus.

1 Like

Hey this is PERFECT!! Thank you! I added this script in to what I was already working on thinking I needed more steps included but discovered what you pasted here was ALL I needed. The ONLY thing I changed was switched "display results in a window" to "display results briefly" which created an macOS notification pop up then ultimately changed that again to "ignore results" which has the script still working but without the added notification pop.

1 Like

Just had an additional thought on this. Would it be possible to add a line to the script that would select a particular folder in the bookmarks? For instance, any time I would ever use this script I would want them to be bookmarked in the same top level folder and when I enter the name in and hit add it will make a new folder there. But I can see that at some point I might add a single bookmark in a different folder and then that folder would be come the last location that would be the default the next time if I used the script.

Hey Clif,

This sort of thing is possible if not very pretty.

----------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2019/01/04 15:28
# dMod: 2019/01/04 15:28 
# Appl: Safari, System Events
# Task: Save Tabs of front Window to a Specific Bookmarks Folder.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @Safari, @System_Events, @Save, @Tabs, @Front, @Window, @Specific, @Bookmarks, @Folder
----------------------------------------------------------------

tell application "System Events"
   
   tell application process "Safari"
      tell menu bar 1
         tell menu bar item "Bookmarks"
            tell menu 1
               tell (first menu item whose name contains "Add Bookmarks for")
                  perform action "AXPress"
               end tell
            end tell
         end tell
      end tell
      
      tell (first window whose subrole is "AXStandardWindow")
         tell pop up button 1 of sheet 1
            perform action "AXShowMenu"
            
            delay 0.1
            
            tell its menu 1
               tell menu item "Pend"
                  perform action "AXPress"
               end tell
            end tell
            
         end tell
      end tell
      
   end tell
   
end tell

----------------------------------------------------------------

Google Chrome makes it easier to script bookmarks. (Bad Apple!)

-Chris

2 Likes

Thanks Chris! Since I'm still a bit of a newb, do I replace Pend with the name of the folder in Bookmarks I want it to use?

Hey Clif,

That depends upon where the menu you want to save to is in your folder hierarchy.

For me my “Pend” folder is at the top level of my Favorites.

If your folder is deeper in the hierarchy you'll need to show me an example for me to guide you more exactly.

-Chris

Thanks Chris, did a bit of trial and error and got it working perfectly. When I changed the folder name of Pend I thought I might have to change "menu 1" to its number in bookmark hierarchy which of course was incorrect, leaving that 1 and just changing Pend to what I'm calling "Sessions" in honor of the fallen Safari extension it worked like a charm. Understandably that if my Sessions folder was inside another folder then hierarchy would need to be addressed but like you mine is top level.

THANKS for assist!!

1 Like

@CJK, can you explain this script, as it contains a couple of things I'm not sure I understand.

Specifically:

  1. Why use a reference to the menu item instead of just directly targeting it like you do in the script you posted later?

  2. What's up with the if _M exists then set [M] to _M line? Watching the values of _M and M in Script Debugger, I can see that _M displays the raw object specifier in AEPrint format while M shows the object and its properties, etc. But I'll be damned if I get why this is or what the significance is for the script.

Thanks!

I only posted the one script. The second script came from @ccstone. While I can't speak for him, I think the easiest way to account for the differences between my script and his is simply to say that we have different scripting styles. I don't see one as being better or worse than the other here.

I declare the variable _M by reference so that it doesn't get evaluated at the time of its declaration. When you declare by reference, it allows you do perform tests and actions on it that "de-reference" and evaluates the contents of the variable when you are ready to do so.

Why does that matter here ? If I were to declare _M by value, and give it the object reference menu items of menus...of my process whose name starts with..., if any of those children in the hierarchy don't exist, the script will terminate with an error. That's also one reason I chose to declare the individual children in their plural form, instead of ...of menu 1 of...menu bar item "Bookmarks" of menu bar 1..., because the collective object reference (e.g. menus) will always return a value, even if it's an empty list, whereas menu 1 either exists or it doesn't, the latter being error-creating.

So, in fact, when the script goes on to check the existence of _M, it's really only confirming that the process "Safari" is running, and that the entire collection contains at least one physical object in its collection, and isn't just an empty list, {}, or nested sets of empty lists, e.g. {{{}, {}}, {}, {}, {{}, {}}}.

If I had used specific children in their singular forms, (e.g. menu 1, menu bar item "Bookmarks", etc., which is completely fine to do, and arguably more sensible in some ways), then checking for its existence and getting a true result would confirm to you that every one of those children definitively exists.

Yes, you wouldn't be the first to remark on my unusual coding style.

The first part of the line is testing for existence, as I described above. The second part is less transparent. This:

set [M] to _M

is basically shorthand for:

set M to item 1 of _M

WHY? Because of how AppleScript allows you to co-assign variables by assigning lists of values to lists of variables:

set [a, b, c] to [1, 2, 3]

Here, a, b, and c are assigned the respective values of 1, 2, and 3. If you try and do a similar expression where there are an excess of variables and not enough values to fill them, AppleScript throws an error:

set [a, b, c] to [1, 2] --> error "Can’t get item 3 of {1, 2}." number -1728

But... if you have an excess of values and not enough variables, then AppleScript fills the variables in order with the respective values until it runs out of variables. Then the remaining, unused values are discarded:

set [a, b] to [1, 2, 3]

Here, a = 1, b = 2, and 3 is lost to the wind. So, hopefully, you can see why set [M] to _M is equivalent to set M to item 1 of _M.

What's less obvious is how on earth it picked out item 1 to be the object that appears to materialise deep inside a set of nested lists, surrounded above, below, left and right by a load of empties...

That's the other reason to declare _M by reference. Since the variable hasn't been evaluated before accessing its items, i.e. is still in a fully referenced state, it only typically needs to evaluate a collection as minimally deep as necessary to obtain the requested objects, and it ignores values that don't exist (which later materialise in the form of an empty list when the variable is dereferenced).

Once the variable is dereferenced, then you lose these features as they evaluate to physical AppleScript objects in a list of lists, where empty values are replaced with empty lists (a physical data object). This would also be true had I declared _M by value.

You can demonstrate the effect of dereferencing on the way list items are accessed by comparing the current return value for M with the one that gets returned if we do this instead:

if _M exists then set [M] to (_M as list)

_M as list is a coercion that forces the variable to evaluate and makes a nested list of hell. Then when we try and access the first element, all you get back is another nested list of hell that is one level shallower than before. It turns out that, in this case, the location of the menu item we're after inside the dereferenced list is given by:

set M to item 1 of item 1 of item 7 of item 1 of (_M as list)

I hope this helps bring some clarity to things. Feel free to ask if have any more queries.

1 Like

Oops, you're right. Sorry, between @CJK, @ccstone and @commanderclif, this thread has too darn many c-names! :wink:

Your explanation makes perfect sense (after reading it a couple of times). I think what was tripping me up was that since _M was being displayed in Script Debugger as a raw object specifier I wasn't thinking of it as a list so I couldn't see how set [M] to _M was the equivalent of set M to item 1 of _M. But I get it now.

Thanks for the detailed explanation. I'm always glad to learn more tricks for writing AppleScript.

Hey @roosterboy,

If you have Pretty-Print set ON in Script Debugger it's easier to visualize lists and records.

-Chris