Remove Due date when done in the tag list

Hi All,
I’m using Taskpaper as my main PRO task manager and It work very well.
I work with plenty of tasks on multiple project and add the tag @due(YYYY-MM-DD) very often.

My question: does exist a way to remove from the tag list and sublist (mainly) the date when the task is marked done and archived? Keeping the task and tag intact to be able to retrieve when it was due in the achive project.
That will keep in the tag list only the “Active tag”
May be be it can be considered as a feature request ?
Thanks for your help here
Patrick

All of these things can be done by defining a script.

Not entirely sure that I follow the description, would you like to show us examples of the before and after texts ?

Show is usually easier than tell, for the readers
(though I appreciate that it can be the opposite for the writer : -)

e.g. perhaps

  • before script / keyboard event:

    • the line in the main file
  • after the script / keyboard event:

    • the updated line in the main file
    • an added line in the archive

thanks a lot for the answer here
May be a picture is better than lot of word :slight_smile:


If you have any script to share or idea on how is it possible thanks in advance
Patrick

OK, that’s part of the picture :slight_smile:

I think it would be helpful if we could see separate before and after views.

The archive part is presumably “after”, but I’m unsure about the test: project - are we looking at it in that screenshot before the script has run, or after ?

Could you show us both ?

Thanks for the quick answer
I create a project Test:
Create a task, tag with @due(2021-02-02), mark as done and run the "archive done " command
It create the archive project and move the task to
it keep the due date in the left tag panel.
The issue is: if I have plenty of tasks the due tag on the left become messy, with old subtag(date)
however empty the archived task will lost the date where I will need to refer to.

Any idea?
Thanks a lot
Patrick

Let me see if I have understood you:

  1. The task moves to the archive, and disappears entirely from the test project.
  2. The listing under @due in the left sidebar is pruned. (A date value is removed)

Is that more or less the picture ?

the point 2 is. what I would like to have when 1 is done
You can reproduce
create a project
add a task with due date
marks as @done
use the archive done command from the command palette

you will see a new project archive
but on the left side the due date tag for the event done is still there
I would like to remove it

Forgive me for being slow. 人老了.

I suppose one option would be to use a script which sends archived material into a separate archive.taskpaper file, rather than keeping it in an archive: project within the same file.

The tags configuration file allows us to add (but not, I think, subtract) the tags displayed in the sidebar.

