mirror of https://git.door43.org/Andley/BG4e
205 lines
7.9 KiB
JavaScript
205 lines
7.9 KiB
JavaScript
'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());
|
|
});
|
|
}
|
|
|
|
const reKey = /\[\^(.+?(?=\]))\]/gi;
|
|
const reDefinition = /\[\^(.+)\]\:/;
|
|
// https://stackoverflow.com/a/1830844
|
|
function isNumeric(value) {
|
|
return !isNaN(value - parseFloat(value));
|
|
}
|
|
function tidyFootnotes(editor) {
|
|
let markers = [];
|
|
let definitions = new Map();
|
|
let firstDefinitionLine = -1;
|
|
let definitionsIndexed = new Map();
|
|
// Iterate through each line
|
|
const lineCount = editor.lineCount();
|
|
let prevKey = '';
|
|
for (let i = 0; i < lineCount; i++) {
|
|
const line = editor.getLine(i);
|
|
let isDefinition = false;
|
|
let match;
|
|
if (prevKey.length) {
|
|
const hasIndent = /^[ \t]/.test(line);
|
|
const isLastLine = i === (lineCount - 1);
|
|
if (hasIndent || (line.length === 0 && !isLastLine)) {
|
|
// Append line to the previous footnote definition
|
|
const value = definitions.get(prevKey);
|
|
definitions.set(prevKey, value + "\n" + line);
|
|
markers[markers.length - 1].length++;
|
|
continue;
|
|
}
|
|
else {
|
|
prevKey = '';
|
|
}
|
|
}
|
|
// Look for footnote definition
|
|
while ((match = reDefinition.exec(line)) !== null) {
|
|
if (match.length < 1)
|
|
return;
|
|
isDefinition = true;
|
|
// Remember definition and where it is
|
|
let key = match[1];
|
|
let value = line.substring(match[0].length);
|
|
definitions.set(key, value);
|
|
prevKey = key;
|
|
let marker = {
|
|
key,
|
|
line: i,
|
|
index: 0,
|
|
length: 0,
|
|
isDefinition: true
|
|
};
|
|
markers.push(marker);
|
|
// Remember first definition line to insert combined list later
|
|
if (firstDefinitionLine === -1) {
|
|
firstDefinitionLine = i;
|
|
}
|
|
break;
|
|
}
|
|
if (isDefinition)
|
|
continue;
|
|
// Look for footnote key
|
|
while ((match = reKey.exec(line)) !== null) {
|
|
if (match.length < 1)
|
|
return;
|
|
// Remember where footnote key is
|
|
let key = match[1];
|
|
let marker = {
|
|
key,
|
|
line: i,
|
|
index: match.index,
|
|
length: match[0].length,
|
|
isDefinition: false
|
|
};
|
|
markers.push(marker);
|
|
if (!definitionsIndexed.has(key)) {
|
|
// Add key into index
|
|
definitionsIndexed.set(key, {
|
|
key,
|
|
newKey: key,
|
|
isNumber: isNumeric(key),
|
|
value: ''
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// Assign definition to key in index
|
|
// If definition has no key, it will be appended with its current key
|
|
definitions.forEach((value, key) => {
|
|
definitionsIndexed.set(key, {
|
|
key,
|
|
newKey: key,
|
|
isNumber: isNumeric(key),
|
|
value
|
|
});
|
|
});
|
|
// Re-index numbers and construct combined definitions output
|
|
let count = 1;
|
|
let definitionsStr = '';
|
|
definitionsIndexed.forEach((definition, marker) => {
|
|
let key = definition.key;
|
|
if (definition.isNumber) {
|
|
const current = definitionsIndexed.get(marker);
|
|
key = count.toString();
|
|
definitionsIndexed.set(marker, Object.assign(Object.assign({}, current), { newKey: key }));
|
|
count++;
|
|
}
|
|
definitionsStr += `[^${key}]:${definition.value}\n`;
|
|
});
|
|
const markersCount = markers.length;
|
|
for (let i = markersCount - 1; i >= 0; i--) {
|
|
const marker = markers[i];
|
|
const markerLine = marker.line;
|
|
if (marker.isDefinition) {
|
|
let rangeStart, rangeEnd;
|
|
const lineEnd = markerLine + 1 + marker.length;
|
|
if (lineEnd === editor.lineCount()) {
|
|
// Replace from previous to current line to fix CodeMirror 6 error
|
|
rangeStart = { line: markerLine, ch: 0 };
|
|
rangeEnd = { line: lineEnd - 1, ch: Infinity };
|
|
}
|
|
else {
|
|
// Replace from current to next line
|
|
rangeStart = { line: markerLine, ch: 0 };
|
|
rangeEnd = { line: lineEnd, ch: 0 };
|
|
}
|
|
if (markerLine === firstDefinitionLine) {
|
|
// Replace first definition line with list of indexed definitions
|
|
editor.replaceRange(definitionsStr, rangeStart, rangeEnd);
|
|
continue;
|
|
}
|
|
// Remove line(s)
|
|
editor.replaceRange('', rangeStart, rangeEnd);
|
|
continue;
|
|
}
|
|
// Check if key has changed
|
|
const definition = definitionsIndexed.get(marker.key);
|
|
const newKey = definition.newKey;
|
|
if (marker.key === newKey)
|
|
continue;
|
|
// Replace footnote key in line with the new one
|
|
const line = editor.getLine(markerLine);
|
|
const prefix = line.substring(0, marker.index);
|
|
const newMarker = `[^${newKey}]`;
|
|
const suffix = line.substr(marker.index + marker.length);
|
|
const newLine = prefix + newMarker + suffix;
|
|
editor.replaceRange(newLine, { line: markerLine, ch: 0 }, { line: markerLine, ch: Infinity });
|
|
}
|
|
if (firstDefinitionLine == -1) {
|
|
// If there are no definitions, add definitions list at the end
|
|
const lineCount = editor.lineCount();
|
|
editor.replaceRange("\n\n" + definitionsStr, { line: lineCount, ch: 0 }, { line: lineCount, ch: Infinity });
|
|
}
|
|
// console.log(markers, definitions, definitionsIndexed, definitionsStr);
|
|
}
|
|
|
|
class TidyFootnotes extends obsidian.Plugin {
|
|
onload() {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
this.addCommand({
|
|
id: 'tidy-footnotes',
|
|
name: 'Tidy Footnotes',
|
|
checkCallback: (checking) => {
|
|
// Ensure the active view is a Markdown editor
|
|
const view = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView);
|
|
if (checking)
|
|
return !!view;
|
|
if (!view || view.sourceMode == undefined)
|
|
return false;
|
|
let editor = view.editor;
|
|
tidyFootnotes(editor);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = TidyFootnotes;
|