Change the welcome text

Hi @complexpoint
Thanks a lot but get this

in my ~/Documents/Template/Taskpaper/" folder I have 2 files with extension .taskpaper
Something goes wrong
Both running from Keyboard maestro or Script editor (using Javascript) same issue

Thanks
Patrick

Forgat to add in the Script editors got this
ā€œNo ā€˜com.hogbaysoftware.taskpaperā€™ templates\n\nfound in:\n\t/Users/XXX/Documents/Template/Taskpaperā€

The first thing to try might be to edit the line (near the top of the script)

const templateUTI = "com.hogbaysoftware.taskpaper";

changing it to something like:

const templateUTI = "public.text";

and then tell us what happens.

Exactly the same as the message change a bit

does the folder must contain more than .taskpaper file into right?
thanks

with:

const fpTemplateFolder = "~/Documents/Template/Taskpaper/";

const templateUTI = "public.text";

const title = "TaskPaper Templates";

Thanks.

Next test (seems to work here):

const templateUTI = "com.hogbaysoftware.taskpaper";

same issue as before
Should I grant Script editor to access the file system?

OK, give me 20 mins. Iā€™ll look closer.

Something seems to be behaving differently between Mojave and Big Sur, and I have obviously not yet thought deeply enough about it :slight_smile:

Merci
Take your time Iā€™m back in 3 hā€¦

1 Like

Iā€™ve updated to 2.4 here and above:

