Tinderbox Forum

Trying to make a simple list of tags

I’ve got a big document with a hundred or more tags. I’d like to make a simple list of all my tags, and copy and paste it into a separate document so I can see at a quick glance the tags I’ve created.

I’m not the smartest kind on the block, so if someone could help me with this simple task, I’d appreciate it.

Any help? I just need to make a simple list of all my tags. Thanks.

Probably the easiest way is to make a new note and give it this edict:

$Text=values("Tags").format("\n");

This will set the note’s text to all the unique (N.B. case sensitive) values of $Tags, one per line. It is then easy to click in the note text and do Cmd+A (select all) and Cmd+C (copy) and the list is on the clipboard. This saves doing any special export work. Or if you want RTF output, slect the note, use file -> Export -> RTF, chose (non-default) pop-up scope setting of ‘Selected Notes’:

Monosnap%202019-06-22%2016-11-51

Why an edict? It runs on first creation and then once per hours (approx) but can be run on demand from the Edict Inspector. THe older way of using a rule means this runs all the time which you don’t really need. Indeed, if you don’t need to repeat the task, delete the new note once you’ve exported the data.

“But I need a case-insenstive list”

Ok, then do this. Again a new note but a slightly different edict code:

$MySet=values("Tags").lowercase;
$Text=$MySet.format("\n");
$MySet=;

Does that help?

2 Likes

Russ, the approach that @mwra provided – namely, using values($AttributeName).format("\n") to get a list of all values for a certain attribute – works like a charm in my experience.

(And, with the refinements he suggested about upper/lower case sensitivity, if needed in your set-up. I usually enter all my tags and attribute-values with initial-upper-case, so the simpler version above does the job for me.)

1 Like

If you are using Tinderbox 8 and don’t want to set up an extra note for a temporary task then you can also just run a short script like this one:

-- select Tinderbox notes, click <run>, paste results
use framework "Foundation" -- for sort and removing duplicates
use scripting additions
tell application "Tinderbox 8" to tell selections of front document to set tagsList to value of attribute "Tags"
set text item delimiters to ";"
set tagsList to text items of (tagsList as text) --> 'flat' AppleScript list
set tagsList to (current application's NSSet's setWithArray:tagsList)'s allObjects() as list
set tagsList to ((current application's NSArray's arrayWithArray:tagsList)'s sortedArrayUsingSelector:"compare:") as list
set text item delimiters to return
set the clipboard to tagsList as text
return tagsList -- line optional, to view tags in Script Editor 'result' pane
  1. Copy-paste above script into Script Editor (in Applications/Utilities)
  2. Make sure Script Editor and Tinderbox are listed and checked at System Preferences > Security & Privacy > Privacy > Accessibility.
  3. Select the relevant notes in Tinderbox (multiple outline levels ok).
  4. Click the triangle button in Script Editor.
  5. Command-v to paste wherever.

This script can be reused with other Tinderbox documents if you have to do this again in the future.

SG

1 Like

For comparison, or anyone using JS, we could write, either in a briefer idiom (not really my thing, as it happens):

Relatively parsimonious JS

(() => {
    'use strict';

    const main = () => {
        const docs = Application('Tinderbox').documents;
        return 0 < docs.length ? (() => {
            const
                selns = docs[0].selections,
                tags = 0 < selns.length ? (
                    Array.from(new Set(
                        concatMap(
                            s => s.split(';')
                            .filter(k => 0 < k.trim().length),
                            selns.attributes.byName('Tags').value()
                        )
                    ))
                ) : [],
                sa = Object.assign(Application('System Events'), {
                    includeStandardAdditions: true
                }),
                strList = tags.sort().join('\n')

            return (
                sa.setTheClipboardTo(strList),
                strList
            );
        })() : 'No document open';
    };

    // GENERIC FUNCTIONS ----------------------------------
    // https://github.com/RobTrew/prelude-jxa

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) => [].concat(...xs.map(f))

    // MAIN ---
    return console.log(
        main()
    );
})();

or
Slightly fuller JS (alerts, and slightly more explanatory messages)

(() => {
    'use strict';

    const main = () => {

        const strTitle = 'Tags in selected Tinderbox notes';
        return either(
            // A message (why no tags – in the Left channel),
            msg => alert(strTitle)(msg),

            // OR a list of tags (in the Right channel).
            strTagList => (
                sa.setTheClipboardTo(strTagList),
                alert(strTitle)(
                    'Copied to clipboard:\n\n' + strTagList
                ),
                strTagList
            ),
            bindLR(
                bindLR( // If a document is found, bind it to the name 'doc'.
                    tbxFrontDocLR(),
                    doc => 0 < doc.selections.length ? (
                        Right(doc.selections)
                    ) : Left('No notes selected in: ' + doc.name())
                ),
                // If the selection is not empty, bind it to the name 'selns'.
                selns => {
                    const
                        tags = Array.from(new Set(
                            concatMap(
                                s => s.split(';')
                                .map(x => x.trim())
                                .filter(k => 0 < k.length),
                                selns.attributes.byName('Tags').value()
                            )
                        ));
                    return 0 < tags.length ? (
                        Right(tags.sort().join('\n'))
                    ) : Left(
                        'No tags attached to selected notes.'
                    );
                }
            )
        );
    };

    // TINDERBOX ------------------------------------------

    // tbxFrontDocLR :: Tbx IO () -> Either String Tbx Doc
    const tbxFrontDocLR = () => {
        // Either the front document in Tinderbox 8, or an
        // explanatory message if no documents are open,
        // or Tinderbox 8 is not running.
        const tbx = Application('Tinderbox 8');
        return tbx.running() ? (() => {
            const ds = tbx.documents;
            return 0 < ds.length ? (
                Right(ds.at(0))
            ) : Left('No documents open in Tinderbox');
        })() : Left('Tinderbox 8 is not running.');
    };

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

    const
        sa = Object.assign(Application('System Events'), {
            includeStandardAdditions: true
        });

    // alert :: String => String -> IO String
    const alert = title => s => (
        sa.activate(),
        sa.displayDialog(s, {
            withTitle: title,
            buttons: ['OK'],
            defaultButton: 'OK',
            withIcon: sa.pathToResource('AppIcon.icns', {
                inBundle: 'Applications/Tinderbox 8.app'
            })
        }),
        s
    );


    // GENERIC FUNCTIONS ----------------------------------
    // https://github.com/RobTrew/prelude-jxa

    // 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) =>
        undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) =>
        xs.reduce((a, x) => a.concat(f(x)), []);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = (fl, fr, e) =>
        'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // MAIN ---
    return main();
})();
1 Like

Thank you everybody. Very much appreciated!

Absolutely, it does, thanks

The others work, too.