Default folder location for Prompt for File action?

Update - looks like there was an answer here:

(Looks workable but perhaps just a little (Rube Goldberg | Heath Robinson) – I'll script it :slight_smile:

I had the same problem, found the same solution and have been using it without any problems for a while now.

The only catch is how many returns you need. Which depends on whether you are Saving As an unnamed file (one return) or an already named one (two returns).

1 Like

In the meanwhile, pasting from JavaScript for Automation functions:

(() => {
    'use strict';

    // just :: a -> Just a
    const just = x => ({
        nothing: false,
        just: x
    });

    // nothing :: () -> Nothing
    const nothing = (optionalMsg) => ({
        nothing: true,
        msg: optionalMsg
    });

    // Split a filename into directory and file. combine is the inverse.
    // splitFileName :: FilePath -> (String, String)
    const splitFileName = strPath =>
        strPath !== '' ? (
            strPath[strPath.length - 1] !== '/' ? (() => {
                const
                    xs = strPath.split('/'),
                    stem = xs.slice(0, -1);
                return stem.length > 0 ? (
                    [stem.join('/') + '/', xs.slice(-1)[0]]
                ) : ['./', xs.slice(-1)[0]];
            })() : [strPath, '']
        ) : ['./', ''];

    // doesDirectoryExist :: FilePath -> IO Bool
    const doesDirectoryExist = strPath => {
        const ref = Ref();
        return $.NSFileManager.defaultManager
            .fileExistsAtPathIsDirectory(
                $(strPath)
                .stringByStandardizingPath, ref
            ) && ref[0] === 1;
    };

    // show :: Int -> a -> Indented String
    // show :: a -> String
    const show = (...x) =>
        JSON.stringify.apply(
            null, x.length > 1 ? [x[1], null, x[0]] : x
        );

    // CONFIRMING A SAVE PATH ------------------------------------------------

    // confirmSavePathMay :: FilePath -> Maybe FilePath
    const confirmSavePathMay = fp => {
        const
            se = Application('System Events'),
            sa = (se.includeStandardAdditions = true, se),
            [fldr, fname] = splitFileName(fp),
            fpFolder = doesDirectoryExist(fldr) ? (
                fldr
            ) : '~';
        return (() => {
            sa.activate();
            try {
                return just(
                    sa.chooseFileName({
                        withPrompt: "Save As:",
                        defaultName: fname,
                        defaultLocation: Path(
                            ObjC.unwrap($(fpFolder)
                                .stringByExpandingTildeInPath)
                        )
                    })
                    .toString()
                );
            } catch (e) {
                return nothing(e.message)
            }
        })();
    };

    return show(
        confirmSavePathMay('~/Notes/someFile.txt')
    );
})();

or the equivalent in AppleScript:

use framework "Foundation"
use scripting additions

-- CONFIRMING A SAVE PATH

-- confirmSavePathMay :: FilePath -> Maybe FilePath
on confirmSavePathMay(fp)
    set {fldr, fname} to splitFileName(fp)
    if doesDirectoryExist(fldr) then
        set strFolder to fldr
    else
        set strFolder to "~"
    end if
    try
        set ca to current application
        tell application "System Events"
            activate
            set choice to choose file name with prompt ¬
                "Save As:" default name fname ¬
                default location (((ca's NSString's stringWithString:strFolder)'s ¬
                stringByStandardizingPath) as string)
        end tell
        just(POSIX path of choice)
    on error strError
        nothing(strError)
    end try
end confirmSavePathMay


-- TEST ------------------------------------------------------------------
on run
    confirmSavePathMay("~/Notes/someFile.txt")
end run


-- GENERIC FUNCTIONS ------------------------------------------------------

-- doesDirectoryExist :: FilePath -> IO Bool
on doesDirectoryExist(strPath)
    set ca to current application
    set oPath to (ca's NSString's stringWithString:strPath)'s ¬
        stringByStandardizingPath
    set {bln, int} to (ca's NSFileManager's defaultManager's ¬
        fileExistsAtPath:oPath isDirectory:(reference))
    bln and (int = 1)
end doesDirectoryExist

-- initDef :: [a] -> [a]
-- initDef :: [String] -> [String]
on initDef(xs)
    set blnString to class of xs = string
    set lng to length of xs
    
    if lng > 1 then
        if blnString then
            text 1 thru -2 of xs
        else
            items 1 thru -2 of xs
        end if
    else if lng > 0 then
        if blnString then
            ""
        else
            {}
        end if
    else
        missing value
    end if
end initDef

-- intercalate :: String -> [String] -> String
on intercalate(s, xs)
    set {dlm, my text item delimiters} to {my text item delimiters, s}
    set str to xs as text
    set my text item delimiters to dlm
    return str
end intercalate

-- just :: a -> Just a
on just(x)
    {nothing:false, just:x}
end just

-- lastDef :: [a] -> a
on lastDef(xs)
    item -1 of xs
end lastDef

-- nothing :: () -> Nothing
on nothing(msg)
    {nothing:true, msg:msg}
end nothing

-- Split a filename into directory and file. combine is the inverse.
-- splitFileName :: FilePath -> (String, String)
on splitFileName(strPath)
    if strPath ≠ "" then
        if last character of strPath ≠ "/" then
            set xs to splitOn("/", strPath)
            set stem to initDef(xs)
            if stem ≠ {} then
                {intercalate("/", stem) & "/", lastDef(xs)}
            else
                {"./", lastDef(xs)}
            end if
        else
            {strPath, ""}
        end if
    else
        {"./", ""}
    end if
end splitFileName

-- splitOn :: String -> String -> [String]
on splitOn(strDelim, strMain)
    set {dlm, my text item delimiters} to {my text item delimiters, strDelim}
    set xs to text items of strMain
    set my text item delimiters to dlm
    return xs
end splitOn


I don't think that technique will work with the KM Prompt for File Action. When the choose file dialog opens, the macro flow is still at the Prompt for File Action. So, you can't just add the keyboard commands in following Actions, as is shown in the linked example.

@peternlewis, could you please add an option to the Prompt for File Action to provide the default folder.

And, in fact, I don't use the KM Prompt for File Action with the Command-Shift-G approach. My macro (which just types that keystroke and evaluates the trigger for how many returns to use) is a function called by various other macros that are triggered in text editors or in the Finder.

I'm not sure I understand your response.
If you want to provide the user with a prompt for file, how do you do it?

I don't see any way to use the KM Action "Prompt for File" and provide for a default folder. However, this can be easily done in AppleScript and JXA.

It is on the todo list.

3 Likes

I don't. In my particular case, I use a palette with a number of text templates, some of which have default names. When I select one it opens it and goes to the default folder for that particular month.

There's also an option to just Save As to the default folder, which I can use in any application (say, Photoshop) to get to this particular default folder.

Can you also add to your to do list a similar Safari-specific prompt for file action that would follow on after a Flash or other type of "Select File" button was clicked, allowing a previously iterated token to be concatenated into the directory path with ".jpg" or ".png" added on (for example) to complete the exact file at the exact path expression?

That doesn't sound like something Keyboard Maestro could do.

You could trigger a macro after clicking the flash button and then Keyboard Maestro could adjust the location of the current selected folder.

Actually, I DO have KM clicking on a flash button. I have it click on it as a picture, so it uses a screen shot I made of the button and it clicks on the center of it (with about 15% fuzziness built-in). It works rock-solid. The only caveat is that you must make the screen shot within the browser you use in KM (Safari), because different browsers render the same button slightly different. And of course you need the normal Safari pauses.

I have it click on the Browse button, and I have it click on the Upload button (which are visually different).

So all I need is to be able to get that final step where it will select the particular file I want from the particular directory, and I can finally fully automate this part of my process.

But of course this is my runaround for truly addressing the button programmatically.

Sorry, to be clear, Keyboard Maestro can click on any button in any number of ways.

Keyboard Maestro cannot be triggered by clicking on a flash button (or any button generally unless it can run AppleScript, or unless it is a Keyboard Maestro button (say in a Custom HTML Prompt for example)).

I trust that is clear.

Hey all...

Did this ever get addressed somehow? 'Cause it is now biting me in the butt and I see no way to do it.

Peter, any chance this feature will be available any time soon?

It is not on the schedule for 9.0

Hey Scott,

@ComplexPoint shows how above, but here's a simplified version:

Choose File with Default Destination.kmmacros (4.8 KB)

-Chris

1 Like

Thanks Chris...

ended up with something very similar:

tell application (path to frontmost application as text) to set thePath to choose file with prompt "Please choose a file:" of type {"txt"} default location "/Users/ss/Documents/Handy Stuff/Snippets/"

1 Like

Yes. Since KM does not provide this, or other options readily available by script, I have created the following macro/script:

Macro/Script: Choose File Given Optional Parameters

I have added an option to set the default directory for the Prompt for File action for the next version.

6 Likes