Plugin: Webnotes

name

Webnotes

icon

public

instructions


Press Ctrl-O and choose "Publish a tag" to bulk-publish all notes in a tag.

Navigate to Note Options -> "Publish note and all of its children" to bulk-publish a note and all notes linked to from it, recursively.

You will always see a list of notes to be published before they are actually published. Use this list to check off any entries you'd rather not make public just yet:

Publishing options:

Type {embed} anywhere in a note to paste the embed code of a youtube video, google map, google spreadsheet/anything else publicly accessible that you can get an embed code for.

You can also simply paste plain HTML here and it will be rendered

Go to the plugin settings page over at amplenote.com/account/plugin -> Webnotes and type "yes" in the setting if you want new notes to be published automatically.

How this works: when you are in an already published note, link to a new note with @ (eg. @My new note) and when you press Enter to confirm, the note will be created by Amplenote. As long as you are doing this from a public note, Webnotes will detect that and it will publish that note automatically for you, such that users visiting your website will have access to it.

Release notes:

August 29th, 2026:

You can also paste plain HTML inside {embed} now

description

Bulk-publish an entire tag or a note and all of its references.

setting

Auto-publish new notes linked to from public notes


/***
* Source Code: undefined
* Author:
* Build: production
* Character Count: 22718 (0.023 M)
* Target Folder: plugin
***/
(() => {
// common-utils/embed-comunication.js
var COMMON_EMBED_COMMANDS = {
navigate: async (app, url) => {
if (await app.navigate(url)) return !0;
try {
return window.open(url, "_blank") != null;
} catch {
return !1;
}
},
prompt: async (app, ...args) => await app.prompt(...args),
alert: async (app, ...args) => await app.alert(...args),
getSettings: async (app) => app.settings,
setSetting: async (app, key, value) => app.setSetting(key, value),
getNoteTitleByUUID: async (app, noteUUID) => {
var _a;
return (_a = await app.notes.find(noteUUID)) == null ? void 0 : _a.name;
},
getNoteContentByUUID: async (app, noteUUID) => await app.notes.find(noteUUID) ? await app.getNoteContent({ uuid: noteUUID }) : null,
getNoteBacklinksByUUID: async (app, noteUUID) => {
var _a;
return await ((_a = await app.notes.find(noteUUID)) == null ? void 0 : _a.backlinks());
},
getNoteTagsByUUID: async (app, noteUUID) => {
var _a;
return (_a = await app.notes.find(noteUUID)) == null ? void 0 : _a.tags;
},
getNoteSections: async (app, ...args) => await app.getNoteSections(...args),
getNoteTasks: async (app, noteHandle, options = {}) => {
let finalOptions = { includeDone: !0, ...options };
return await app.getNoteTasks(noteHandle, finalOptions);
},
insertTask: async (app, ...args) => await app.insertTask(...args),
updateTask: async (app, ...args) => await app.updateTask(...args),
createNote: async (app, ...args) => await app.createNote(...args),
deleteNote: async (app, ...args) => await app.deleteNote(...args),
replaceNoteContent: async (app, ...args) => await app.replaceNoteContent(...args),
setNoteName: async (app, ...args) => await app.setNoteName(...args),
addNoteTag: async (app, ...args) => await app.addNoteTag(...args),
removeNoteTag: async (app, ...args) => await app.removeNoteTag(...args),
insertNoteContent: async (app, ...args) => await app.insertNoteContent(...args),
getTaskDomains: async (app) => await app.getTaskDomains(),
getTaskDomainTasks: async (app, ...args) => await app.getTaskDomainTasks(...args),
getTask: async (app, ...args) => await app.getTask(...args),
findNote: async (app, ...args) => await app.findNote(...args),
filterNotes: async (app, ...args) => await app.filterNotes(...args),
saveFile: async (app, ...args) => {
try {
let { name, data } = args[0];
return data.startsWith("data:") && (data = await (await fetch(data)).blob()), await app.saveFile(data, name);
} catch (e) {
throw e.message;
}
}
};
function createOnEmbedCallHandler(embedCommands = {}, logBlacklistedCommands = []) {
return async function(app, commandName, ...args) {
try {
if (commandName in embedCommands) {
let result = await embedCommands[commandName](app, ...args);
return logBlacklistedCommands.includes(commandName) || console.debug("onEmbedCall:", commandName, args, result), result;
}
} catch (e) {
throw app.alert("Error:", e.message || e), console.error("onEmbedCall:", commandName, args, e), e;
}
throw new Error(`Unknown command: ${commandName}`);
};
}
 
// plugin/plugin.js
var plugin = {
// Promise resolver for pause/resume functionality
resumeResolver: null,
appOption: {
"Publish a tag": async function(app) {
let [tag, optionConfirmEveryNote, optionPublishLinkedNotes, optionCreateHomepage] = await app.prompt(
"Choose the tag you want to publish to the web. Note that all notes with this tag will become public.",
{
inputs: [
{
type: "tags",
label: "Tag to publish"
},
{
type: "checkbox",
label: "Ask me to manually confirm each note before publishing"
},
{
type: "checkbox",
label: "Publish notes linked to from notes in this tag, even if they are not in this tag"
},
{
type: "checkbox",
label: "Also create a homepage/index page, containing a list of links to all of the published notes"
}
]
}
);
if (console.log("tag", tag), console.log("optionConfirmEveryNote", optionConfirmEveryNote), console.log("optionPublishLinkedNotes", optionPublishLinkedNotes), console.log("optionCreateHomepage", optionCreateHomepage), !tag) return;
let noteList = await app.filterNotes({ tag });
console.log("noteList", noteList);
let noteSources = /* @__PURE__ */ new Map();
for (let note of noteList)
noteSources.set(note.uuid, `#${tag}`);
if (optionPublishLinkedNotes) {
let linkedNotesWithSources = await this.getLinkedNotesFromManyNotes(app, noteList);
console.log("linkedNotes", linkedNotesWithSources);
for (let { note, source } of linkedNotesWithSources)
noteList.push(note), noteSources.set(note.uuid, `\u{1F4D2} Linked from ${source}`);
}
if (optionCreateHomepage) {
let homepageNote = await this.createHomepageNote(app, tag, noteList);
console.log("homepageNote", homepageNote), noteList.push(homepageNote), noteSources.set(homepageNote.uuid, "\u{1F3E0} Homepage");
}
let answer = await app.prompt("Let's double check the list of notes to be published. Uncheck any notes you don't want to publish.", {
// We show a checkbox for every note by iterating over the noteList array
inputs: noteList.map((note) => ({
type: "checkbox",
label: `${note.name} (${noteSources.get(note.uuid)})`,
value: !0
}))
});
if (console.log("answer", answer), noteList = noteList.filter((note, index) => answer[index]), console.log("noteList", noteList), optionCreateHomepage) {
let homepageNote = noteList.find((note) => noteSources.get(note.uuid) === "Homepage");
homepageNote && await this.insertHomepageContent(app, homepageNote, tag, noteList.filter((note) => noteSources.get(note.uuid) !== "Homepage"));
}
await this.publishNotes(app, noteList, optionConfirmEveryNote);
},
"Resume webnotes": async function(app) {
if (!this.resumeResolver) {
await app.alert("No publishing process to resume.");
return;
}
this.resumeResolver(), this.resumeResolver = null;
}
},
async getLinkedNotesFromManyNotes(app, noteList) {
console.log("getLinkedNotesFromManyNotes", noteList);
let allLinkedNotesWithSources = [], processedNotes = /* @__PURE__ */ new Set(), notesToProcess = [...noteList];
for (let note of noteList)
processedNotes.add(note.uuid);
for (; notesToProcess.length > 0; ) {
let currentNote = notesToProcess.shift();
console.log("Processing note for links:", currentNote.name);
let linkedNotes = await this.getLinkedNotesFromNote(app, currentNote);
for (let linkedNote of linkedNotes)
processedNotes.has(linkedNote.uuid) || (console.log("Found new linked note:", linkedNote.name), allLinkedNotesWithSources.push({
note: linkedNote,
source: currentNote.name
}), processedNotes.add(linkedNote.uuid), notesToProcess.push(linkedNote));
}
return allLinkedNotesWithSources;
},
async getLinkedNotesFromNote(app, note) {
console.log("getLinkedNotesFromNote", note);
let noteContent = await app.getNoteContent(note), linkRegex = /\[([^\]]*)\]\(https:\/\/www\.amplenote\.com\/notes\/([a-f0-9-]+)(?:#[^)]+)?\)/g, match, foundUUIDs = /* @__PURE__ */ new Set();
for (; (match = linkRegex.exec(noteContent)) !== null; ) {
let uuid = match[2];
console.log("uuid", uuid), foundUUIDs.add(uuid);
}
let linkedNotes = Array.from(foundUUIDs), linkedNotesObjects = [];
for (let uuid of linkedNotes) {
let note2 = await app.findNote({ uuid });
linkedNotesObjects.push(note2);
}
return linkedNotesObjects;
},
async createHomepageNote(app, tag, noteList) {
let homepageNote = await app.createNote(`${tag} Homepage`, [tag]);
return await app.findNote({ uuid: homepageNote });
},
async insertHomepageContent(app, homepageNote, tag, noteList) {
let homepageContent = `# ${tag} Homepage
 
`;
for (let note of noteList)
homepageContent += `- [${note.name}](https://www.amplenote.com/notes/${note.uuid})
`;
await app.insertNoteContent({ uuid: homepageNote.uuid }, homepageContent);
},
async publishNotes(app, noteList, optionConfirmEveryNote) {
let actionToTakeForAllRemainingNotes = null;
for (let note of noteList) {
console.log("note", note);
let action = actionToTakeForAllRemainingNotes;
for (; optionConfirmEveryNote && !actionToTakeForAllRemainingNotes; ) {
let noteDetails = await app.findNote(note), [optionDoTheSameForAllRemainingNotes, userAction] = await app.prompt(
`Do you want to publish ${noteDetails.name}?`,
{
inputs: [
{
type: "checkbox",
label: "Do the same for all remaining notes"
}
],
actions: [
{
icon: "check",
label: "Publish this note",
value: "publish"
},
{
icon: "clear",
label: "Skip this note",
value: "skip"
},
{
icon: "open_in_new",
label: 'Let me see it first. Ctrl-O, then type "Resume webnotes" to resume the process',
value: "open"
}
]
}
);
if (console.log("userAction", userAction), console.log("optionDoTheSameForAllRemainingNotes", optionDoTheSameForAllRemainingNotes), action = userAction, action === "open") {
console.log("navigating to note", noteDetails.name), await app.navigate(`https://www.amplenote.com/notes/${noteDetails.uuid}`), await this.waitForResume();
continue;
}
optionDoTheSameForAllRemainingNotes && (optionConfirmEveryNote = !1, actionToTakeForAllRemainingNotes = action);
break;
}
if (action === "publish" || !optionConfirmEveryNote && !actionToTakeForAllRemainingNotes)
console.log("publishing note", note), await app.publishNote(note);
else if (action === "skip") {
console.log("skipping note", note);
continue;
}
}
},
async waitForResume() {
return new Promise((resolve) => {
this.resumeResolver = resolve;
});
},
noteOption: {
"Publish this note and all its children": async function(app, noteUUID) {
let currentNote = await app.findNote({ uuid: noteUUID }), noteName = currentNote.name, [optionConfirmEveryNote, optionCreateHomepage] = await app.prompt(
`Publish "${noteName}" and all notes linked from it. All selected notes will become public.`,
{
inputs: [
{
type: "checkbox",
label: "Ask me to manually confirm each note before publishing"
},
{
type: "checkbox",
label: "Also create a homepage/index page, containing a list of links to all of the published notes"
}
]
}
);
console.log("currentNote", currentNote), console.log("optionConfirmEveryNote", optionConfirmEveryNote), console.log("optionCreateHomepage", optionCreateHomepage);
let noteList = [currentNote];
console.log("noteList", noteList);
let noteSources = /* @__PURE__ */ new Map();
noteSources.set(currentNote.uuid, "\u{1F4DD} Root note");
let linkedNotesWithSources = await this.getLinkedNotesFromManyNotes(app, noteList);
console.log("linkedNotes", linkedNotesWithSources);
for (let { note, source } of linkedNotesWithSources)
noteList.push(note), noteSources.set(note.uuid, `\u{1F4D2} Linked from ${source}`);
if (optionCreateHomepage) {
let homepageNote = await this.createHomepageNote(app, `"${noteName}" Collection`, noteList);
console.log("homepageNote", homepageNote), noteList.push(homepageNote), noteSources.set(homepageNote.uuid, "\u{1F3E0} Homepage");
}
let answer = await app.prompt("Let's double check the list of notes to be published. Uncheck any notes you don't want to publish.", {
// We show a checkbox for every note by iterating over the noteList array
inputs: noteList.map((note) => ({
type: "checkbox",
label: `${note.name} (${noteSources.get(note.uuid)})`,
value: !0
}))
});
if (console.log("answer", answer), noteList = noteList.filter((note, index) => answer[index]), console.log("noteList", noteList), optionCreateHomepage) {
let homepageNote = noteList.find((note) => noteSources.get(note.uuid) === "\u{1F3E0} Homepage");
homepageNote && await this.insertHomepageContent(app, homepageNote, `"${noteName}" Collection`, noteList.filter((note) => noteSources.get(note.uuid) !== "\u{1F3E0} Homepage"));
}
await this.publishNotes(app, noteList, optionConfirmEveryNote);
}
},
insertText: {
"Insert embed at current position": async function(app, noteUUID) {
let currentNote = await app.findNote({ uuid: noteUUID }), promptResults = await app.prompt("Insert the HTML or iframe you want to embed", {
inputs: [
{
type: "text",
label: "HTML or iframe"
},
{
type: "radio",
label: "Aspect ratio (optional)",
options: [
{ label: "Portrait (2:3)", value: "2:3" },
{ label: "Portrait (3:4)", value: "3:4" },
{ label: "Square (1:1)", value: "1:1" },
{ label: "Landscape (4:3)", value: "4:3" },
{ label: "Widescreen (16:9)", value: "16:9" }
]
}
]
});
if (console.log("promptResults", promptResults), !Array.isArray(promptResults)) return null;
let [results, requestedRatio] = promptResults;
if (typeof results != "string" || !results.trim()) return null;
let explicitAspectRatio = this.parseAspectRatio(requestedRatio);
if (requestedRatio != null && requestedRatio.trim() && !explicitAspectRatio)
return await app.alert('Enter the aspect ratio as width:height, for example "4:3".'), null;
let embedHTML = results.trim(), aspectRatio = explicitAspectRatio || this.getEmbedAspectRatio(embedHTML);
console.log("aspectRatio", aspectRatio);
let encodedResults = encodeURIComponent(embedHTML);
return await app.context.replaceSelection(`<object data="plugin://${app.context.pluginUUID}?${encodedResults}" data-aspect-ratio="${aspectRatio}"/>`), null;
}
},
validateSettings(app, settings) {
let autoPublish = settings["Auto-publish new notes linked to from public notes"];
if (autoPublish !== "Yes" && autoPublish !== "No" && autoPublish !== "yes" && autoPublish !== "no")
return ['Enter "yes" or "no" without the quotes in this field'];
},
async onNoteCreated(app, noteHandle) {
if (app.settings["Auto-publish new notes linked to from public notes"] !== "yes" && app.settings["Auto-publish new notes linked to from public notes"] !== "Yes")
return;
console.log("onNoteCreated", noteHandle), await new Promise((resolve) => setTimeout(resolve, 1e3));
let backlinks = await app.getNoteBacklinks(noteHandle);
console.log("backlinks", backlinks);
let foundPublicNote = !1;
for (let backlink of backlinks) {
let backlinkNote = await app.findNote({ uuid: backlink.uuid }), publicURL = await app.getNotePublicURL(backlinkNote);
if (console.log("publicURL", publicURL), publicURL) {
foundPublicNote = !0;
break;
}
}
foundPublicNote && (console.log(`Note ${noteHandle.name} is linked to from a public note, so it will be published to the web.`), this.publishNotes(app, [noteHandle], !1));
},
// onEmbedCall: createOnEmbedCallHandler(COMMON_EMBED_COMMANDS),
renderEmbed(app, embedHTML) {
let decodedHTML;
try {
decodedHTML = decodeURIComponent(embedHTML);
} catch {
decodedHTML = embedHTML;
}
return this.isStandaloneIframe(decodedHTML) && (decodedHTML = this.makeIframeResponsive(decodedHTML)), this.addPlainHTMLSupport(decodedHTML);
},
parseAspectRatio(ratio) {
if (typeof ratio != "string" || !ratio.trim()) return null;
let match = ratio.trim().match(/^(\d+(?:\.\d+)?)\s*[:/]\s*(\d+(?:\.\d+)?)$/);
if (!match) return null;
let width = parseFloat(match[1]), height = parseFloat(match[2]);
return width > 0 && height > 0 ? width / height : null;
},
getEmbedAspectRatio(embedHTML) {
var _a, _b;
let declaredRatio = embedHTML.match(/(?:^|[\s<])ratio=["'](\d+(?:\.\d+)?):(\d+(?:\.\d+)?)["']/i);
if (declaredRatio)
return parseFloat(declaredRatio[1]) / parseFloat(declaredRatio[2]);
if (this.isStandaloneIframe(embedHTML)) {
let iframeTag = (_a = embedHTML.match(/<iframe\b[^>]*>/i)) == null ? void 0 : _a[0];
return this.getElementAspectRatio(iframeTag) || 16 / 9;
}
if (this.isStandaloneSVG(embedHTML)) {
let svgTag = (_b = embedHTML.match(/<svg\b[^>]*>/i)) == null ? void 0 : _b[0];
return this.getElementAspectRatio(svgTag) || this.getSVGViewBoxAspectRatio(svgTag) || 1;
}
return 1;
},
getElementAspectRatio(elementTag) {
if (!elementTag) return null;
let width = elementTag.match(/(?:^|\s)width=["'](\d+(?:\.\d+)?)(?:px)?["']/i), height = elementTag.match(/(?:^|\s)height=["'](\d+(?:\.\d+)?)(?:px)?["']/i);
return !width || !height ? null : parseFloat(width[1]) / parseFloat(height[1]);
},
getSVGViewBoxAspectRatio(svgTag) {
if (!svgTag) return null;
let viewBox = svgTag.match(/(?:^|\s)viewBox=["'][\d.-]+\s+[\d.-]+\s+([\d.]+)\s+([\d.]+)["']/i);
if (!viewBox) return null;
let width = parseFloat(viewBox[1]), height = parseFloat(viewBox[2]);
return width > 0 && height > 0 ? width / height : null;
},
isStandaloneIframe(embedHTML) {
let trimmedHTML = embedHTML.trim();
return (trimmedHTML.match(/<iframe\b/gi) || []).length === 1 && /^<iframe\b[^>]*>[\s\S]*<\/iframe>$/i.test(trimmedHTML);
},
isStandaloneSVG(embedHTML) {
let trimmedHTML = embedHTML.trim();
return (trimmedHTML.match(/<svg\b/gi) || []).length === 1 && /^<svg\b[^>]*>[\s\S]*<\/svg>$/i.test(trimmedHTML);
},
addPlainHTMLSupport(embedHTML) {
let supportMarkup = `
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html, body {
max-width: 100%;
overflow: auto;
overscroll-behavior: contain;
}
</style>
`;
return /<head\b[^>]*>/i.test(embedHTML) ? embedHTML.replace(/<head\b[^>]*>/i, (match) => `${match}${supportMarkup}`) : /<html\b[^>]*>/i.test(embedHTML) ? embedHTML.replace(/<html\b[^>]*>/i, (match) => `${match}<head>${supportMarkup}</head>`) : `${supportMarkup}${embedHTML}`;
},
makeIframeResponsive(iframeHTML) {
let iframeMatch = iframeHTML.match(/<iframe[^>]*>/i);
if (!iframeMatch) return iframeHTML;
let iframe = iframeMatch[0], srcMatch = iframe.match(/src="([^"]+)"/i);
if (!srcMatch) return iframeHTML;
let src = srcMatch[1], paddingBottom = (1 / this.getAspectRatioForEmbed(src, iframe) * 100).toFixed(2), attributes = this.extractIframeAttributes(iframe);
return `
<div style="position: relative; width: 100%; height: 0; padding-bottom: ${paddingBottom}%;">
<iframe
src="${src}"
title="${attributes.title}"
frameborder="0"
${attributes.allow ? `allow="${attributes.allow}"` : ""}
${attributes.referrerpolicy ? `referrerpolicy="${attributes.referrerpolicy}"` : ""}
${attributes.allowfullscreen ? "allowfullscreen" : ""}
${attributes.loading ? `loading="${attributes.loading}"` : ""}
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; ${attributes.style || ""}"
${attributes.otherAttrs}>
</iframe>
</div>
`;
},
getAspectRatioForEmbed(src, iframe) {
let widthMatch = iframe.match(/width="(\d+)"/i), heightMatch = iframe.match(/height="(\d+)"/i);
if (widthMatch && heightMatch) {
let width = parseInt(widthMatch[1]), height = parseInt(heightMatch[1]);
return width / height;
}
return 16 / 9;
},
extractIframeAttributes(iframe) {
let attributes = {
title: this.extractAttribute(iframe, "title") || "Embedded content",
allow: this.extractAttribute(iframe, "allow"),
referrerpolicy: this.extractAttribute(iframe, "referrerpolicy"),
loading: this.extractAttribute(iframe, "loading"),
style: this.extractAttribute(iframe, "style"),
allowfullscreen: iframe.toLowerCase().includes("allowfullscreen"),
otherAttrs: ""
}, knownAttrs = ["src", "width", "height", "title", "allow", "referrerpolicy", "loading", "style", "frameborder", "allowfullscreen"], otherAttrs = (iframe.match(/(\w+)="[^"]*"/g) || []).filter((attr) => !knownAttrs.some((known) => attr.toLowerCase().startsWith(known.toLowerCase()))).join(" ");
return attributes.otherAttrs = otherAttrs, attributes;
},
extractAttribute(iframe, attrName) {
let match = iframe.match(new RegExp(`${attrName}="([^"]+)"`, "i"));
return match ? match[1] : null;
},
onEmbedCall: createOnEmbedCallHandler(COMMON_EMBED_COMMANDS)
}, plugin_default = plugin;
return plugin;
})()
//# sourceMappingURL=plugin.js.map