Write to Working Copy

I made an action to write the current draft to a file in Working Copy, it looks for the filename in the title and locates it in the repo. The title should be a Markdown heading.

Here’s my shared action: https://actions.getdrafts.com/a/1Pe

The Javascript for the action. Suggestions for improvement are welcome! Especially a good way to pass in the parameters from outside the action…

(() => {

	const repoName = "<ENTER REPO NAME>";
	const urlAutomationKey = "<ENTER URL KEY>";

	// Get the filename from the draft
	let fileName = draft.title.replace(/^#(\w?)/gi, '$1');

	if (draft.title.match(/^#/) !== "#") {
		context.fail("ERROR: Require Heading 1 tag on first line for filename");
		return;
	}

	fileName = fileName + ".md";

	// Get all details of the repo files
	let cb = CallbackURL.create();

	cb.baseURL = "working-copy://x-callback-url/status/";
	cb.addParameter("repo", repoName);
	cb.addParameter("path", "/");
	cb.addParameter("unchanged", 1);

	let success = cb.open();

	if (success) {
		let repo = cb.callbackResponse;
		let filePath = "";

		// Look for all instances of this file name
		const files = findObjectsByKey(
				JSON.parse(repo.json), 
				"name",
 				fileName);

		if (files.length == 1 ) {
			filePath = dirName(files[0].path);
		
		} else if(files.length > 1) {
			const pr = Prompt.create();

			pr.title = "Select correct path";
			pr.message = "Multiple files with this filename located.  " + 				"Please choose the correct path as save destination";
		
			const paths = [];
		
			for (let i=0; i < files.length; i++) {
				paths.push(dirName(files[i].path));
			}

			pr.addPicker("chosenPath", "", [paths], null);
			pr.addButton("OK", 1);
		
			let didSelect = pr.show();
		
			if (didSelect) {
				filePath = paths[pr.fieldValues["chosenPath"]];
			}
		}
	
		// Write file to repo
		let cb2 = CallbackURL.create();
		cb2.baseURL = "working-copy://x-callback-url/write/";
		cb2.addParameter("key", urlAutomationKey);
		cb2.addParameter("repo", repoName);
		
		if (files.length == 0) {
			// Nothing was found.  
			// Working Copy should prompt you to Save As...
			cb2.addParameter("no_path","empty");
			cb2.addParameter("filename", fileName);
		} else {
			cb2.addParameter("path", filePath + fileName);
		}
	
		cb2.addParameter("text", draft.content);

		success = cb2.open();
		if (!success) {
			alert("Something went wrong");
		}	
	}
})();

// Support functions

// Search the entire repo to find the matching filenames.
function findObjectsByKey(array, key, value) {

	var fileList = [];
	for (var i = 0; i < array.length; i++) {
		if (array[i][key] === value) {
			fileList.push(array[i]);
		}
	}
	return fileList;
}

// Get the path without the filename
function dirName(str) {
	var base = new String(str).substring(0, str.lastIndexOf('/') + 1); 
	return base;
}
2 Likes

For the parameters, have you looked at the Credential object? It allows secure storage of information that only has to be entered once and can then be reused.

Its intended for login details, tokens etc, but I have an action that just uses it to allow persistent storage for a specific action. The action is here:

https://actions.getdrafts.com/a/1Pa

I use it to streamline script development, but the relevant part for you is that it uses a credential object based on the action name to store the required information. It you duplicate the action and rename it then a new credential is created.

In your case you could duplicate the action and rename for each repo. In your case you could also define a name format for the action where the repo is included and then just pick it up by parsing the action.name property.

You’d still need to use a credential for the url key but, presumably, this would be common to all the actions

Dave

1 Like

I went from “I don’t think that will work…” to “ohhhh…” to “THAT’S BRILLIANT!” in just a few minutes, thanks!

I updated my script. There was another issue that drafts.title simply ignores the heading tag (#) in a markdown file. I’m not too sure if I like that. There’s also a problem with CallbackURL - if anything interferes with it, it hangs up the application/script. The URL step doesn’t do this, but I don’t think I can pass in conditional template tags like I can in code.


(() => {

	const settings = Credential.create("Working Copy Notes","Repo information and URL key for Working Copy notes");

	settings.addTextField("repo", "Repository");
	settings.addTextField("key","URL key");

	settings.authorize();

	const repoName = settings.getValue("repo");
	const urlAutomationKey = settings.getValue("key") || "";
	
	// Get the filename from the draft
	// This is probably not necessary, as Drafts invisibly removes the heading tag
	let fileName = draft.title.replace(/^#(\w?)/gi, '$1') + ".md";

	// Get all details of the repo files
	let cb = CallbackURL.create();

	cb.baseURL = "working-copy://x-callback-url/status/";
	cb.addParameter("repo", repoName);
	cb.addParameter("path", "/");
	cb.addParameter("unchanged", 1);

	let success = cb.open();

	if (success) {
		let repo = cb.callbackResponse;
		let filePath = "";

		// Look for all instances of this file name
		const files = findObjectsByKey(
				JSON.parse(repo.json), 
				"name",
 				fileName);

		if (files.length == 1 ) {
			filePath = dirName(files[0].path);
		
		} else if(files.length > 1) {
			const pr = Prompt.create();

			pr.title = "Select correct path";
			pr.message = "Multiple files with this filename located.  " + 				"Please choose the correct path as save destination";
		
			const paths = [];
		
			for (let i=0; i < files.length; i++) {
				paths.push(dirName(files[i].path));
			}

			pr.addPicker("chosenPath", "", [paths], null);
			pr.addButton("OK", 1);
		
			let didSelect = pr.show();
		
			if (didSelect) {
				filePath = paths[pr.fieldValues["chosenPath"]];
			}
		}
	
		// Write file to repo
		let cb2 = CallbackURL.create();
		cb2.baseURL = "working-copy://x-callback-url/write/";
		cb2.addParameter("key", urlAutomationKey);
		cb2.addParameter("repo", repoName);
		if (filePath === "" && files.length == 0) {
			// Nothing was found.  Working Copy should prompt you to Save As
			// path may be empty if the file exists at the root
			cb2.addParameter("no_path","empty");
			cb2.addParameter("filename", fileName);
		} else {
			cb2.addParameter("path", filePath + fileName);
		}
	
		cb2.addParameter("text", draft.content);

		success = cb2.open();
		if (!success) {
			alert("Something went wrong");
		}	
	}
})();

// Support functions

// Search the entire repo to find the filenames.  Could be multiple.
function findObjectsByKey(array, key, value) {

	var fileList = [];
	for (var i = 0; i < array.length; i++) {
		if (array[i][key] === value) {
			fileList.push(array[i]);
		}
	}
	return fileList;
}

// Get the path without the filename
function dirName(str) {
	var base = new String(str).substring(0, str.lastIndexOf('/') + 1); 
	return base;
}

Credentials are very handy.

I don’t know how Drafts deal with the .title property, but you should be able to get the full first line of the draft using a template. Something like:

firstLine = draft.processTemplate("[[line|1]]");

I haven’t had issues with the CallBackURL object, but I’ve never tried to use it with Working Copy. Hopefully someone else will have ideas on that.

Dace