Matching now on the file extension rather than the UTI:

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    // 1. Create a folder containing two or more
    //    model TaskPaper documents
    // 2. Edit the value of `fpTemplateFolder` at the top
    //    of this script to match the path the folder.
    // 3. Run this script
    //    (e.g. from a Keyboard Maestro or Fastscripts shortcut)

    ObjC.import("AppKit");

    // TaskPaper 3 ā€“ new file from menu of templates.
    // Ver 2.8

    // Rob Trew @2021

    const fpTemplateFolder = "~/TaskPaper Templates";

    const templateExtension = ".taskpaper";

    const title = "TaskPaper Templates";

    // main :: IO ()
    const main = () => {
        const
            fpFolder = filePath(fpTemplateFolder),
            extension = templateExtension
            .startsWith(".") ? (
                templateExtension
            ) : `.${templateExtension}`;

        return either(
            msg => alert(title)(msg)
        )(
            chosenTemplateName => chosenTemplateName
        )(
            bindLR(
                doesDirectoryExist(fpFolder) ? (
                    Right(fpFolder)
                ) : Left(`Folder not found: ${fpFolder}`)
            )(folderPath => {
                const
                    templateNames = folderTemplates(
                        fpFolder
                    )(
                        extension
                    )
                    .sort(),
                    intFiles = templateNames.length;

                return bindLR(
                    0 < intFiles ? (
                        1 < intFiles ? (
                            showMenuLR(false)(title)(
                                "Choose a template:"
                            )(templateNames[0])(
                                templateNames
                            )
                        ) : Right([templateNames[0]])
                    ) : Left(
                        `No '${extension}' templates` + (
                            `\n\nfound in:\n\t${fpFolder}`
                        )
                    )
                )(
                    chosenTemplateOpened(folderPath)(
                        extension
                    )
                );
            })
        );
    };


    // folderTemplates :: FilePath -> String -> [String]
    const folderTemplates = fpFolder =>
        extension => listDirectory(fpFolder)
        .flatMap(fileName => {
            const [base, ext] = Array.from(
                splitExtension(fileName)
            );

            return extension !== ext ? (
                []
            ) : Boolean(base) ? (
                [base]
            ) : [];
        });


    // chosenTemplateOpened :: FilePath -> [FileName]
    // -> IO FilePath
    const chosenTemplateOpened = fpFolder =>
        extension => choices => {
            const
                taskpaper = Application("TaskPaper"),
                fpTemplate = filePath(combine(fpFolder)(
                    `${choices[0]}${extension}`
                ));

            return bindLR(
                readFileLR(fpTemplate)
            )(
                txt => {
                    // showLog("Template path:", fpTemplate);
                    // showLog("Template content:", txt);

                    const newDoc = taskpaper.Document({
                        textContents: txt
                    });

                    // In TaskPaper,
                    return (
                        taskpaper.activate(),
                        taskpaper.documents.push(newDoc),
                        // newDoc.textContents = txt,
                        // newDoc.evaluate({
                        //     script: `${TaskPaperContext}`,
                        //     withOptions: {
                        //         textContents: txt
                        //     }
                        // }),
                        // in the JS interpreter.
                        Right(fpTemplate)
                    );
                }
            );
        };


    // ---------------- TASKPAPER CONTEXT ----------------
    // const TaskPaperContext = (editor, options) =>
    //     editor.outline.reloadSerialization(
    //         options.textContents
    //     );


    // ----------------------- JXA -----------------------

    // alert :: String => String -> IO String
    const alert = alertTitle =>
        s => {
            const
                sa = Object.assign(
                    Application("System Events"), {
                        includeStandardAdditions: true
                    });

            return (
                sa.activate(),
                sa.displayDialog(s, {
                    withTitle: alertTitle,
                    buttons: ["OK"],
                    defaultButton: "OK"
                }),
                s
            );
        };


    // showMenuLR :: Bool -> String -> String ->
    // [String] -> String -> Either String [String]
    const showMenuLR = blnMult =>
        // An optionally multi-choice menu, with
        // a given title and prompt string.
        // Listing the strings in xs, with
        // the string `selected` pre-selected
        // if found in xs.
        menuTitle => prompt => selected => xs =>
        0 < xs.length ? (() => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            sa.activate();

            const v = sa.chooseFromList(xs, {
                withTitle: menuTitle,
                withPrompt: prompt,
                defaultItems: xs.includes(selected) ? (
                    [selected]
                ) : [xs[0]],
                okButtonName: "OK",
                cancelButtonName: "Cancel",
                multipleSelectionsAllowed: blnMult,
                emptySelectionAllowed: false
            });

            return Array.isArray(v) ? (
                Right(v)
            ) : Left(`User cancelled ${title} menu.`);
        })() : Left(`${title}: No items to choose from.`);


    // --------------------- GENERIC ---------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });


    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });


    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a =>
        b => ({
            type: "Tuple",
            "0": a,
            "1": b,
            length: 2
        });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);


    // combine (</>) :: FilePath -> FilePath -> FilePath
    const combine = fp =>
        // Two paths combined with a path separator.
        // Just the second path if that starts with
        // a path separator.
        fp1 => Boolean(fp) && Boolean(fp1) ? (
            "/" === fp1.slice(0, 1) ? (
                fp1
            ) : "/" === fp.slice(-1) ? (
                fp + fp1
            ) : `${fp}/${fp1}`
        ) : fp + fp1;


    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);


    // doesDirectoryExist :: FilePath -> IO Bool
    const doesDirectoryExist = fp => {
        const ref = Ref();

        return $.NSFileManager.defaultManager
            .fileExistsAtPathIsDirectory(
                $(fp)
                .stringByStandardizingPath, ref
            ) && ref[0];
    };


    // filePath :: String -> FilePath
    const filePath = s =>
        // The given file path with any tilde expanded
        // to the full user directory path.
        ObjC.unwrap(ObjC.wrap(s)
            .stringByStandardizingPath);


    // listDirectory :: FilePath -> [FilePath]
    const listDirectory = fp =>
        ObjC.unwrap(
            $.NSFileManager.defaultManager
            .contentsOfDirectoryAtPathError(
                ObjC.wrap(fp)
                .stringByStandardizingPath,
                null
            ))
        .map(ObjC.unwrap);


    // readFileLR :: FilePath -> Either String IO String
    const readFileLR = fp => {
        // Either a message or the contents of any
        // text file at the given filepath.
        const
            e = $(),
            ns = $.NSString
            .stringWithContentsOfFileEncodingError(
                $(fp).stringByStandardizingPath,
                $.NSUTF8StringEncoding,
                e
            );

        return ns.isNil() ? (
            Left(ObjC.unwrap(e.localizedDescription))
        ) : Right(ObjC.unwrap(ns));
    };


    // splitExtension :: FilePath -> (String, String)
    const splitExtension = fp => {
        // A tuple of the basename and the extension,
        // in which the latter includes the "."
        const
            xs = fp.split("."),
            lng = xs.length;

        return 1 < lng ? (
            Tuple(
                xs.slice(0, -1).join(".")
            )(
                `.${xs[lng - 1]}`
            )
        ) : Tuple(fp)("");
    };

    // // showLog :: a -> IO ()
    // const showLog = (...args) =>
    //     // eslint-disable-next-line no-console
    //     console.log(
    //         args
    //         .map(JSON.stringify)
    //         .join(" -> ")
    //     );

    return main();
})();
1 Like

