Replace entire content of Drafts from 1Writert to Drafts

So I integrate Drafts with quite a few applications, and am comfortable with x-callback, however, I’m struggling to understand how to (best) replace the entire contents of a specific Draft.

I use 1Writer and, while 99% of the content is created in Drafts, every now and then I will make an update to a note in 1Writer, and thus want to keep it in synch with the ‘source of truth’, that being the specific Draft which is used to keep the note updated.

While I can easily /replace going TO 1Writer (and many other applications), how does one /replace coming IN to Drafts?

I see the /replaceRange param but do I need to first derive the character count of the 1Writer note populate the length argument or is there a simpler way? I’ve searched but not finding much.

Thanks in advance!

You could go via Shortcuts and use the “Replace” option on the “Update Draft” shortcut action. As long as you can reliably identify the draft.

If that were an issue, you could consider passing the draft UUID to the external editor, along with the draft content on the way out of Drafts.

Another way might be to use an x-callback-url to run an action in Drafts which replaces the content of a specified draft with the content it is passed directly, or via the clipboard.

Thanks for the response:

This is exactly what I want to do…however without explicitly defining the end character range, I’m unsure how to accomplish this,
It’s really just a matter of keeping an artifact in sync between two applications. If I make an change within application, A, I’d like the ability to (easily) update the artifact in the application B (and vice-versa).

Ironically, I can do this in every application I’ve tried, with the exception of Drafts. So I have a feeling I’m missing something here.

