About Me

My photo
I know the last digit of PI

Monday, September 14, 2020

Transfer/Export/Copy Notes from one Mac to a another

Suprisingly the Notes application that comes with MacOS lack of export/import functionality. One workaround of that limitation is using Automator. However it exports only the notes text without the attachments.

Exporting notes from Mac 1:

  1. Open Automator from the applications.
  2. Create a new Workflow
  3. Choose Run JavaScript
  4. Copy/paste following export script:
    function run(input, parameters) {
    
    	const hexEncode = function(s){
        	let result = "";
        	for (let i=0; i < s.length; i++) {
            	hex = s.charCodeAt(i).toString(16);
            	result += ("0"+hex).slice(-2);
        	}
    
        	return result;
    	};
    	
    	const pad = function(n, size) {
        	let s = n + "";
        	while (s.length < size) {
    			s = "0" + s;
    		}
        	return s;
    	};
    
    	
    	const app = Application.currentApplication();
    	app.includeStandardAdditions = true;
    	const notesApp = Application('Notes');
    	notesApp.includeStandardAdditions = true;
    
    	const notes = notesApp.notes;
    	
    	const selectedNotes = app.chooseFromList(notes.name(), { withPrompt: "Select notes to export ?", multipleSelectionsAllowed: true });
    
    
    	if (selectedNotes) {
    		var destFolder = app.chooseFolder().toString();
    		let exported = 0;
    		if (destFolder) {
    			for(let i = 0; i < notes.length; i++) {
    				if (selectedNotes.indexOf(notes[i].name()) > -1) {
    					const fileName = destFolder + "/" 
    						+ pad(i, 9) + 
    						+ "." + hexEncode(notes[i].name())
    						+ "." + notes[i].modificationDate().getTime()
    						+ "." + notes[i].modificationDate().getTime()
    						//+ "." + hexEncode(notes[i].id()) 
    						+ ".html";
    					var f = app.openForAccess(Path(fileName), { writePermission: true });
    					app.setEof(f, { to: 0 });
    					app.write(notes[i].body(), {to: f});
    					
    					app.closeAccess(f);
    					
    					//const attachments = notes[i].attachments();
    					//if (attachments.length > 0) {
    					//	attachments[0].show();
    					//	app.displayDialog(attachments[0].id().toString());						
    					//}
    					
    					exported++;
    				}
    			}
    			app.displayDialog("Done. Notes exported: " + exported);
    		}
    	}
    
    	return input;
    }
        
  5. Click Run and select notes to be exported and destination directory (preferable empty one)
  6. Copy all notes files to destination Mac 2 (keep the original file names).

Importing notes to Mac 2:

  1. Open Automator from the applications.
  2. Create a new Workflow
  3. Choose Run JavaScript
  4. Copy/paste following export script:
    function run(input, parameters) {
    	'use strict';
    	
    	
    	const hexDecode = function(s) {
    		const hexes = s.match(/.{1,2}/g) || [];
    		let res = hexes.map(h => String.fromCharCode(parseInt(h, 16))).join("");
    		return res;
    	}
    		
    	const epochToStr = function(s) {
    		const lz = function (n) { return (n <=9 ? "0" : "") + n };
     	
    		const d = new Date(parseInt(s))
    		return d.getFullYear() + "-" + lz(d.getMonth() + 1) + "-" + lz(d.getDate()) + " " + lz(d.getHours()) + ":" + lz(d.getMinutes());
    	}
    
    	const app = Application.currentApplication();
    	app.includeStandardAdditions = true;
    	
    	const response = app.displayDialog("Prefix note titles with", {
        	withIcon: "note",
        	buttons: ["Modification date", "Note index", "Don't prefix"],
        	defaultButton: "Modification date"
    	});
    	
    	//0index.1title.2create.3modify.
    	let prefixColumn = -1;
    	switch (response.buttonReturned) {
    		case "Modification date": prefixColumn = 3; break;
    		case "Creation date": prefixColumn = 2; break;
    		case "Note index": prefixColumn = 0; break;
    	}
    	
        const finder = Application('System Events');
    	const folderName = app.chooseFolder().toString();
    	const fileNames = finder.folders.byName(folderName).diskItems.name();
    	
    	const notesApp = Application('Notes');
    	notesApp.includeStandardAdditions = true;
    		
    
    	const prefix = function (fileName) {
    		const titleItems = fileName.split('.');		
    		if (prefixColumn >= 0) {
    			titleItems[2] = epochToStr(titleItems[2]);
    			titleItems[3] = epochToStr(titleItems[3]);
    			return titleItems[prefixColumn];
    		} else {
    			return titleItems[0];
    		}
    	}
    	const prefixCoeff = prefixColumn == 2 || prefixColumn == 3 ? 1 : -1;
    	fileNames.sort(function(a,b) { return prefixCoeff * prefix(a).localeCompare(prefix(b)); });
    	
    	
    
    	for (let i = 0; i < fileNames.length; i++) {
    		const path = new Path(folderName + "/" + fileNames[i]);
    
    		const titleItems = fileNames[i].split('.');
    		titleItems[2] = epochToStr(titleItems[2]);
    		titleItems[3] = epochToStr(titleItems[3]);
    		const titlePrefix = (prefixColumn >= 0 ? "[" + titleItems[prefixColumn] + "] " : "");
    		const title = titlePrefix + hexDecode(titleItems[1]);
    		     	
    		const f = app.openForAccess(path);
    		const size = parseInt(app.doShellScript("stat -f%z " + path.toString().replace(" ", "\\ ")));
    		
    		try {
    			let content = "";
    		    if (size > 0) {
    				content = app.read(f);
    			}
    		
    			notesApp.make({ new:"note", withProperties: {name: title, body:content}});			
    		} catch (e) {
    			const msg = "Error processing file: " + fileNames[i] + " (title: " + title + "). Error message:" + e.toString();
    			app.displayDialog(msg + ". Do you want to continue?");
    			notesApp.make({ new:"note", withProperties: {name: title, body:msg}});
    		} finally {
    			app.closeAccess(f);
    		}
    	}
    	
    	app.displayDialog("Done. Notes processed: " + fileNames.length);
    
    	return input;
    }
    
  5. Click Run and select the folder containing notes exported from Mac 1