Import Markdown Plugin

name

Import Markdown

icon

upload_file

description

Import a markdown file from your computer into any note — at the end, at the beginning, or replacing the note's content.

instructions

Import a markdown (.md) file straight into a note, without copying and pasting its contents.

linkHow to use it

Open the note you want to import into.

Open the note's options menu (the ⋯ menu) and choose Import Markdown.

In the dialog, click the dashed box to choose a file, or drag a file from your computer and drop it onto the box. The box turns green and shows the file's name and length once it's ready.

Choose where the imported content should go:

At the end of the note (the default)

At the beginning of the note

Replace the note's content — everything currently in the note is replaced by the file's content.

Click Import.

The plugin remembers the placement you chose last, so the next import defaults to it.

linkSupported files

Files ending in .md, .markdown, .mdown, .mkd or .txt can be picked. Before importing, the plugin:

Removes YAML front matter (the ----fenced block of key: value lines at the top of files exported from Obsidian, Jekyll, Hugo and similar apps), which would otherwise show up as a horizontal rule followed by a paragraph of settings.

Normalizes Windows and classic Mac line endings, and strips a leading byte-order mark.

Trims blank lines from the start and end of the file.

Images referenced by the file aren't uploaded: links to images hosted online keep working, but images referenced by a path on your computer (e.g. ![](images/photo.png)) won't display.

linkLimits

One file per import. If you drop several files, only the first is used.

100,000 characters. Amplenote rejects longer content in a single insert; the plugin tells you the file's length when it's over. Split larger files and import them one at a time.

linkIf something goes wrong

"No markdown file was selected" — Import was clicked before a file was chosen, or the file failed to load (the box shows the error in red). Run Import Markdown again and choose the file.

"Doesn't contain any content to import" — the file is empty, or contains only front matter.

"Failed to import" — Amplenote refused the content; the message includes the reason.


linkCode block