So…I would prefer to do this either via x-callback or Javascript, as can easily kick off that code from within `1Writer.

To that end, I guess more so, I’m intrigued why a /Replace param does not exist, or at least some logic within the /ReplaceRange argument (such as '*'does not exist…as I’m hard pressed to find a URL scheme that does not support the ability to replace the contents of an artifact, yet the app that pretty much created this protocol does not posses it. Surely somebody is updating the entire contents of a Draft and curious how they are doing this, I’m still new to Drafts (but not X-callbackl)…

1 Like

Your title and initial post suggest you are replacing all of the draft’s content. Are you wanting to just replace some sort of excerpt instead? Otherwise, why would you need a range? The range would simply be everything, the whole contents. Wouldn’t it?

If you are replacing a range, then I think you would have to grab the text range before you start editing it or replace it at that point in Drafts with some sort of marker token.

In action terms, you can get the content of a draft directly as it is a property. Then you could replace by manipulating a substring of the content and setting the content back to it.

Either/or. Remember, my intent here is simply to keep two artifacts in synch. I just think replace(all) is easiest (as you alluded).


YES!!! Exactly. However, if you read my original post, I asked how to do this.
When I look at the Drafts URL scheme, I am ONLY seeing the replaceRange parameter.

So, my question(s):

  1. Is there a /Replace* (that replaces all of the content) that I have missed?
  2. If ‘no’ to #1, is there any logic in the /ReplaceRange arguments to replace ALL of the content?

I’m trying to eliminate points of failure here. I know some ways I can do this, but would rather insert a single action to do this.


To that end, (not to throw a wrench in to this) but if there IS a way to (easily) send the deltas and to replace I can do this to. It just seems replace all is the most streamlined.

Thanks again!

In my first response I suggested utilising Shortcuts as a conduit. Shortcuts has a URL scheme and also tight integration with Drafts via the Drafts actions Greg developed; specifically the aforementioned “Update Draft” action. 1Writer also references some level of Shortcuts integration on it’s web site home page, so URL schemes may not even be required at all depending upon how you would wish to trigger it.

If you are particularly set on or simply most comfortable with utilising a pure direct URL scheme solution, then the replaceRange parameter could certainly be utilised. For replacing all, you would always want to replace from 0, so the first range parameter is easy. The end of the draft you could set as something arbitrarily high to always catch the end (e.g. Number.MAX_SAFE_INTEGER from JavaScript which Drafts can deal with; Drafts returns 9007199254740991 for this figure, given they both likely utilise the same core engine, I suspect the same is also true of 1Writer), or you could use the get URL scheme parameter for a double URL scheme use to first retrieve the original content from Drafts, from which you could count the length in 1Writer utilising JavaScript, and then build in the end point of the range from that for the second URL call to do the replace.

Above I’ve covered the all content situation. Dealing with subsets comes back to the point in my second response where I queried if this was in fact your intent.

That would still require some sort of marker or set of unique delimiters you could replace against or you would need to keep a separate store accessible to 1Writer that could track the original range. Note that if you use a range rather than a marker or delimiter, you are practically restricted to only having one artifact to work on at any time for a particular draft as when a replacement takes place this could affect the position of other artifacts, and unless you were also going to recalculate and refresh the stored data each time, it has the potential for data loss and corruption of the content.

If you are going to deal with transitioning sections from within a single draft then you have to consider what your preferred approach is and there would be a more involved solution involving some level of tracking within the drafts.

Hopefully that all makes sense and gives you some options on ways to proceed.

Perfecto. Many thanks for taking the time to assist. :+1:
This is what I was thinking to do (hoping it wouldn’t choke on a value higher than the character count). I still like the concept of replacing a range and imagine I’ll leverage it at some point.

I’ll also check out many of the other wonderful suggestions specific to Shortcuts and the likes. FWIW, I’m fairly new to the IOS platform as I’m card-carrying IBM/Windows guy since the late 80’s (when I started working in software development using 2.1x))…so I have little experience with Apple products other than having a “Mac” in college.

Most of my actions have been internal inner-app (lots of javascript actions for text manipulation). I’m writing my own little app for the IOS as a way to learn more about the stack, I do have other questions regarding Drafts but will create separate posts. Thank you again.

Honestly, no one has asked for a /set type URL scheme, and on the whole I don’t think it’s a great idea for URLs to perform destructive actions because they are not secure.

As noted, there are Shortcuts actions to perform updates.

Or, as suggested by @sylumer, you can setup your own method to do this using the /runAction URL scheme to call an action in Drafts you have configured to update a specific draft via script. Say you had an action name “Update-Draft” with a script like:

let d = Draft.find("UUID-OF-DRAFT");
d.content = draft.content;
d.update();

You could then call the URL:

drafts5://runAction?text=MY-NEW-TEXT&action=Update-Draft

Not sure what your exact workflow is, but I might also suggest not destructively overwriting a single draft with these changes, but create new drafts with specific tags for each new version and use the most recent of them as your canonical reference to the current state.

Hi, I made an attempt at implementing a workflow to edit drafts in an external editor and uploaded it to the directory if anyone wants to try it. There are two options, the simplest and probably best one is this action which opens a new instance of your choice of editor and waits for it to close before the action finishes by writing back the edited draft. The second option is more involved and uses a Shortcuts script to sync changes back to Drafts.

Proper documentation is in this GitHub README.

2 Likes

This is super cool! Using it to open TaskPaper files in TaskPaper.app.

Thank you very much for sharing :slight_smile:

1 Like

Could you please share…? Thanks

Below is the modified script that I am running to open a Draft in an external editor (taskpaper format in TaskPaper.app). As long as the draft is opened in TaskPaper.app, Drafts.app waits for you to save the text in TaskPaper and come back to Drafts app, so that it is sent back to the original draft.

let EDITOR = '';
let fileEnding = '';

// ---- CONFIGURATION ----
// Set EDITOR to the application (note: not shell command) you want to use.
// Below are some examples.

// TextEdit
// EDITOR = 'TextEdit'

// Visual Studio Code
// EDITOR = 'Visual Studio Code'

// Neovim Qt
// EDITOR = 'nvim-qt'

// TaskPaper
// EDITOR = 'TaskPaper'

// -----------------------

function bash(script) {
    let sh = ShellScript.create(`#!/usr/bin/env bash\n${script}`);
    if (!sh.execute()) {
        throw new Error(sh.standardError);
    }
}

(function() {
    let bookmark = Bookmark.findOrCreate("proxy-files");
    let fm = FileManager.createForBookmark(bookmark);

    if (draft.syntax.name.includes("TaskPaper")) {
        fileEnding = "taskpaper";
        EDITOR = 'TaskPaper';
    } else if (draft.syntax.name.includes("Markdown")) {
        fileEnding = "md";
        EDITOR = 'Sublime Text';
		//EDITOR = 'BBEdit';
    }
    let fileInBookmarks = `/${draft.uuid}.${fileEnding}`;
    let file = `${fm.basePath}${fileInBookmarks}`;  

    if (!fm.writeString(fileInBookmarks, draft.content)) {
        alert("Error: Couldn't write file");
        return;
    }

    try {
        bash(`open -n -W -a "${EDITOR}" ${file}`);
        alert("Writing back to Draft.")
        draft.content = fm.readString(`${fileInBookmarks}`);
        draft.update();
        bash(`rm ${file}`);
    } catch (e) {
        alert(e);
        return;
    }
})();