|
|
|
'use strict';
|
|
|
|
|
|
|
|
var obsidian = require('obsidian');
|
|
|
|
|
|
|
|
/*! *****************************************************************************
|
|
|
|
Copyright (c) Microsoft Corporation.
|
|
|
|
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
purpose with or without fee is hereby granted.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
***************************************************************************** */
|
|
|
|
|
|
|
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
class MyPlugin extends obsidian.Plugin {
|
|
|
|
constructor() {
|
|
|
|
super(...arguments);
|
|
|
|
this.detailLineRegex = /\[\^(\d+)\]\:/;
|
|
|
|
this.reOnlyMarkers = /\[\^(\d+)\]/gi;
|
|
|
|
this.numericalRe = /(\d+)/;
|
|
|
|
}
|
|
|
|
onload() {
|
|
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
|
|
this.addCommand({
|
|
|
|
id: "insert-footnote",
|
|
|
|
name: "Insert and Navigate Footnote",
|
|
|
|
checkCallback: (checking) => {
|
|
|
|
if (checking)
|
|
|
|
return !!this.app.workspace.getActiveViewOfType(obsidian.MarkdownView);
|
|
|
|
this.insertFootnote();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
insertFootnote() {
|
|
|
|
const mdView = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView);
|
|
|
|
if (!mdView)
|
|
|
|
return false;
|
|
|
|
if (mdView.editor == undefined)
|
|
|
|
return false;
|
|
|
|
const doc = mdView.editor;
|
|
|
|
const cursorPosition = doc.getCursor();
|
|
|
|
const lineText = doc.getLine(cursorPosition.line);
|
|
|
|
const markdownText = mdView.data;
|
|
|
|
if (this.shouldJumpFromDetailToMarker(lineText, cursorPosition, doc))
|
|
|
|
return;
|
|
|
|
if (this.shouldJumpFromMarkerToDetail(lineText, cursorPosition, doc))
|
|
|
|
return;
|
|
|
|
return this.shouldCreateNewFootnote(lineText, cursorPosition, doc, markdownText);
|
|
|
|
}
|
|
|
|
shouldJumpFromDetailToMarker(lineText, cursorPosition, doc) {
|
|
|
|
// check if we're in a footnote detail line ("[^1]: footnote")
|
|
|
|
// if so, jump cursor back to the footnote in the text
|
|
|
|
// https://github.com/akaalias/obsidian-footnotes#improved-quick-navigation
|
|
|
|
let match = lineText.match(this.detailLineRegex);
|
|
|
|
if (match) {
|
|
|
|
let s = match[0];
|
|
|
|
let index = s.replace("[^", "");
|
|
|
|
index = index.replace("]:", "");
|
|
|
|
let footnote = s.replace(":", "");
|
|
|
|
let returnLineIndex = cursorPosition.line;
|
|
|
|
// find the FIRST OCCURENCE where this footnote exists in the text
|
|
|
|
for (let i = 0; i < doc.lineCount(); i++) {
|
|
|
|
let scanLine = doc.getLine(i);
|
|
|
|
if (scanLine.contains(footnote)) {
|
|
|
|
let cursorLocationIndex = scanLine.indexOf(footnote);
|
|
|
|
returnLineIndex = i;
|
|
|
|
doc.setCursor({
|
|
|
|
line: returnLineIndex,
|
|
|
|
ch: cursorLocationIndex + footnote.length,
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
shouldJumpFromMarkerToDetail(lineText, cursorPosition, doc) {
|
|
|
|
// Jump cursor TO detail marker
|
|
|
|
// check if the cursor is inside or left or right of a footnote in a line
|
|
|
|
// if so, jump cursor to the footnote detail line
|
|
|
|
// https://github.com/akaalias/obsidian-footnotes#improved-quick-navigation
|
|
|
|
// does this line have a footnote marker?
|
|
|
|
// does the cursor overlap with one of them?
|
|
|
|
// if so, which one?
|
|
|
|
// find this footnote marker's detail line
|
|
|
|
// place cursor there
|
|
|
|
let reOnlyMarkersMatches = lineText.match(this.reOnlyMarkers);
|
|
|
|
let markerTarget = null;
|
|
|
|
if (reOnlyMarkersMatches) {
|
|
|
|
for (let i = 0; i <= reOnlyMarkersMatches.length; i++) {
|
|
|
|
let marker = reOnlyMarkersMatches[i];
|
|
|
|
if (marker != undefined) {
|
|
|
|
let indexOfMarkerInLine = lineText.indexOf(marker);
|
|
|
|
if (cursorPosition.ch >= indexOfMarkerInLine &&
|
|
|
|
cursorPosition.ch <= indexOfMarkerInLine + marker.length) {
|
|
|
|
markerTarget = marker;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (markerTarget != null) {
|
|
|
|
// extract index
|
|
|
|
let match = markerTarget.match(this.numericalRe);
|
|
|
|
if (match) {
|
|
|
|
let indexString = match[0];
|
|
|
|
let markerIndex = Number(indexString);
|
|
|
|
// find the first line with this detail marker index in it.
|
|
|
|
for (let i = 0; i < doc.lineCount(); i++) {
|
|
|
|
let theLine = doc.getLine(i);
|
|
|
|
let lineMatch = theLine.match(this.detailLineRegex);
|
|
|
|
if (lineMatch) {
|
|
|
|
// compare to the index
|
|
|
|
let indexMatch = lineMatch[1];
|
|
|
|
let indexMatchNumber = Number(indexMatch);
|
|
|
|
if (indexMatchNumber == markerIndex) {
|
|
|
|
doc.setCursor({ line: i, ch: lineMatch[0].length + 1 });
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
shouldCreateNewFootnote(lineText, cursorPosition, doc, markdownText) {
|
|
|
|
// create new footnote with the next numerical index
|
|
|
|
let matches = markdownText.match(this.reOnlyMarkers);
|
|
|
|
let numbers = [];
|
|
|
|
let currentMax = 1;
|
|
|
|
if (matches != null) {
|
|
|
|
for (let i = 0; i <= matches.length - 1; i++) {
|
|
|
|
let match = matches[i];
|
|
|
|
match = match.replace("[^", "");
|
|
|
|
match = match.replace("]", "");
|
|
|
|
let matchNumber = Number(match);
|
|
|
|
numbers[i] = matchNumber;
|
|
|
|
if (matchNumber + 1 > currentMax) {
|
|
|
|
currentMax = matchNumber + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let footNoteId = currentMax;
|
|
|
|
let footnoteMarker = `[^${footNoteId}]`;
|
|
|
|
let linePart1 = lineText.substr(0, cursorPosition.ch);
|
|
|
|
let linePart2 = lineText.substr(cursorPosition.ch);
|
|
|
|
let newLine = linePart1 + footnoteMarker + linePart2;
|
|
|
|
doc.replaceRange(newLine, { line: cursorPosition.line, ch: 0 }, { line: cursorPosition.line, ch: lineText.length });
|
|
|
|
let lastLineIndex = doc.lastLine();
|
|
|
|
let lastLine = doc.getLine(lastLineIndex);
|
|
|
|
while (lastLineIndex > 0) {
|
|
|
|
lastLine = doc.getLine(lastLineIndex);
|
|
|
|
if (lastLine.length > 0) {
|
|
|
|
doc.replaceRange("", { line: lastLineIndex, ch: 0 }, { line: doc.lastLine(), ch: 0 });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
lastLineIndex--;
|
|
|
|
}
|
|
|
|
let footnoteDetail = `\n[^${footNoteId}]: `;
|
|
|
|
if (currentMax == 1) {
|
|
|
|
footnoteDetail = "\n" + footnoteDetail;
|
|
|
|
}
|
|
|
|
doc.setLine(doc.lastLine(), lastLine + footnoteDetail);
|
|
|
|
doc.setCursor(doc.lastLine(), footnoteDetail.length - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = MyPlugin;
|
|
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsIm1haW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyohICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqXHJcbkNvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLlxyXG5cclxuUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55XHJcbnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZC5cclxuXHJcblRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIgQU5EIFRIRSBBVVRIT1IgRElTQ0xBSU1TIEFMTCBXQVJSQU5USUVTIFdJVEhcclxuUkVHQVJEIFRPIFRISVMgU09GVFdBUkUgSU5DTFVESU5HIEFMTCBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZXHJcbkFORCBGSVRORVNTLiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SIEJFIExJQUJMRSBGT1IgQU5ZIFNQRUNJQUwsIERJUkVDVCxcclxuSU5ESVJFQ1QsIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyBPUiBBTlkgREFNQUdFUyBXSEFUU09FVkVSIFJFU1VMVElORyBGUk9NXHJcbkxPU1MgT0YgVVNFLCBEQVRBIE9SIFBST0ZJVFMsIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBORUdMSUdFTkNFIE9SXHJcbk9USEVSIFRPUlRJT1VTIEFDVElPTiwgQVJJU0lORyBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBVU0UgT1JcclxuUEVSRk9STUFOQ0UgT0YgVEhJUyBTT0ZUV0FSRS5cclxuKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiogKi9cclxuLyogZ2xvYmFsIFJlZmxlY3QsIFByb21pc2UgKi9cclxuXHJcbnZhciBleHRlbmRTdGF0aWNzID0gZnVuY3Rpb24oZCwgYikge1xyXG4gICAgZXh0ZW5kU3RhdGljcyA9IE9iamVjdC5zZXRQcm90b3R5cGVPZiB8fFxyXG4gICAgICAgICh7IF9fcHJvdG9fXzogW10gfSBpbnN0YW5jZW9mIEFycmF5ICYmIGZ1bmN0aW9uIChkLCBiKSB7IGQuX19wcm90b19fID0gYjsgfSkgfHxcclxuICAgICAgICBmdW5jdGlvbiAoZCwgYikgeyBmb3IgKHZhciBwIGluIGIpIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoYiwgcCkpIGRbcF0gPSBiW3BdOyB9O1xyXG4gICAgcmV0dXJuIGV4dGVuZFN0YXRpY3MoZCwgYik7XHJcbn07XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19leHRlbmRzKGQsIGIpIHtcclxuICAgIGlmICh0eXBlb2YgYiAhPT0gXCJmdW5jdGlvblwiICYmIGIgIT09IG51bGwpXHJcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkNsYXNzIGV4dGVuZHMgdmFsdWUgXCIgKyBTdHJpbmcoYikgKyBcIiBpcyBub3QgYSBjb25zdHJ1Y3RvciBvciBudWxsXCIpO1xyXG4gICAgZXh0ZW5kU3RhdGljcyhkLCBiKTtcclxuICAgIGZ1bmN0aW9uIF9fKCkgeyB0aGlzLmNvbnN0cnVjdG9yID0gZDsgfVxyXG4gICAgZC5wcm90b3R5cGUgPSBiID09PSBudWxsID8gT2JqZWN0LmNyZWF0ZShiKSA6IChfXy5wcm90b3R5cGUgPSBiLnByb3RvdHlwZSwgbmV3IF9fKCkpO1xyXG59XHJcblxyXG5leHBvcnQgdmFyIF9fYXNzaWduID0gZnVuY3Rpb24oKSB7XHJcbiAgICBfX2Fzc2lnbiA9IE9iamVjdC5hc3NpZ24gfHwgZnVuY3Rpb24gX19hc3NpZ24odCkge1xyXG4gICAgICAgIGZvciAodmFyIHMsIGkgPSAxLCBuID0gYXJndW1lbnRzLmxlbmd0aDsgaSA8IG47IGkrKykge1xyXG4gICAgICAgICAgICBzID0gYXJndW1lbnRzW2ldO1xyXG4gICAgICAgICAgICBmb3IgKHZhciBwIGluIHMpIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwocywgcCkpIHRbcF0gPSBzW3BdO1xyXG4gICAgICAgIH1cclxuICAgICAgICByZXR1cm4gdDtcclxuICAgIH1cclxuICAgIHJldHVybiBfX2Fzc2lnbi5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19yZXN0KHMsIGUpIHtcclxuICAgIHZhciB0ID0ge307XHJcbiAgICBmb3IgKHZhciBwIGluIHMpIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwocywgcCkgJiYgZS5pbmRleE9mKHApIDwgMClcclxuICAgICAgICB0W3BdID0gc1twXTtcclxuICAgIGlmIChzICE9IG51bGwgJiYgdHlwZW9mIE9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMgPT09IFwiZnVuY3Rpb25cIilcclxuICAgICAgICBmb3IgKHZhciBpID0gMCwgcCA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMocyk7IGkgPCBwLmxlbmd0aDsgaSsrKSB7XHJcbiAgICAgICAgICAgIGlmIChlLmluZGV4T2YocFtpXSkgPCAwICYmIE9iamVjdC5wcm90b3R5cGUucHJvcGVydHlJc0VudW1lcmFibGUuY2FsbChzLCBwW2ldKSlcclxuICAgICAgICAgICAgICAgIHRbcFtpXV0gPSBzW3BbaV1dO1xyXG4gICAgICAgIH1cclxuICAgIHJldHVybiB0O1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19kZWNvcmF0ZShkZWNvcmF0b3JzLCB0YXJnZXQsIGtleSwgZGVzYykge1xyXG4gICAgdmFyIGMgPSBhcmd1bWVudHMubGVuZ3RoLCByID0gYyA8IDMgPyB0YXJnZXQgOiBkZXNjID09PSBudWxsID8gZGVzYyA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IodGFyZ2V0LCBrZXkpIDogZGVzYywgZDtcclxuICAgIGlmICh0eXBlb2YgUmVmbGVjdCA9PT0gXCJvYmplY3RcIiAmJiB0eXBlb2YgUmVmbGVjdC5kZWNvcmF0ZSA9PT0gXCJmdW5jdGlvblwiKSByID0gUmVmbGVjdC5kZWNvcmF0ZShkZWNvcmF0b3JzLCB0YXJnZXQsIGtleSwgZGVzYyk7XHJcbiAgICBlbHNlIGZvciAodmFyIGkgPSBkZWNvcmF0b3JzLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSBpZiAoZCA9IGRlY29yYXRvcnNbaV0pIHIgPSAoYyA8IDMgPyBkKHI
|