// --------------------------------------------------------------------------
// Imports a markdown file into the note the option was invoked on.
//
// app.prompt has no file input type, and plugin code runs in a `display: none` sandbox iframe the user can't interact
// with. Instead, `noteOption` opens an `app.prompt` with an "embed" input: the embed is a separate, visible iframe,
// so it can host a file picker / drop zone.
// --------------------------------------------------------------------------
(() => ({
// Embed actions callable via `window.callAmplenotePlugin(action, ...)`
EMBED_ACTION_SET_MARKDOWN: "setMarkdown",
 
// File extensions offered by the embed's file picker
FILE_ACCEPT: ".md,.markdown,.mdown,.mkd,.txt,text/markdown,text/plain",
 
// app.insertNoteContent / app.replaceNoteContent throw on content longer than this
MAX_CONTENT_LENGTH: 100000,
 
PICKER_HEIGHT_PIXELS: 140,
 
PLACEMENT_APPEND: "append",
PLACEMENT_PREPEND: "prepend",
PLACEMENT_REPLACE: "replace",
 
// --------------------------------------------------------------------------
async noteOption(app, noteUUID) {
delete this._importByNoteUUID[noteUUID];
 
const result = await app.prompt("Import markdown", {
inputs: [
{ label: "Markdown file", type: "embed", value: [ noteUUID ] },
{
label: "Where should the imported content go?",
options: [
{ label: "At the end of the note", value: this.PLACEMENT_APPEND },
{ label: "At the beginning of the note", value: this.PLACEMENT_PREPEND },
{ label: "Replace the note's content", value: this.PLACEMENT_REPLACE },
],
type: "select",
value: app.settings["lastPlacement"] || this.PLACEMENT_APPEND,
},
],
primaryAction: { icon: "upload_file", label: "Import" },
});
 
const imported = this._importByNoteUUID[noteUUID];
delete this._importByNoteUUID[noteUUID];
 
// null when the prompt was canceled, otherwise [ embedValue, placement ]
if (!Array.isArray(result)) return;
const placement = result[1] || this.PLACEMENT_APPEND;
 
if (!imported) {
await app.alert("No markdown file was selected, so nothing was imported.");
return;
}
 
const markdown = this._noteMarkdownFromFileMarkdown(imported.markdown);
if (!markdown) {
await app.alert(`"${ imported.fileName }" doesn't contain any content to import.`);
return;
}
if (markdown.length > this.MAX_CONTENT_LENGTH) {
await app.alert(
`"${ imported.fileName }" is ${ markdown.length.toLocaleString() } characters long, which exceeds the ` +
`${ this.MAX_CONTENT_LENGTH.toLocaleString() } character limit for importing into a note.`,
);
return;
}
 
app.setSetting("lastPlacement", placement);
 
try {
await this._insertMarkdown(app, { uuid: noteUUID }, markdown, placement);
} catch (error) {
await app.alert(`Failed to import "${ imported.fileName }": ${ (error && error.message) || error }`);
}
},
 
// --------------------------------------------------------------------------
async onEmbedCall(app, action, ...args) {
switch (action) {
case this.EMBED_ACTION_SET_MARKDOWN: {
const [ fileName, markdown, noteUUID ] = args;
this._importByNoteUUID[noteUUID] = { fileName, markdown };
return true;
}
 
default:
throw new Error(`Unknown embed action: ${ action }`);
}
},
 
// --------------------------------------------------------------------------
renderEmbed: {
check(app) {
return app.context.renderEmbedTarget === "prompt";
},
 
run(app, noteUUID) {
return this._embedHTMLFromNoteUUID(noteUUID);
},
},
 
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
// Serialized into the embed page via toString, so it can only reference its `config` argument and browser globals.
_embedScript: function(config) {
const { height, noteUUID, setMarkdownAction } = config;
 
window.setAmplenoteEmbedHeight(height);
 
const dropZone = document.getElementById("drop-zone");
const fileInput = document.getElementById("file-input");
const statusElement = document.getElementById("status");
 
const setStatus = (text, isError) => {
statusElement.textContent = text;
statusElement.classList.toggle("error", !!isError);
};
 
const handleFile = async file => {
if (!file) return;
 
try {
setStatus(`Reading ${ file.name }…`);
const markdown = await file.text();
await window.callAmplenotePlugin(setMarkdownAction, file.name, markdown, noteUUID);
dropZone.classList.add("selected");
setStatus(`${ file.name } (${ markdown.length.toLocaleString() } characters) is ready to import`);
} catch (error) {
setStatus(`Failed to read ${ file.name }: ${ (error && error.message) || error }`, true);
}
};
 
fileInput.addEventListener("change", () => handleFile(fileInput.files[0]));
 
dropZone.addEventListener("dragover", event => {
event.preventDefault();
dropZone.classList.add("dragging");
});
dropZone.addEventListener("dragleave", () => dropZone.classList.remove("dragging"));
dropZone.addEventListener("drop", event => {
event.preventDefault();
dropZone.classList.remove("dragging");
handleFile(event.dataTransfer.files[0]);
});
},
 
// --------------------------------------------------------------------------
_embedHTMLFromNoteUUID(noteUUID) {
const config = {
height: this.PICKER_HEIGHT_PIXELS,
noteUUID,
setMarkdownAction: this.EMBED_ACTION_SET_MARKDOWN,
};
 
// Escaping "<" ensures nothing in the config can close the script tag early
const configJSON = JSON.stringify(config).replace(/</g, "\\u003c");
 
return `
<style>${ this._pageStyle() }</style>
<label id="drop-zone">
<input id="file-input" type="file" accept="${ this.FILE_ACCEPT }">
<span id="status">Choose a markdown file, or drop one here</span>
</label>
<script>(${ this._embedScript.toString() })(${ configJSON });</script>
`;
},
 
// --------------------------------------------------------------------------
// Holds the markdown file selected in each open prompt's embed until the prompt is submitted
_importByNoteUUID: {},
 
// --------------------------------------------------------------------------
async _insertMarkdown(app, noteHandle, markdown, placement) {
switch (placement) {
case this.PLACEMENT_PREPEND:
await app.insertNoteContent(noteHandle, markdown);
break;
 
case this.PLACEMENT_REPLACE:
await app.replaceNoteContent(noteHandle, markdown);
break;
 
default:
await app.insertNoteContent(noteHandle, markdown, { atEnd: true });
break;
}
},
 
// --------------------------------------------------------------------------
// Normalizes line endings and drops YAML front matter (common in markdown exported from other apps), which would
// otherwise be imported as a horizontal rule followed by a paragraph of key/value pairs.
_noteMarkdownFromFileMarkdown(fileMarkdown) {
return fileMarkdown
.replace(/^/, "")
.replace(/\r\n?/g, "\n")
.replace(/^---\n[\s\S]*?\n---(\n|$)/, "")
.trim();
},
 
// --------------------------------------------------------------------------
_pageStyle() {
return `
html, body { background: transparent; height: 100%; margin: 0; }
#drop-zone {
align-items: center;
border: 2px dashed #b0b0b0;
border-radius: 8px;
box-sizing: border-box;
color: #555555;
cursor: pointer;
display: flex;
flex-direction: column;
font: 15px/1.4 system-ui, sans-serif;
gap: 10px;
height: 100%;
justify-content: center;
padding: 16px;
text-align: center;
}
#drop-zone.dragging { background: #eef4fb; border-color: #1a5fb4; }
#drop-zone.selected { border-color: #2e8b57; border-style: solid; }
#status.error { color: #c01c28; }
@media (prefers-color-scheme: dark) {
#drop-zone { border-color: #666666; color: #cccccc; }
#drop-zone.dragging { background: #1e2a38; border-color: #62a0ea; }
#status.error { color: #ff7b72; }
}
`;
},
}))()