(() => ({
EMBED_ACTION_SET_MARKDOWN: "setMarkdown",
FILE_ACCEPT: ".md,.markdown,.mdown,.mkd,.txt,text/markdown,text/plain",
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];
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);
},
},
_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,
};
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>
`;
},
_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;
}
},
_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; }
}
`;
},
}))()