Useful javascript snippets for the editor

Since there are a number of add-ons that makes it possible to run your own JavaScript in the editor through a shortcut or button press I thought it might be an idea to have somewhere to share useful JavaScript snippets. Some add-ons that allow for this are Custom editor keymap, Custom Styles and Wrapper meta-addon. I’ve never tried to last two but if I’ve understood things correctly it should be possible to use them for this purpose. There might be additional add-ons that can be used for this purpose as well.

My personal use case is for small repetitive actions that simply don’t merit developing a fully fledged add-on (i.e. ensuring edge-case functionality, proofing against user error documentation etc.). So feel free to share useful stuff you have, even if it is a quick hack without documentation or configurability.

2 Likes

I find myself creating a lot of tables with clozes in them, the following is a really rough parser for markdown tables (it really only parses | and newline) that creates an HTML table HTML from selection, i.e. retains images, HTML bold etc. It also inserts clozes in all cells except the first column using the next available cloze number. Do some logic changes to avoid inserting clozes on the first row PRN. Change styles PRN.

var sel = document.activeElement.shadowRoot.getSelection();
if (rng = sel.getRangeAt(0)) {
	var ord = 1;
	var html = document.activeElement.shadowRoot.activeElement.innerHTML;
	var ahtml = [...html.matchAll(/{{c(\d+)::.*?}}/gs)];
	if (ahtml.length >= 1) {
		var max = ahtml.reduce(function(prev, curr) { return prev[1] > curr[1] ? prev : curr });
		ord = parseInt(max[1]) + 1;
	}
	var outrows = [];
	const table = '<table class="markdown-add" style="width: 100%; border-collapse: collapse;">';
	const tr = '<tr class="markdown-add">';
	const td = '<td class="markdown-add" style="border: 1px solid black; padding: 0px 5px 0px 5px;">';
	var input = new XMLSerializer().serializeToString(rng.extractContents());
	input = input.replace(/<br.*?>/gs, "<br>");
	input = input.replace(/\sxmlns=".*?"/gs, "");
	var inrows = input.split("<br>");
	inrows.forEach(function (row) {
		var outcells = [];
		var incells = row.split("|");
		outcells.push(incells.shift());
		incells.forEach(cell => { outcells.push("{{c" + ord + "::" + cell.trim() + "}}"); });
		outrows.push(tr + td + outcells.join('</td>' + td) + '</td></tr>');
	});
	rng.insertNode(rng.createContextualFragment(table + outrows.join("") + '</table>'));
	sel.removeAllRanges();
	sel.addRange(rng);
}

The following strips out div, span and p tags without attributes but keeping the content. Can be useful as some external editors per default use the “old” Anki behaviour of inserting div tags instead of br. Modify which tags to include and logic for flattening or not PRN.

var els = document.activeElement.shadowRoot.querySelectorAll("div, p, span");
els.forEach(el => {
	if (!el.hasAttributes()) {
		var parent = el.parentNode;
		while (el.firstChild) parent.insertBefore(el.firstChild, el);
		parent.removeChild(el);
	}
});

Editor JS snippets - AnkiWeb also allow for adding/executing JS in the editor

I find myself creating a lot of tables with clozes in them, the following is a really rough parser for markdown tables (it really only parses | and newline) that creates an HTML table HTML from selection, i.e. retains images, HTML bold etc. It also inserts clozes in all cells except the first column using the next available cloze number. Change styles PRN.

