Experimental 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

Here is a different experimental draft, sketched for Bike Preview 1.14 (152), updated to work with note type rows in Bike, rather than “>” prefixes.

It aims to toggle between:

  1. Omni group _note attributes (in OPML generated by OmniIOutliner, and opened in Bike)
  2. leading note type children.

Not fully tested or recommended for use with real data. Back up any Bike file before you test this script.

See Using Scripts - Bike


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 2.00
-- 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 ⇄ NOTE TYPE 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 noteAttribsToNoteChildren(doc, rowNotePairs)
			else
				my noteChildrenToNoteAttribs(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


-- noteAttribsToNoteChildren :: [(Row, String)] -> IO ()
on noteAttribsToNoteChildren(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
					make new row at beginning of rows of attribRow ¬
						with properties ¬
						{name:para, type:note}
				end tell
				
				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 noteAttribsToNoteChildren


-- noteChildrenToNoteAttribs :: Char -> Rows -> IO ()
on noteChildrenToNoteAttribs(docRows)
	using terms from application "Bike"
		
		set noteRows 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
						if type of oChild is note then
							set end of lst to name of oChild
							set end of noteRows 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 noteRows
		repeat with i from n to 1 by -1
			delete item i of noteRows
		end repeat
	end using terms from
end noteChildrenToNoteAttribs


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

2 Likes