HI
Better Thanks a lot
now it propose the choice box with my template name into
when I click(choose) on one it open an empty taskpaper document
in the script editor the result is success with the full path and name of the template I have choose
looks like the template is not used to create the new doc

Thanks

Iā€™ve seen that with one file, but not others.

Are you able to share a zip of one or more templates with which you see that ? It would be helpful to track down the pattern.

UPDATE

Version 2.7 reverts to the original technique for reading template contents into a new document,

but requires an update to TaskPaper 3.8.16 or above.

You are really great!!!
The 2.7 works perfectly
I have already Taskpaper 3.8.16
Super!!!
I will need to pay you a coffee! :slight_smile:

1 Like

Hi complexpoint. If youā€™re not busy, would it be possible to update the other script that copies a template to the clipboard? I canā€™t seem to get that one working and have zero coding skillsā€¦

You will have to help me there : -)

Not quite sure what we are discussing ā€“ do you have a link, for example ?

Sorry I wasnā€™t clear. I was asking about this script being discussed in this thread - the post #13 by you from April 2016. It was about the version of this script that copies the chosen project template to a clipboard, for pasting on to an existing taskpaper document instead of making a new document.

You could test this in Script Editor (language selector at top left set to JavaScript)


Menu of Taskpaper files in given directory.

