Action to format a paragraph

Are there any actions that format a paragraph? I mean without having to select the paragraph. Selection can be tedious, and tricky, with long paragraphs on a phone.

The action would place the cursor anywhere within a paragraph and run the action to wrap it in asterisks or underscores.

I think this is what you are after. Hopefully I’ve added enough comments in that you can follow along and tailor it if that’s what you need.

function selectParagraph()
{
	//Define what the delimiter is for a paragraph
	//Note that the start and end of a draft are implicitly
	// catered for as delimiters of a paragraph
	const PARA_DELIM = "\n\n";

	//Get the current cursor position
	let [intPos, intSelLen] = editor.getSelectedRange();

	//Everything before the cursor position
	let strBefore = draft.content.substring(0, intPos);

	//Everything after the cursor position
	let strAfter = draft.content.substring(intPos, draft.content.length);

	//Find the start of the paragraph
	let intParaStart = strBefore.lastIndexOf(PARA_DELIM);
	if (intParaStart == -1) intParaStart = 0;
	else intParaStart = intParaStart + PARA_DELIM.length;

	//Find the end of the paragraph
	let intParaEnd = strAfter.indexOf(PARA_DELIM);
	if (intParaEnd == -1) intParaEnd = strAfter.length;
	intParaEnd = intPos + intParaEnd;

	//Set the selection
	editor.setSelectedRange(intParaStart, intParaEnd - intParaStart);
}

function wrapParagraph(p_strDelimit)
{
	//Get the current cursor position
	let [intPos, intSelLen] = editor.getSelectedRange();
	
	//Select the entire paragraph
	selectParagraph();
	
	//Replace the selected text with the wrapped text
	editor.setSelectedText(p_strDelimit + editor.getSelectedText() + p_strDelimit);
	
	//Set the cursor back in position
	editor.setSelectedRange(intPos + p_strDelimit.length, intSelLen);
}

wrapParagraph("**");
1 Like

Many Thanks, the action works great :clap: :grinning:.

One addition would be good.
After the action has run, the caret is in front of the final asterisks. You have to move the cursor before you can continue typing, which is tricky on mobile.

How do you place the cursor/caret on a new line?

That all depends on which newline you are on about and if it even exists. If your final paragraph ends at the end of the draft with no newline after it, then you can’t place it on the next “new line” :wink:

But if you have a look in the last part of the wrapParagraph function, that is all about placing the cursor. At the moment it places it in the original position of where you started when you triggered the action - which is what seems logical to me. If you wish to change the final position you probably want to consider changing the first parameter to the number of the character position-wise in the content where you want the cursor to be placed. The second parameter probably wants to be zero. Hopefully you can follow the script enough to understand why. If not, please look up the setSelectedRange function in the editor object documentation.

Now if you want to say place it immediately after the final paragraph wrapping delimiter you could run the selectParagraph function after the last line that runs the wrapParagraph function. Then get the length and selection (as done in both functions), add the two together and that is your new position. If you wanted to place it on the next line add the length of a newline character (1) to the position and it’ll move the cursor that many additional places along.

Now there are more efficient ways to do this, but rather than rewrite the whole thing to accommodate this additional requirement, which you are of course at liberty to do (hint: we don’t have to do the selectParagraph twice to know where the next new line is), given the blazing speed of Drafts on most i*OS devices, I think adding the two or so lines of extra code to do this is probably the easiest way to follow.

So to summarise, after the current line that triggers everything, add a line that selects the paragraph, then add a line that sets the cursor position to the end of the current selection plus however many additional characters you require.

Okay, I’ll have a go. I think I understand what you mean.

Edit: The code below works erratically. I cannot find a pattern of why it works sometimes and other times not. The problem is that the action seems to stop after selecting the paragraph and does not continue to move the cursor.

I added the code below, but it’s not actually setting the cursor. It leaves the paragraph selected. I set the length to 0 because nothing needs selecting , just the cursor placed at the end of the paragraph

wrapParagraph("**");

selectParagraph();

// Get the current cursor position
let [intPos, intSelLen] = editor.getSelectedRange();

editor.setSelectedRange(intPos + intSelLen, 0);

When I use exactly those two lines of code it places the cursor after the final asterisk of the paragraph it just wrapped.

Here’s a video. It wouldn’t play for me embedded. I had to link to it externally on Dropbox.

Only the second attempt works as intended. The first and third stop after selection, without placing the cursor.

@sylumer, do you think this is something in the script, or is it Drafts related? I have force quit and restarted Drafts and also created new draft documents. Results are always unpredictable.

I had one draft where all four or five tests were successful, then in the next attempt all failed. It made no difference where I placed the cursor, whether the text was at the top or bottom, with no line above or below respectively, or in the middle.

video link

It’s working each time for me for my own tests.

The same script should do exactly the same thing if it is employed in the same way each time.

Try repeating things absolutely exactly and see if that reveals any potential patterns which we can then check further.

The way I would do this is with a standard draft. Duplicate the draft each time for each test and test in the same place in the same way each time to see if things really are varying if all variables are kept the same.

Well, I can use the same draft, but I have tested on three devices. I wonder whether it is some kind of a timing issue?

Selecting the paragraph is a function, and JavaScript doesn’t wait for that routine to finish. It continues immediately with the next statement. I’ve witnessed problems with JavaScript like this before. There are probably ways to deal with such timing issues, but my JavaScript knowledge is not that great.

I suppose it means waiting for waiting for the function to finish, before continuing.

Up to now I’ve not seen any JavaScript timing issues in Drafts. Also I’ve not seen the effect in my own testing.

From your own testing you seem to have identified that the functions are triggering out of sync. There’s no JavaScript promises involved and I had thought it was single threaded; but if it really is that the commands are being expressed as functions rather than as a single sequence of instructions, the logical solution would then be to consolidate the instructions.

Just removing some of the interposing lines and a slight reordering to reflect the original ordering in he functions approach gives me this. Given that I couldn’t reproduce the issue, I can’t tell if this resolves the issue you’re experiencing, but if it really is as you suggest, this should fix it.

const PARA_DELIM = "\n\n";
const WRAP_DELIM = "**";
let [intPos, intSelLen] = editor.getSelectedRange();
let strBefore = draft.content.substring(0, intPos);
let strAfter = draft.content.substring(intPos, draft.content.length);
let intParaStart = strBefore.lastIndexOf(PARA_DELIM);
if (intParaStart == -1) intParaStart = 0;
else intParaStart = intParaStart + PARA_DELIM.length;
let intParaEnd = strAfter.indexOf(PARA_DELIM);
if (intParaEnd == -1) intParaEnd = strAfter.length;
intParaEnd = intPos + intParaEnd;
editor.setSelectedRange(intParaStart, intParaEnd - intParaStart);
editor.setSelectedText(WRAP_DELIM + editor.getSelectedText() + WRAP_DELIM);
editor.setSelectedRange(intParaEnd + (2 * WRAP_DELIM.length), 0);

There is also small efficiency change at the end that is easy to add at this point as we aren’t aiming for any flexibility or the modification of functions; things that could have been done as an alternative previously to the selection approach I suggested.

1 Like

@sylumer, Many Thanks.

That appears to fix the issue so far. I have the old action and the new one on the keyboard and tried them both with the same text. The new action works every time, and the old one fails every time, stopping at selection.

I will test it more, but so far the new action has not failed once, while the old action failed about 98% of the time.

If all the steps in the new action are equivalent, then it does appear to have been a timing issue with the select paragraph function.