[Using the Sidebar](https://www.taskpaper.com/guide/using-taskpaper/using-the-sidebar.html)

My understanding is that, as you suggest, any tags used in the active file will indeed show up in the tags section of the sidebar.

hi
No worry I’m happy to receive some advices :slight_smile:
It can be an option for sure and my scriptiong knowledge do not allow me to create it
In a other way it can be also an option in the preferences and a new feature.

If you can help to create the script I will really appreciate
Thanks
Patrick

There are various ways of writing “Clear archived tasks to archive file” as a script.

Would you want the contents of the Archived: project to be transferred to an

archive.taskpaper

file in the same folder ? Somewhere else ?

Would you prefer recently completed tasks to go to:

  • the top of the archive file ?
  • end end of the archive file ?

etc

If you have any preferences of this kind, then I can quickly sketch something.

Wouhaa a bedspoke service!
Thanks a lot
ideally
the content of Archive: in Archive.taskpaper in the same folder
Most recent on the top
Is it possible to remove the @done and replace by @archived(YYYY-MM-DD) where the date is the achiving date it would be easiest to find in case of need
I think is all my need
Thanks a lot

Thanks – I’ll try to sketch the core of it over the weekend.

An indicative sketch, no guarantees.

This rough draft (to be tested only with dummy data), simply aims to:

  1. copy any items in a project named Archive: in the front document, to the top of a file named archive.taskpaper in the same folder as the front document. (The archive file will be created if it doesn’t yet exist), and
  2. then remove those Archive: items from the front document.

Note that in the TaskPaper 3 main menu, the path:

TaskPaper > Tag > Archive @done items

automates the moving of any items with a @done tag to a project called Archive: which is created if it doesn’t yet exist.

Important: This draft script deletes items from the front TP3 document. Test this draft carefully on dummy data.

For the installation and use of scripts in TaskPaper 3, see:

[Using Scripts]( https://www.taskpaper.com/guide/using-taskpaper/using-scripts.html )

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

    // For testing only. Not considered ready for active use.

    // Rob Trew @2020

    // Rough draft 0.03
    // Simply issues a message and stops if the name of the
    // front document is that of the archive file.

    // Rough Draft of a script for archiving the content of
    // an Archive: project in the front TaskPaper document
    // to the top or bottom of an archive.taskpaper file
    // in the same folder.

    // Permission is hereby granted, free of charge, to any
    // person obtaining a copy of this software and
    // associated documentation files (the "Software"),
    // to deal in the Software without restriction,
    // including without limitation the rights to use,
    // copy, modify, merge, publish, distribute, sublicense,
    // and/or sell copies of the Software, and to permit
    // persons to whom the Software is furnished to do so,
    // subject to the following conditions:

    // The above copyright notice and this permission notice
    // shall be included in all copies or substantial
    // portions of the Software.

    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY
    // OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
    // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
    // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
    // ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


    // --------------------- OPTIONS ---------------------

    const archiveFileName = "archive.taskpaper";

    // Should archived items go to the top or bottom of
    // the archive file ?
    const blnArchiveAtTop = true;

    // ---------------------- MAIN -----------------------
    // main :: IO ()
    const main = () => {
        const
            taskpaper = Application("Taskpaper"),
            documents = taskpaper.documents;

        return either(
            msg => alert("Archiving TaskPaper items")(msg)
        )(
            x => x
        )(
            0 < documents.length ? (() => {
                const
                    frontDocument = documents.at(0),
                    folderPath = takeDirectory(
                        `${frontDocument.file()}`
                    ),
                    taskIdPairs = frontDocument.evaluate({
                        script: `${tp3ReadArchive}`
                    });

                return toLower(archiveFileName) !== (
                    toLower(frontDocument.name())
                ) ? (
                    0 < taskIdPairs.length ? (
                        archivedAndConfirmedLR(
                            blnArchiveAtTop
                        )(
                            frontDocument
                        )(
                            combine(folderPath)(
                                archiveFileName
                            )
                        )(
                            taskIdPairs
                        )
                    ) : Left(
                        "No archived tasks seen in front document."
                    )
                ) : Left([
                    "The front document appears to be the archive itself.",
                    "",
                    `${frontDocument.file()}`,
                    "",
                    "(Archiving *from* the archive *to* the ",
                    "archive itself is not a defined option ...)"
                ].join("\n"));
            })() : Left("No document open in TaskPaper")
        );
    };

    // archivedAndConfirmedLR :: Bool -> TP3 Document ->
    // FilePath -> [(String, ID)] -> IO Either String String
    const archivedAndConfirmedLR = blnTop =>
        frontDocument => fpArchive => taskIdPairs => bindLR(
            idsAddedToArchiveFileLR(
                blnTop
            )(
                fpArchive
            )(
                taskIdPairs
            )
        )(
            ids => bindLR(
                frontDocument.evaluate({
                    script: `${tp3ClearArchive}`,
                    withOptions: {
                        archivedIDs: ids
                    }
                })
            )(
                taskList => Right(
                    `Archived to:\n\t${fpArchive}\n\n${taskList}`
                )
            )
        );

    // idsAddedToArchiveFileLR :: Bool -> FilePath ->
    // [(String, ID)] -> Either String [ID]
    const idsAddedToArchiveFileLR = blnTopOfFile =>
        fp => textsWithIDs => {
            const
                existingLines = either(() => "")(s => s)(
                    readFileLR(fp)
                ),
                timeStamp = taskPaperDateString(new Date()),
                linesToArchive = [
                    `archived: @at(${timeStamp})`
                ].concat(
                    textsWithIDs
                    .map(x => `\t${x[0]}`)
                )
                .join("\n"),
                updatedContent = blnTopOfFile ? (
                    `${linesToArchive}\n\n${existingLines}`
                ) : `${existingLines}\n\n${linesToArchive}`;

            return bindLR(
                writeFileLR(fp)(updatedContent)
            )(
                () => bindLR(
                    readFileLR(fp)
                )(
                    checked => updatedContent === checked ? (
                        Right(textsWithIDs.map(snd))
                    ) : Left(
                        "Archive not updated as expected."
                    )
                )
            );
        };

    // ---------------- TASKPAPER CONTEXT ----------------

    // tp3ReadArchive :: Editor -> IO String
    const tp3ReadArchive = editor => {
        const
            archivedTasks = editor.outline
            .evaluateItemPath("//Archive//*");

        return archivedTasks.map(
            task => [task.bodyString, task.id]
        );
    };


    // tp3ClearArchive :: IO ()
    const tp3ClearArchive = (editor, options) => {
        const tp3ClearMain = () => {
            const
                outline = editor.outline,
                archivedNodesToClear = options.archivedIDs
                .flatMap(nodeID => {
                    const
                        node = outline.getItemForID(
                            nodeID
                        );

                    return Boolean(node) ? (
                        [node]
                    ) : [];
                }),
                archivedText = archivedNodesToClear.map(
                    x => x.bodyString
                ).join("\n");

            return Right((
                outline.removeItems(
                    archivedNodesToClear
                ),
                archivedText
            ));
        };

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

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

        return tp3ClearMain();
    };

    // ----------------------- 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
            );
        };

    // --------------------- 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
    });


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


    // 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);


    // 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;


    // iso8601Local :: Date -> String
    const iso8601Local = dte =>
        new Date(dte - (6E4 * dte.getTimezoneOffset()))
        .toISOString();


    // 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));
    };

    // taskPaperDateString :: Date -> String
    const taskPaperDateString = dte => {
        const [d, t] = iso8601Local(dte).split("T");

        return [d, t.slice(0, 5)].join(" ");
    };

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


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


    // takeDirectory :: FilePath -> FilePath
    const takeDirectory = fp =>
        "" !== fp ? (
            (xs => xs.length > 0 ? xs.join("/") : ".")(
                fp.split("/").slice(0, -1)
            )
        ) : ".";


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


    // writeFileLR :: FilePath -> Either String IO FilePath
    const writeFileLR = fp =>
        s => {
            const
                e = $(),
                efp = $(fp)
                .stringByStandardizingPath;

            return $.NSString.alloc.initWithUTF8String(s)
                .writeToFileAtomicallyEncodingError(
                    efp, false,
                    $.NSUTF8StringEncoding, e
                ) ? (
                    Right(ObjC.unwrap(efp))
                ) : Left(ObjC.unwrap(e.localizedDescription));
        };

    // ------------ MAIN FUNCTION CALLED HERE ------------
    return main();
})();

