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.
| Call | Hands back | Refuses when |
|---|---|---|
mojo.pages() | an array of {id, name, type, tags, aliases} for every page | never |
mojo.currentPage() | the same shape, for the page you are looking at | null if no page is open |
mojo.read(ref) | the page's plain text | null if there is no such page, or it is locked |
mojo.readHtml(ref) | the page's stored HTML | null, same reasons |
mojo.exists(ref) | true if a page answers to this name | never |
mojo.getMeta(ref, key) | one page-meta value | null if the page or key is absent. Throws if you pass only one argument |
| Call | Hands back | Refuses when |
|---|---|---|
mojo.write(ref, text) | true if written | false for a missing or locked page, a file page, a canvas, or a card board |
mojo.append(ref, text, opts?) | true if appended | false 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 inserted | false 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 taken | never; it does not overwrite |
mojo.setMeta(ref, key, value) | true if written | false 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.
| Call | Hands back | Refuses when |
|---|---|---|
await mojo.fetch(url) | {status, headers, body}, with body as a string | status: 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.
| Call | Does |
|---|---|
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.event | present only on an Event: page — what triggered the run |
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.
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.
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.
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 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.