New version:

  • Set HEADER to false prevent creating a header row from the first line
  • Set CLOZEFIRST to false before calling to prevent clozing the first column (in current config assumes {{cXX::!Something will lead to cloze being in shown state when inactive)
  • Set INCREASE to false to prevent incrementing clozes

{var sel = document.activeElement.shadowRoot.getSelection();
if (rng = sel.getRangeAt(0)) {
var _clozefirst = true;
var _header = true;
var _increase = true;
if(typeof CLOZEFIRST === ‘undefined’ || !CLOZEFIRST) { _clozefirst = false; }
if(typeof HEADER === ‘undefined’ || !HEADER) { _header = false; }
if(typeof INCREASE === ‘undefined’ || !INCREASE) { _increase = false; }
var ord_fst = 1;
var html = document.activeElement.shadowRoot.activeElement.innerHTML;
var ahtml = […html.matchAll(new RegExp(/{{c(\d+)::.?}}/gs))];
if (ahtml.length >= 1) {
var max = ahtml.reduce(function(prev, curr) { return parseInt(prev[1]) > parseInt(curr[1]) ? prev : curr });
ord_fst = parseInt(max[1]) + 1;
}
var ord = ord_fst;
if (_clozefirst) { ord++; }
var outrows = [];
const TABLE = ‘<table class=“markdown-add” style=“width: 100%; border-collapse: collapse;”>’;
const HTR = TR = ‘’;
const HTD = ‘’;
const TD = ‘’;
var input = new XMLSerializer().serializeToString(rng.extractContents());
input = input.replace(/<br.
?>/gs, “
”);
input = input.replace(/\sxmlns=".*?"/gs, “”);
var inrows = input.split("
")
inrows.forEach(function (row) {
var outcells = [];
var incells = row.split("|");
if(outrows.length || !_header) {
fst_col = incells.shift();
if(_clozefirst) { fst_col = “{{c” + String(ord_fst) + “::!” + fst_col.trim() + “}}”; }
outcells.push(fst_col);
incells.forEach(cell => { outcells.push("{{c" + String(ord) + “::” + cell.trim() + “}}”); });
outrows.push(TR + TD + outcells.join(’’ + TD) + ‘’);
if (_increase) { ord++; }
}
else outrows.push(HTR + HTD + incells.join(’’ + HTD) + ‘’);
});
rng.insertNode(rng.createContextualFragment(TABLE + outrows.join("") + ‘</table>’));
sel.removeAllRanges();
sel.addRange(rng);
}}

Upper and lower case selection.

Uppercase
{var sel = document.activeElement.shadowRoot.getSelection(); for (i = 0; i < sel.rangeCount; i++) { var rng = sel.getRangeAt(i); var input = new XMLSerializer().serializeToString(rng.extractContents()); input = input.toUpperCase(); rng.insertNode(rng.createContextualFragment(input)); sel.addRange(rng);}}

Lowercase
{var sel = document.activeElement.shadowRoot.getSelection(); for (i = 0; i < sel.rangeCount; i++) { var rng = sel.getRangeAt(i); var input = new XMLSerializer().serializeToString(rng.extractContents()); input = input.toLowerCase(); rng.insertNode(rng.createContextualFragment(input)); sel.addRange(rng);}}

Remove all table row heights in selection
{if (tbl = document.activeElement.shadowRoot.getSelection().anchorNode.parentElement.closest(“table”)) { tbl.style.removeProperty(“height”); els = tbl.querySelectorAll(“thead, tbody, tfoot, tr, td, th”); els.forEach(el => { el.style.removeProperty(“height”); });}}

“Sort” the clozes, i.e. rename them so that they are in ascending order in the note. Bear in mind that if you run this on a non-new note it will mess up your learning as it will not move the scheduling information.
{
const html = document.activeElement.shadowRoot.activeElement.innerHTML;
const ahtml = […html.matchAll(new RegExp(/{{c(\d+)::.*?}}/gs))];
var ord = 1;
var ord_map = new Map();
for (el of ahtml) { if (!ord_map.has(parseInt(el[1]))) { ord_map.set(parseInt(el[1]), ord++); } };
document.activeElement.shadowRoot.activeElement.innerHTML = html.replace(/{{c(\d+)::/gs, function(match, p1) { return “{{c” + ord_map.get(parseInt(p1)) + “::”; });
}

function wrap(lhs, rhs) {
  let root = document.activeElement.shadowRoot
  let sel = root.getSelection()
  let range = sel.getRangeAt(0)

  let input = new XMLSerializer().serializeToString(range.extractContents())
  newInput = lhs + input + rhs
  range.insertNode(range.createContextualFragment(newInput))

  sel.removeAllRanges()
  sel.addRange(range)

  rangeOffset = sel.getRangeAt(0).endOffset
  sel.collapse(sel.focusNode, rangeOffset - rhs.length)
}

Function to wrap currently selected text or none with lhs and rhs, an example is more illustrative.

Say that we use the function with:

lhs = String.raw`\(`
rhs = String.raw`\)`
wrap(lhs, rhs)

And our current text in the editor is, where | will indicate cursor position and <...> selection

foo bar |

after we execute the function the text becomes:

foo bar \(|\)

If we then select the text all the text

<foo bar \(\)>|

and execute again it becomes:

\(<foo bar \(\)>|\)

As you can see the cursor always jumps to just before rhs.

Small snippet to color text just as you would using anki itself:

function setColor(color) {
  let colors = { "black": "#000000", "green": "#00de34" }
  document.execCommand('forecolor', false, colors[color])
}

The only important part really is the third line, which you can give any html color string you want (the same as is listed in the anki color picker).

Hello, could you help me to create an script to use an ordered or unordered list?

I’m pretty noobish… :frowning:

1 Like

Hey did you find a solution for your problem? I’m currently also looking for shortcut options for lists – would love it…

In anki 2.1.50 already has a shortcut

1 Like

Is there a JS code for determining selected text size? For example 16px?

4 Likes

Thanks but I’m looking for something else.
I want the selected text size to change (not get the current size).
Thanks.
(I tried injecting a tag using the extension 1899278645 but it deletes any other formatting that is on the text. For example color)

I’m afraid I don’t understand.

Do you mean the size that’s set for the current editor field?

1 Like

I’m looking for something like this:
https://www.geeksforgeeks.org/allow-users-to-change-font-size-of-a-webpage-using-javascript/#:~:text=Make%20a%20JavaScript%20function%20to,size%20attribute%20from%20javascript%20itself
I’m trying to develop a addon that will resize some of the text in the field similar to what can be done in Word and similar software.
Thanks.

Then the code I posted above should actually be of use to you. It allows you to get information about the currently selected node, as well as edit its attributes.

2 Likes

As I am not a coder I just want to share an idea. Since Remove Cloze Button and Hotkey hasn’t been updated since 2019-12-22 it would be awesome to have some java script to do it. Some regex can be used to achieve this:
(\{\{c\d::|\}\}) - it works in Find and replace option - but would be great to have java script and keyboard shortcut or button to execute it when a piece of text is highlighted.