Updated (above, draft 0.02) to cover the case where the front document appears to be the archive file itself.

(The script simply shows a message to this effect, and then halts)

1 Like

hi @complexpoint thanks a lot for your work here
I can’t test it as I receive this when I ran in the script editor
I used the link you have provided to implement it


Thanks

The part of [Using Scripts](https://www.taskpaper.com/guide/using-taskpaper/using-scripts.html) that will help with that is:

Using Scripts > To Try a Script > section 3

( on JavaScript ⇄ AppleScript)

There’s a pop up in the upper left of the Script Editor allowing you to choose the scripts format.

( C’est le JS qu’il vous faut )

Screenshot 2021-03-09 at 14.06.32

Thank you. This looks very cool but dangerous. Added to the wiki!

Sorry I was to quick ! But you learn french? :slight_smile:
It seems to make the job :
Move the @done to an archive.taskpaper file in the same folder
It take all @done from anywhere on the front doc and move to the archive exactly what I would like !!
Thanks a lot for that!!
In the archive file no project, and each time I run the script it create a blank line and move the @done line into the archive line, it create a block at each run
Is it possible to add the time and hour of the created block? Or create a project with the date, time?
It is more some cosmetics stuff but it works and do the job!!
Thanks a lot!!!

Thanks – I agree about the dangers, and I wonder if it’s tested enough, at this point, for the wiki ?

Should we hide it for a week or two until its better cooked ?

( I don’t really enjoy sharing scripts that involve deletions …)

All those things can be done
and the source code is there to be studied : -)

( I may not get around to it myself, just now, a little busy at the moment )