Contents of chosen file copied to clipboard:

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    const fpTemplatesFolder = "~/Taskpaper templates";

    // Menu of Taskpaper files in given directory.
    // Contents of chosen file copied to clipboard.

    // Rob Trew @2021
    // Ver 0.01

    ObjC.import("AppKit");

    // main :: IO ()
    const main = () =>
        either(
            msg => msg.startsWith("User cancelled") ? ("") : (
                alert("Copy Taskpaper template to clipboard"),
                msg
            )
        )(
            x => x
        )(
            bindLR(
                getDirectoryContentsLR(fpTemplatesFolder)
            )(
                xs => bindLR(
                    compose(
                        templateChoiceLR(fpTemplatesFolder),
                        sortOn(toLower),
                        filter(x => x.endsWith(".taskpaper"))
                    )(xs)
                )(
                    templateName => bindLR(
                        readFileLR(
                            combine(fpTemplatesFolder)(
                                templateName
                            )
                        )
                    )(
                        txt => 0 < txt.trim().length ? (
                            copyText(txt),
                            Right(`Copied contents of: ${templateName}`)
                        ) : Left(`Empty: ${templateName}`)
                    )
                )
            )
        );

    // templateChoice :: filePath ->
    // [FilePath] -> Either String String
    const templateChoiceLR = folderPath =>
        fileNames => 0 < fileNames.length ? (() => {
            const
                sa = Object.assign(
                    Application.currentApplication(), {
                        includeStandardAdditions: true
                    }),
                legend = `\n\n\t${folderPath}\n\n`,
                menu = fileNames.map(takeBaseName),
                choice = sa.chooseFromList(menu, {
                    withTitle: "Copy template contents to clipboard",
                    withPrompt: `Templates in folder:${legend}Choose:`,
                    defaultItems: menu[0],
                    okButtonName: "Copy file contents",
                    cancelButtonName: "Cancel",
                    multipleSelectionsAllowed: false,
                    emptySelectionAllowed: true
                });

            return Array.isArray(choice) ? (
                0 < choice.length ? (
                    Right(`${choice[0]}.taskpaper`)
                ) : Left("No template chosen.")
            ) : Left("User cancelled.");
        })() : Left(`No templates found in: ${folderPath}`);


    // ----------------------- JXA -----------------------

    // alert :: String => String -> IO String
    const alert = title =>
        s => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            return (
                sa.activate(),
                sa.displayDialog(s, {
                    withTitle: title,
                    buttons: ["OK"],
                    defaultButton: "OK"
                }),
                s
            );
        };


    // copyText :: String -> IO String
    const copyText = s => {
        const pb = $.NSPasteboard.generalPasteboard;

        return (
            pb.clearContents,
            pb.setStringForType(
                $(s),
                $.NSPasteboardTypeString
            ),
            s
        );
    };


    // getDirectoryContentsLR :: FilePath ->
    // Either String IO [FilePath]
    const getDirectoryContentsLR = fp => {
        const
            error = $(),
            xs = $.NSFileManager.defaultManager
            .contentsOfDirectoryAtPathError(
                $(fp).stringByStandardizingPath,
                error
            );

        return xs.isNil() ? (
            Left(ObjC.unwrap(error.localizedDescription))
        ) : Right(ObjC.deepUnwrap(xs));
    };


    // readFileLR :: FilePath -> Either String IO String
    const readFileLR = fp => {
        // Either a message or the contents of any
        // text file at the given filepath.
        const
            e = $(),
            ns = $.NSString
            .stringWithContentsOfFileEncodingError(
                $(fp).stringByStandardizingPath,
                $.NSUTF8StringEncoding,
                e
            );

        return ns.isNil() ? (
            Left(ObjC.unwrap(e.localizedDescription))
        ) : Right(ObjC.unwrap(ns));
    };

    // --------------------- GENERIC ---------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });


    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });


    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a =>
        b => ({
            type: "Tuple",
            "0": a,
            "1": b,
            length: 2,
            *[Symbol.iterator]() {
                for (const k in this) {
                    if (!isNaN(k)) {
                        yield this[k];
                    }
                }
            }
        });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);


    // combine (</>) :: FilePath -> FilePath -> FilePath
    const combine = fp =>
        // Two paths combined with a path separator.
        // Just the second path if that starts with
        // a path separator.
        fp1 => Boolean(fp) && Boolean(fp1) ? (
            "/" === fp1.slice(0, 1) ? (
                fp1
            ) : "/" === fp.slice(-1) ? (
                fp + fp1
            ) : `${fp}/${fp1}`
        ) : fp + fp1;


    // comparing :: (a -> b) -> (a -> a -> Ordering)
    const comparing = f =>
        x => y => {
            const
                a = f(x),
                b = f(y);

            return a < b ? -1 : (a > b ? 1 : 0);
        };


    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
        // A function defined by the right-to-left
        // composition of all the functions in fs.
        fs.reduce(
            (f, g) => x => f(g(x)),
            x => x
        );


    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);


    // filter :: (a -> Bool) -> [a] -> [a]
    const filter = p =>
        // The elements of xs which match
        // the predicate p.
        xs => [...xs].filter(p);


    // fst :: (a, b) -> a
    const fst = tpl =>
        // First member of a pair.
        tpl[0];


    // snd :: (a, b) -> b
    const snd = tpl =>
        // Second member of a pair.
        tpl[1];


    // sortOn :: Ord b => (a -> b) -> [a] -> [a]
    const sortOn = f =>
        // Equivalent to sortBy(comparing(f)), but with f(x)
        // evaluated only once for each x in xs.
        // ("Schwartzian" decorate-sort-undecorate).
        xs => xs.map(
            x => Tuple(f(x))(x)
        )
        .sort(uncurry(comparing(fst)))
        .map(snd);


    // takeBaseName :: FilePath -> String
    const takeBaseName = fp =>
        ("" !== fp) ? (
            ("/" !== fp[fp.length - 1]) ? (() => {
                const fn = fp.split("/").slice(-1)[0];

                return fn.includes(".") ? (
                    fn.split(".").slice(0, -1)
                    .join(".")
                ) : fn;
            })() : ""
        ) : "";


    // toLower :: String -> String
    const toLower = s =>
        // Lower-case version of string.
        s.toLocaleLowerCase();


    // uncurry :: (a -> b -> c) -> ((a, b) -> c)
    const uncurry = f =>
        // A function over a pair, derived
        // from a curried function.
        (...args) => {
            const [x, y] = Boolean(args.length % 2) ? (
                args[0]
            ) : args;

            return f(x)(y);
        };

    return main();
})();
1 Like

Works great! Thanks complex point!