Experimental (not for use) toggling between note attributes and rows

The (AppleScript) sketch below is a thought experiment – untested, and not ready for use with real data.

The issue it deals with is the non-standard (Omni-initiated, I think, and some other apps follow its lead) pattern of storing some non-outline paragraphs in an outline row as “notes”, using an XML attribute named _note.

By default, Bike happily opens and saves OPML with these _note attributes but doesn’t at the moment show their presence in the editor.

I’m personally not a fan of the Title + Body Paras approach to using outliners, which I think:

  • is a hangover from email
  • excludes some paragraphs from outlining operations
  • implies two different kinds of editor in one app

but given that it’s out there, and Bike users may well need to import existing _note attributed OPML, here’s an experiment (not ready for use – test only with dummy data) in:

  • toggling hidden _note attributes into visible Bike rows, prefixed with (for example) > (for import)
  • toggling childless (> prefixed) rows, at the start of a sibling sequence, into a _note attribute of the parental row (for export)

Really just a first experiment with the new scriptability of key:value attributes in Bike rows, and not intended as a working tool:

Expand disclosure triangle to view AppleScript source
-- Possible approach ?
-- Not sure ...

-- EXPERIMENT ONLY – NOT READY FOR USE WITH REAL DATA

-- (I never like to share anything that uses a 'delete' method)


-- Ver 0.02
-- This cannot really be done in JXA I, I think, for lack of location specifiers
--  e.g. in `make new row at beginning of rows`

-- 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.


------- TOGGLE _NOTE ATTRIBUTES ⇄ PREFIXED CHILDREN ------
on run
    tell application "Bike"
        set doc to front document
        
        if exists doc then
            set docRows to a reference to rows of doc
            set rowNotePairs to my docRowsWithNotes(docRows)
            
            if 0 < length of rowNotePairs then
                my noteAttribsToPrefixedChildren(">", doc, rowNotePairs)
            else
                my prefixedChildrenToNoteAttribs(">", docRows)
            end if
        else
            "No document open in Bike"
        end if
    end tell
end run


--------------------- NOTE ATTRIBUTES --------------------

-- docRowsWithNotes :: Bike Doc -> IO [(Row, String)]
on docRowsWithNotes(rowsOfDoc)
    using terms from application "Bike"
        tell rowsOfDoc
            set noteAttribs to a reference to ¬
                (attributes where name is "_note")
        end tell
        
        script go
            on |λ|(xs)
                if {} ≠ xs then
                    {item 1 of xs}
                else
                    {}
                end if
            end |λ|
        end script
        
        zip(concatMap(go, container row of noteAttribs), ¬
            concatMap(my identity, value of noteAttribs))
    end using terms from
end docRowsWithNotes


-- noteAttribsToPrefixedChildren :: Char -> [(Row, String)] -> IO ()
on noteAttribsToPrefixedChildren(prefixChar, doc, rowNotePairs)
    using terms from application "Bike"
        repeat with ab in rowNotePairs
            set {attribRow, attribText} to ab
            --                     
            repeat with para in paragraphs of attribText
                tell doc to make new row at beginning of rows of attribRow ¬
                    with properties {name:prefixChar & space & para}
                
                set attrib to (a reference to ¬
                    (attribute "_note" of attribRow))
                if exists attrib then delete attrib
            end repeat
        end repeat
    end using terms from
end noteAttribsToPrefixedChildren


-- prefixedChildrenToNoteAttribs :: Char -> Rows -> IO ()
on prefixedChildrenToNoteAttribs(prefixChar, docRows)
    using terms from application "Bike"
        
        set prefixedNoteRows to {}
        repeat with oRow in docRows
            set isParent to contains rows of oRow
            if isParent then
                set xs to (rows of oRow)
                set lst to {}
                
                repeat with oChild in xs
                    set x to oChild
                    if not (contains rows of x) then
                        set s to name of oChild
                        if s begins with prefixChar then
                            set end of lst to my drop(2, s)
                            set end of prefixedNoteRows to x
                        end if
                    end if
                end repeat
                
                if {} ≠ lst then
                    tell oRow
                        make attribute with properties {name:"_note", value:my unlines(lst)}
                    end tell
                end if
            end if
        end repeat
        set n to length of prefixedNoteRows
        repeat with i from n to 1 by -1
            delete item i of prefixedNoteRows
        end repeat
    end using terms from
end prefixedChildrenToNoteAttribs


------------------------- GENERIC ------------------------

-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
    set lng to length of xs
    set acc to {}
    
    tell mReturn(f)
        repeat with i from 1 to lng
            set acc to acc & (|λ|(item i of xs, i, xs))
        end repeat
    end tell
    acc
end concatMap


-- identity :: a -> a
on identity(x)
    -- The argument unchanged.
    x
end identity


-- min :: Ord a => a -> a -> a
on min(x, y)
    if y < x then
        y
    else
        x
    end if
end min


-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

-- drop :: Int -> String -> String
on drop(n, xs)
    if n < length of xs then
        text (1 + n) thru -1 of xs
    else
        ""
    end if
end drop

-- unlines :: [String] -> String
on unlines(xs)
    -- A single string formed by the intercalation
    -- of a list of strings with the newline character.
    set {dlm, my text item delimiters} to ¬
        {my text item delimiters, linefeed}
    set s to xs as text
    set my text item delimiters to dlm
    s
end unlines


-- zip :: [a] -> [b] -> [(a, b)]
on zip(xs, ys)
    set n to min(length of xs, length of ys)
    
    set lst to {}
    repeat with i from 1 to n
        set end of lst to {item i of xs, item i of ys}
    end repeat
    lst
end zip

1 Like

Note: I sketched this on macOS 11.6.6 (Big Sur)

I hear that it may not compile on more recent versions of macOS, so perhaps another draft coming :slight_smile:


UPDATE ( I think that scare is off )


In the meanwhile, if it is compiling on your system, and you want to experiment, the intention is that it should toggle back and forth between an outline like this:

ALPHA
    > BETA
    > GAMMA
    DELTA

and an equivalent outline which looks like this in Bike:

ALPHA
	DELTA

and has a hidden BETA\nGAMMA _note attribute in the ALPHA row which would display as a two line note, attached to ALPHA, in an appropriate application, if you:

  • Saved the Bike file as OPML,
  • and opened it in something like OmniOutliner.

If you do experiment with this script, another script on this forum may help you to bulk-apply > prefixes to all selected lines, (and/or) bulk clear them:

Script :: Toggle a prefix character in selected lines - Bike - Hog Bay Software Support

1 Like