content: --
  --	Shift notes by X pages, v1.0
  --		a script for Skim open-source PDF reader (https://sourceforge.net/projects/skim-app/)
  --	
  --	Created by:			FurloSK @ https://github.com/FurloSK
  --	Created on:			16/05/2020
  --	Last modified on:	16/05/2020, v1.0
  --
  --	Copyright © 2020 FurloSK, All Rights Reserved
  --
  
  use AppleScript version "2.4" -- Yosemite (10.10) or later
  use scripting additions
  
  set lineBreak to "
  "
  set nl to lineBreak
  set nl2 to nl & nl
  set textEnd to ""
  
  
  tell application "Skim"
  	activate
  	
  	# show welcome dialog
  	display dialog ¬
  		"This script will move notes by chosen number of pages forward or backward in the file. This is particularly useful when you want to delete some pages from the file, extract part of the file, or add new pages to it, because after doing any of these, original Skim notes will no longer fit the pages on which they should be." & nl2 ¬
  		& "The tool must be used prior to altering pages of the file with external program (such as Acrobat)." & nl2 ¬
  		& "How to use this tool:" & nl ¬
  		& "❶ Enable \"Automatically save Skim notes backups\" option in Skim Preferences → General." & nl ¬
  		& "❷ Open the file you want to edit and save it." & nl ¬
  		& "❸ If you want to remove pages from the file, delete all notes contained in the pages that will be removed. Alternatively, if you want to extract the portion of the file, delete all notes contained in the pages that won't be in the final extract." & nl ¬
  		& "❹ Save the file." & nl ¬
  		& "❺ Run this tool and shift notes as needed." & nl ¬
  		& "❻ Save the file and close it." & nl ¬
  		& "❼ Open the file with external program and make the edits you need." & nl ¬
  		& "❽ Save the file and close it." & nl ¬
  		& "❾ Done! You can open the file in Skim and see the result for yourself." & nl2 ¬
  		& "Press »Run tool« to start the script." & textEnd ¬
  		with title "Shift notes by X pages" with icon 1 ¬
  		buttons {"Cancel", "Run tool"} default button 2 cancel button 1
  	
  	(*=============================================================
  	===== Stage I: Getting user input & Checking conditions
  	=============================================================*)
  	
  	# check there is at least one document
  	if (count of documents) is 0 then
  		beep
  		display dialog "No documents opened, exiting." & textEnd ¬
  			buttons {"•"} default button 1 with icon caution giving up after 3
  		return
  	end if
  	
  	tell document 1
  		# check there is at least one note
  		if (count of notes) = 0 then
  			beep
  			display dialog "No notes found, exiting." & textEnd ¬
  				buttons {"•"} default button 1 with icon caution giving up after 3
  			return
  		end if
  		
  		# get basic document limits
  		set documentPageCount to (count of pages)
  		set shiftPagesMaximum to documentPageCount - 1
  		set pageRangeMinimum to 1
  		set pageRangeMaximum to documentPageCount
  		
  		# check that the document is at least two pages long
  		if (documentPageCount < 2) then
  			beep
  			display dialog "Document has only one page." & nl2 & "Shifting notes not possible, exiting." & textEnd ¬
  				buttons {"•"} default button 1 with icon caution giving up after 3
  			return
  		end if
  		
  		# get shift value for moving notes
  		set shiftPages to text returned of (display dialog ¬
  			"To start the script, you must select by how many pages to shift the notes, and the page range of notes that should be shifted. Usage examples:" & nl2 ¬
  			& "𝗧𝗼 𝗿𝗲𝗺𝗼𝘃𝗲 𝟱 𝗽𝗮𝗴𝗲𝘀 from page 10 to 14:" & nl ¬
  			& "Delete notes on pages 10-14. Then shift notes by 5 pages backward (-5) on page range from 15 to document's last page. Then delete the pages." & nl2 ¬
  			& "𝗧𝗼 𝗮𝗱𝗱 𝟱 𝗽𝗮𝗴𝗲𝘀 before page 10:" & nl ¬
  			& "Shift notes by 5 pages forward (+5) on page range from 10 to document's last page. Then add new pages." & nl2 ¬
  			& "𝗧𝗼 𝗲𝘅𝘁𝗿𝗮𝗰𝘁 𝟱 𝗽𝗮𝗴𝗲𝘀 from page 10 to 14:" & nl ¬
  			& "Delete notes on all pages before page 10 and after page 14. Then shift notes by 9 pages backward (-9) on page range from 10 to 14. Then delete pages 1 to 9 and 15 to document's last page." & nl2 ¬
  			& "If you wish to proceed, please write down a number to tell the script by how many pages to shift notes. You need to enter positive value to move the notes forward, while to move them backward you need to enter a negative number." & nl2 ¬
  			& "Note that you cannot set the number of pages by which to shift the notes to be higher than " & textEnd ¬
  			& (shiftPagesMaximum as string) & " (or lower than -" & (shiftPagesMaximum as string) ¬
  			& ", respectively), as the total number of pages of current document is " & (documentPageCount as string) & "." & textEnd ¬
  			default answer "0" with title "Shift notes by X pages" with icon 1 ¬
  			buttons {"Cancel", "Next"} default button 2 cancel button 1)
  		try
  			set shiftPages to shiftPages as integer
  		on error
  			beep
  			display dialog "Page shift entered is not an integer, exiting." & textEnd ¬
  				buttons {"•"} default button 1 with icon caution giving up after 3
  			return
  		end try
  		if (shiftPages = 0) then
  			beep
  			display dialog "Page shift entered is 0." & nl2 & "Nothing to do, exiting." & textEnd ¬
  				buttons {"•"} default button 1 with icon caution giving up after 3
  			return
  		end if
  		
  		# check and correct shift value with respect to document length
  		if (shiftPages > shiftPagesMaximum) then # shift forward
  			set shiftPages to shiftPagesMaximum
  		end if
  		if (shiftPages < (-1 * shiftPagesMaximum)) then # shift backward
  			set shiftPages to (-1 * shiftPagesMaximum)
  		end if
  		
  		# adjust page range bounds with respect to shift value
  		if (shiftPages > 0) then
  			set pageRangeMaximum to pageRangeMaximum - shiftPages
  			
  			set pageRangeMinimumText to "Minimum value is " & (pageRangeMinimum as string) & "."
  			set pageRangeMaximumText to "Maximum value is " & (pageRangeMaximum as string) ¬
  				& ", as you have chosen to move notes by " & shiftPages ¬
  				& " pages forward and the document is " & documentPageCount & " pages long."
  		else
  			set pageRangeMinimum to pageRangeMinimum + (shiftPages * -1)
  			
  			set pageRangeMinimumText to "Minimum value is " & (pageRangeMinimum as string) ¬
  				& ", as you have chosen to move notes by " & (shiftPages * -1 as string) & " pages backward."
  			set pageRangeMaximumText to "Maximum value is " & (pageRangeMaximum as string) ¬
  				& ", as the document is " & documentPageCount & " pages long."
  		end if
  		
  		# get starting page for the range of pages to execute shifting
  		set pageRangeFromText to "Select the starting page for range of pages on which notes should be shifted." & nl2 ¬
  			& pageRangeMinimumText & nl2 & pageRangeMaximumText
  		set pageRangeFrom to text returned of (display dialog ¬
  			pageRangeFromText ¬
  				default answer pageRangeMinimum with title "Range of pages: start page" with icon 1 ¬
  			buttons {"Cancel", "Next"} default button 2 cancel button 1)
  		try
  			set pageRangeFrom to pageRangeFrom as integer
  		on error
  			beep
  			display dialog "Start page entered is not an integer, exiting." & textEnd ¬
  				buttons {"•"} default button 1 with icon caution giving up after 3
  			return
  		end try
  		
  		# get ending page for the range of pages to execute shifting
  		set pageRangeToText to "Select the ending page for range of pages on which notes should be shifted." & nl2 ¬
  			& pageRangeMinimumText & nl2 & pageRangeMaximumText
  		set pageRangeTo to text returned of ¬
  			(display dialog pageRangeToText ¬
  				default answer pageRangeMaximum with title "Range of pages: end page" with icon 1 ¬
  				buttons {"Cancel", "Move notes"} default button 2 cancel button 1)
  		try
  			set pageRangeTo to pageRangeTo as integer
  		on error
  			beep
  			display dialog "Ending page entered is not an integer, exiting." & textEnd ¬
  				buttons {"•"} default button 1 with icon caution giving up after 3
  			return
  		end try
  		
  		# check & correct page range with respect to shift value
  		if (pageRangeFrom > pageRangeMaximum) then
  			set pageRangeFrom to pageRangeMaximum
  		end if
  		if (pageRangeFrom < pageRangeMinimum) then
  			set pageRangeFrom to pageRangeMinimum
  		end if
  		if (pageRangeTo > pageRangeMaximum) then
  			set pageRangeTo to pageRangeMaximum
  		end if
  		if (pageRangeTo < pageRangeMinimum) then
  			set pageRangeTo to pageRangeMinimum
  		end if
  		
  		(*=============================================================
  		===== Stage II: Shifting notes
  		=============================================================*)
  		
  		(* Note properties:
  		generic				→	type, text, color, bounds, modification date, user name
  		text note			→	font name, font size, alignment, font color
  		anchored note		→	extended text, icon
  		circle/box/line note	→	interior color
  		generic ???			→	line width, line style, dash pattern
  		line note			→	start point, end point, start line style, end line style
  		freehand note		→	path list
  		*)
  		
  		set documentNoteCount to (count of notes)
  		set totalNotesMoved to 0
  		
  		# debug
  		log "Proceeding to shift " & documentNoteCount & " notes"
  		log " → page shift: " & shiftPages & " (bounds <-" & shiftPagesMaximum & "," & shiftPagesMaximum & ">)"
  		log " → range from: " & pageRangeFrom & " (minimum " & pageRangeMinimum & ")"
  		log " → range to: " & pageRangeTo & " (maximum " & pageRangeMaximum & ")"
  		
  		# determine the order for processing pages: when notes are moved backward, we start normally on first page and go forward
  		set pageIncrement to 1
  		if (shiftPages > 0) then
  			# but when notes are moved forward, we need to start from last page and go backward to avoid moving the same note multiple times
  			set temp to pageRangeFrom
  			set pageRangeFrom to pageRangeTo
  			set pageRangeTo to temp
  			set pageIncrement to -1
  		end if
  		
  		# cycle over all pages
  		repeat with pageIndex from pageRangeFrom to pageRangeTo by pageIncrement
  			set pageNoteCount to (count of notes of page pageIndex)
  			log "Page " & pageIndex & " has " & pageNoteCount & " notes"
  			
  			# if there are no notes, skip
  			repeat 1 times # fake loop
  				if pageNoteCount is 0 then
  					exit repeat
  				end if
  				
  				set destPage to (pageIndex + shiftPages)
  				
  				# cycle over all notes in current page
  				set noteIndexReal to 1
  				repeat with noteIndex from 1 to pageNoteCount
  					# get the note - since we are moving notes out of their original place, we will always move note 1, not note noteIndex!
  					set theNote to note noteIndexReal of page pageIndex
  					set noteType to (type of theNote)
  					
  					log " → Moving note #" & noteIndex & " [" & noteType & "] to page " & destPage & ¬
  						": »" & (text of theNote) & "«"
  					
  					# moving note
  					if noteType is in {highlight note, underline note, strike out note} then
  						# markup notes cannot be moved, but can be recreated easily
  						
  						# copy original note properties
  						tell theNote
  							set noteProperties to ¬
  								{type:get its type, color:get its color, bounds:get its bounds, modification date:get its modification date}
  							if not (its user name = missing value) then
  								set noteProperties to noteProperties & {user name:get its user name}
  							end if
  							set noteSelection to (get its selection)
  						end tell
  						
  						# check that the markup note has valid selection - if not, keep the original note intact.
  						if (count of noteSelection) is 0 then
  							# Note that this is probably the result of deleting/adding pages to file before moving the notes themselves.
  							# In such cases, just display warning and keep the original note intact.
  							beep
  							display dialog "Warning: " & noteType & " on page " & pageIndex & " could not be moved, as it doesn't contain a valid text selection." & nl2 ¬
  								& "Text of original note:" & nl & " »" & (text of theNote) & "«" & nl2 ¬
  								& "This is probably the result of deleting or adding pages to file before moving the notes themselves, which causes all markup notes (highlight, underline and strike out notes) to loose their selection, and to be uncopyable. To avoid this problem in the future, move the notes first before deleting or adding new pages in the document." & nl2 ¬
  								& "Result: The note has been left intact." & textEnd ¬
  								buttons {"Cancel", "Continue"} default button 2 cancel button 1 with icon caution
  							
  							# increase real note index to start skipping this note (we cannot stay on 1 as the note remains on original page)
  							set noteIndexReal to noteIndexReal + 1
  						else
  							# create duplicate note
  							make note with data noteSelection with properties noteProperties at end of notes of page destPage
  							
  							# delete original note
  							delete (note noteIndexReal of page pageIndex)
  							
  							set totalNotesMoved to totalNotesMoved + 1
  						end if
  					else
  						# move non-markup notes normally
  						move (note noteIndexReal of page pageIndex) to page destPage
  						
  						set totalNotesMoved to totalNotesMoved + 1
  					end if
  				end repeat
  			end repeat
  		end repeat
  	end tell
  	
  	beep
  	display dialog "Done moving " & totalNotesMoved & " notes." buttons {"Continue"} default button 1 with icon 1 giving up after 2
  end tell
  