Replace Clipboard with its NATO Phonetic Representation Macro (v9.0.5)

Replace Clipboard with its NATO Phonetic Representation Macro (v9.0.5)

I find I am often required to spell things out over the phone and since I haven't memorised the NATO phonetic alphabet I created this simple macro to convert and replace any text in the clipboard with the equivalent representation in the NATO alphabet.

The JavaScript it uses I found on the web (sorry, I can't remember where) and I originally employed it in the Drafts for iOS app. Now I've put it into KM it's much more useful as I can access it anywhere through the Global Macro group and a hot key! If you find it useful or instructive, that's great.

Replace Clipboard with its NATO Phonetic Representation.kmmacros (4.1 KB)

1 Like

Very useful !

A quick footnote re this:

The JavaScript it uses I found on the web (sorry, I can't remember where)

It's a safe rule of thumb that if we can't remember where we found code, then we also can't remember whose work it is, and what copyright license was attached to it, whether explicitly or implicitly.

Here is a piece of functionally similar code which you are definitely free to copy, subject to the usual MIT terms, including retention of the attribution:

Click to reveal JS source
(() => {
    'use strict';

    ObjC.import('AppKit');

    // Rob Trew 2020
    // MIT License

    // Ver 0.0

    // main :: IO ()
    const main = () =>
        either(alert('Nato phonetic alphabet'))(
            compose(copyText, natoPhonetic)
        )(
            clipTextLR()
        );


    // natoPhonetic :: String -> String
    const natoPhonetic = s =>
        transcribed(dictNato)('-')(s);


    // transcribed :: Dict -> String -> String
    const transcribed = dict =>
        defaultChar => s => s.split('').reduce(
            (a, c) => a + (
                dict[c.toUpperCase()] || (
                    c !== '\n' ? (
                        defaultChar
                    ) : '\n'
                )
            ) + ' ',
            ''
        );


    // dictNato :: Dict
    const dictNato = {
        '0': 'Zero',
        '1': 'One',
        '2': 'Two',
        '3': 'Three',
        '4': 'Four',
        '5': 'Five',
        '6': 'Six',
        '7': 'Seven',
        '8': 'Eight',
        '9': 'Nine',
        'A': 'Alpha',
        'B': 'Bravo',
        'C': 'Charlie',
        'D': 'Delta',
        'E': 'Echo',
        'F': 'Foxtrot',
        'G': 'Golf',
        'H': 'Hotel',
        'I': 'India',
        'J': 'Juliett',
        'K': 'Kilo',
        'L': 'Lima',
        'M': 'Mike',
        'N': 'November',
        'O': 'Oscar',
        'P': 'Papa',
        'Q': 'Quebec',
        'R': 'Romeo',
        'S': 'Sierra',
        'T': 'Tango',
        'U': 'Uniform',
        'V': 'Victor',
        'W': 'Whiskey',
        'X': 'X-Ray',
        'Y': 'Yankee',
        'Z': 'Zulu',
        '@': '▶at◀',
        '.': '▶dot◀',
        ',': '▶comma◀',
        ' ': '▶⬜︎◀'
    };


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

    // alert :: String -> String -> IO String
    const alert = title =>
        s => (sa => (
            sa.activate(),
            sa.displayDialog(s, {
                withTitle: title,
                buttons: ['OK'],
                defaultButton: 'OK'
            }),
            s
        ))(Object.assign(Application('System Events'), {
            includeStandardAdditions: true
        }));

    // clipTextLR :: () -> Either String String
    const clipTextLR = () => {
        const
            v = ObjC.unwrap($.NSPasteboard.generalPasteboard
                .stringForType($.NSPasteboardTypeString));
        return Boolean(v) && v.length > 0 ? (
            Right(v)
        ) : Left('No utf8-plain-text found in clipboard.');
    };

    // copyText :: String -> IO String
    const copyText = s => {
        // String copied to general pasteboard.
        const pb = $.NSPasteboard.generalPasteboard;
        return (
            pb.clearContents,
            pb.setStringForType(
                $(s),
                $.NSPasteboardTypeString
            ),
            s
        );
    };

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

    // 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 => 'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // ---
    return main()
})();

1 Like

That is a very good point and worth remembering going forward - so thank you @ComplexPoint.

I have to say, your version is a joy to examine although my understanding of JS is a bit lacking. I will be using your code when I get a chance if only for robustness!

1 Like

I think the most important key to JS is the set of Array methods. A good place to start is by practising and internalising the use of:

  • filter
  • map
  • reduce
  • reduceRight
1 Like

Thanks for the pointer @ComplexPoint . I've been looking for ways to learn and improve my understanding of JavaScript and JXA so that is very helpful. Do you have thoughts on courses or books that would be helpful? Cheers!

The first 9 chapters of

https://eloquentjavascript.net

are relevant.

After that I would experiment and use the Mozilla pages, in particular, getting a feel for all of the methods built into the Array type.

Thanks - I’ll check that out.