content: -- Copyright (C) 2012 Robin Trew
  --
  -- 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.
  
  
  property pstrType : "heading"
  property pDelta : 1 -- Promote:1 Demote:-1
  
  property pstrFT : "com.foldingtext.FoldingText"
  property plstDemote : {"Items", "Un-indent"}
  property plstPromote : {"Items", "Indent"}
  
  -- INDENT / OUTDENT FOLDED NON-HEADER LINES AND THEIR SUB-BRANCHES
  -- ( PASSES INDENT/OUTDENT REQUESTS FOR HEADERS OR NON-FOLDED LINES TO THE MENU SYSTEM ITEMS > (UN)INDENT )
  
  on run
  	
  	tell application "FoldingText"
  		-- CHECK THAT THERE IS A DOCUMENT
  		set lstDocs to documents
  		if length of lstDocs < 1 then return
  		
  		tell item 1 of lstDocs
  			-- KEEP TRACK OF WHICH NODES HAVE BEEN MOVED
  			set lstMoved to {}
  			set blnNonHeader to false -- This flag will be set if any non-header folding lines are to be promoted
  			
  			--  ARE ANY OF THE SELECTED NODES COLLAPSED ?
  			set lstSeln to nodeIDs of (read JSON (body of (read JSON (HTTP request URI "/view/selection.json"))))
  			set lstCollapsed to run script (body of (read JSON (HTTP request URI "/view/collapsed.json")))
  			set lstIntersect to my Intersection(lstSeln, lstCollapsed)
  			
  			-- ARE ANY COLLAPSED SELECTIONS *NON* HEADERS ?
  			if lstIntersect ≠ {} then
  				set {dlm, my text item delimiters} to {my text item delimiters, ","}
  				set strIntersect to lstIntersect as string
  				set my text item delimiters to dlm
  				
  				set lstIntersect to read JSON (body of (read JSON (HTTP request URI "/nodes/" & strIntersect & ".json?attributes=type&list=true")))
  				
  				repeat with oType in lstIntersect
  					if oType ≠ "heading" then
  						set blnNonHeader to true
  						exit repeat
  					end if
  				end repeat
  			end if
  			
  			-- IF THERE ARE COLLAPSED SELECTIONS WHICH ARE NOT HEADERS -- LOOP THRU TO HANDLE THEM DIFFERENTLY
  			if blnNonHeader then
  				-- LIST THE LEVEL OF EACH SELECTED NODE 
  				set {dlm, my text item delimiters} to {my text item delimiters, ","}
  				set strSelnID to lstSeln as string
  				set my text item delimiters to dlm
  				
  				set lstNodes to read JSON (body of (read JSON (HTTP request URI "/nodes/" & strSelnID & ".json?attributes=level&list=true")))
  				
  				-- VISIT EACH SELECTED NODE
  				repeat with i from 1 to length of lstSeln
  					set strID to item i of lstSeln
  					
  					-- FIRST MOVING ANY DESCENDANTS (to preserve the ancestral relationships)
  					if strID is in lstCollapsed then
  						repeat with oSub in (read nodes at path "//@id=" & strID & "/descendant::*")
  							set strSubId to |id| of oSub
  							set lngNewLevel to (level of oSub) + pDelta
  							if lngNewLevel ≥ 0 then ¬
  								update nodes at path "//@id=" & strSubId with changes {level:lngNewLevel}
  							set end of lstMoved to strSubId
  						end repeat
  					end if
  					
  					-- AND THEN THE NODE ITSELF (IF IT HASN'T ALREADY BEEN MOVED)
  					if not (lstMoved contains strID) then
  						set lngNewLevel to (item i of lstNodes) + pDelta
  						if lngNewLevel ≥ 0 then ¬
  							update nodes at path "//@id=" & strID with changes {level:lngNewLevel}
  						set end of lstMoved to strID
  					end if
  				end repeat
  			else -- NO COLLAPSED NON-HEADERS ARE SELECTED -- JUST HAND OVER TO THE MENU SYTEM
  				activate
  				if pDelta > 0 then
  					tell application id "sevs" to click my GetMenuItem(pstrFT, plstPromote)
  				else
  					tell application id "sevs" to click my GetMenuItem(pstrFT, plstDemote)
  				end if
  			end if
  		end tell
  	end tell
  end run
  
  -- RETURNS A REFERENCE TO A CLICKABLE MENU ITEM
  -- E.G. set mnuZoomFit to GetMenuItem("com.foldingtext.FoldingText", {"View", "Zoom", "Zoom to Selection"})
  on GetMenuItem(strAppCode, lstMenu)
  	set lngChain to length of lstMenu
  	if lngChain < 2 then return missing value
  	
  	tell application id "sevs"
  		set lstApps to application processes where its bundle identifier = strAppCode
  		if length of lstApps < 1 then return missing value
  		tell first item of lstApps
  			-- GET THE TOP LEVEL MENU
  			set strMenu to item 1 of lstMenu
  			set oMenu to menu strMenu of menu bar item strMenu of menu bar 1
  			
  			-- TRAVEL DOWN THROUGH ANY SUB-MENUS
  			repeat with i from 2 to (lngChain - 1)
  				set strMenu to item i of lstMenu
  				set oMenu to menu strMenu of menu item strMenu of oMenu
  			end repeat
  			
  			-- AND RETURN THE FINAL MENU ITEM
  			return menu item (item -1 of lstMenu) of oMenu
  		end tell
  	end tell
  end GetMenuItem
  
  -- WARN THAT SCRIPT-DRIVEN MENU CLICKING NEEDS A SYS PREFS SETTING
  on GUIEnabled()
  	tell application id "sevs"
  		if UI elements enabled then
  			return true
  		else
  			activate
  			display dialog "This script depends on enabling access for assistive devices in system preferences" buttons "OK" default button "OK" with title pTitle & "   " & pVer
  			tell application id "sprf"
  				activate
  				set current pane to pane id "com.apple.preference.universalaccess"
  			end tell
  			return false
  		end if
  	end tell
  end GUIEnabled
  
  -- Intersection of two sets
  on Intersection(lstA, lstB)
  	set lst to {}
  	if length of lstA > length of lstB then
  		repeat with oItem in lstB
  			if lstA contains oItem then set end of lst to contents of oItem
  		end repeat
  	else
  		repeat with oItem in lstA
  			if lstB contains oItem then set end of lst to contents of oItem
  		end repeat
  	end if
  	return lst
  end Intersection
  