MojoPad

Scripting: API Reference

Every call the mojo API offers, what it takes, what it hands back, and when it refuses. That last column is the one worth reading. Most of these answer null or false rather than throwing, so a plugin that quietly does nothing is usually a call that quietly refused — and the reason is almost always one of three: the page is locked, the page is a file, or the page is a board that cannot hold loose text.

A ref is a page name or a page id. Names are matched the way links are, so aliases and different capitalisation work.

Reading

CallHands backRefuses when
mojo.pages()an array of {id, name, type, tags, aliases} for every pagenever
mojo.currentPage()the same shape, for the page you are looking atnull if no page is open
mojo.read(ref)the page's plain textnull if there is no such page, or it is locked
mojo.readHtml(ref)the page's stored HTMLnull, same reasons
mojo.exists(ref)true if a page answers to this namenever
mojo.getMeta(ref, key)one page-meta valuenull if the page or key is absent. Throws if you pass only one argument

Writing

CallHands backRefuses when
mojo.write(ref, text)true if writtenfalse for a missing or locked page, a file page, a canvas, or a card board
mojo.append(ref, text, opts?)true if appendedfalse for a missing or locked page, a file page, or a canvas. Throws if you pass only one argument
mojo.insertAtCursor(text, opts?)true if insertedfalse if no page is open, or it is a file page
await mojo.create(name, text?)the new page's id — or the existing page's id if the name is takennever; it does not overwrite
mojo.setMeta(ref, key, value)true if writtenfalse for a missing or locked page

opts is { format: 'text' } unless you say otherwise. Pass { format: 'html' } to add real markup — a table, a list, a link — rather than the characters that would have written one. Either way the markup is scrubbed the same as anything else that reaches a page.

Reading from the web

CallHands backRefuses when
await mojo.fetch(url){status, headers, body}, with body as a stringstatus: 0 and an error in words: not https, refused by you, a private address, too large, or too slow

The first time a plugin asks for a site, MojoPad asks you — naming the site and the document — and remembers your answer for that document. You are not asked twice for the same site, and a plugin cannot reach anywhere you have not agreed to.

const r = await mojo.fetch('https://api.example.com/thing.json')
if (r.status !== 200) { await mojo.alert(r.error || `Got ${r.status}`); return }
const data = JSON.parse(r.body)

Only https, and only public addresses. A plugin is code you are running against your own notes; a plaintext request would put the whole exchange in front of anyone in between, and an address on your own network is not the web. Redirects are followed, but each hop is checked again — a site you allowed cannot hand you to one you did not.

Responses are capped and requests time out, so a plugin cannot pull something endless into memory or hang while you wait.

Everything else

CallDoes
mojo.navigate(ref)opens a page in this window. Returns nothing, and does nothing if the page is not found
await mojo.alert(message)shows a message and waits for it to be dismissed
await mojo.prompt(label, value?)asks for a line of text. null if cancelled
mojo.today()today's date as a string, in your date format
mojo.log(…)collects output, shown when the run finishes
mojo.eventpresent only on an Event: page — what triggered the run

Seeing what went wrong

The Scripts palette keeps the last few runs — from the runner, from the Plugin menu and from Event pages alike. Each one says what ran, when, what it printed, and what failed. Nothing else in MojoPad writes there, so everything in it came from your own code.

A failure that happened while running names the line it happened on. A script that would not parse has no line to name — the failure is found before any of it runs — and MojoPad says so rather than pointing at a line whose code is fine.

If your Mac has curled your quotes

macOS replaces straight quotes with typographic ones as you type" becomes and — which is right for writing and is not valid code. Code that looks perfect stops parsing, and the error names a line with nothing wrong on it.

MojoPad recovers: if a script does not parse as written, it tries again with the quotes straightened, and says so in the output when that is what fixed it. A script that already parses is run exactly as you wrote it, curled quotes and all — a typographic quote inside a string is your output, not a mistake.

To stop it happening at all: Edit ▸ Substitutions ▸ Smart Quotes. That is your Mac's setting, not MojoPad's, and it applies wherever you are typing.

Two calls throw rather than refuse

Both used to fail silently, and both were reported by somebody who lost an afternoon to them. mojo.append(text) with one argument resolved your CONTENT as a page name, found no page called that, and returned false. mojo.getMeta(key) with one argument failed inside on a key that was never passed. They now throw, and the message names the shape they wanted.

What a script cannot do

The mojo API reaches your document and nothing else. There is no call here that reads a file, changes a setting, or opens a connection — everything above works on pages in the wiki that is open.

Scripts run only in a document you have said may run them, and only from pages you wrote yourself. See Nothing runs until you say so under Scripting: Plugins.

A worked example

A plugin that adds a formatted summary to the page you are looking at:

const here = mojo.currentPage()
if (!here) { await mojo.alert('Open a page first.'); return }
const words = (mojo.read(here.id) || '').split(/\s+/).length
const ok = mojo.append(here.id,
  `<p><b>${words}</b> words, counted ${mojo.today()}.</p>`,
  { format: 'html' })
if (!ok) await mojo.alert('That page will not take an append — is it locked?')

Note the last line. Checking what a call handed back is the difference between a plugin that works and a plugin that appears to.