1
0
public/.obsidian/plugins/obsidian-outliner/main.js

3260 lines
442 KiB
JavaScript
Raw Permalink Normal View History

2023-05-07 12:39:07 +00:00
'use strict';
var obsidian = require('obsidian');
var view = require('@codemirror/view');
var language = require('@codemirror/language');
var state = require('@codemirror/state');
/******************************************************************************
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 MoveCursorToPreviousUnfoldedLine {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
const list = this.root.getListUnderCursor();
const cursor = this.root.getCursor();
const lines = list.getLinesInfo();
const lineNo = lines.findIndex((l) => {
return (cursor.ch === l.from.ch + list.getCheckboxLength() &&
cursor.line === l.from.line);
});
if (lineNo === 0) {
this.moveCursorToPreviousUnfoldedItem(root, cursor);
}
else if (lineNo > 0) {
this.moveCursorToPreviousNoteLine(root, lines, lineNo);
}
}
moveCursorToPreviousNoteLine(root, lines, lineNo) {
this.stopPropagation = true;
this.updated = true;
root.replaceCursor(lines[lineNo - 1].to);
}
moveCursorToPreviousUnfoldedItem(root, cursor) {
const prev = root.getListUnderLine(cursor.line - 1);
if (!prev) {
return;
}
this.stopPropagation = true;
this.updated = true;
if (prev.isFolded()) {
const foldRoot = prev.getTopFoldRoot();
const firstLineEnd = foldRoot.getLinesInfo()[0].to;
root.replaceCursor(firstLineEnd);
}
else {
root.replaceCursor(prev.getLastLineContentEnd());
}
}
}
function getEditorFromState(state) {
const { editor } = state.field(obsidian.editorInfoField);
if (!editor) {
return null;
}
return new MyEditor(editor);
}
function foldInside(view, from, to) {
let found = null;
language.foldedRanges(view.state).between(from, to, (from, to) => {
if (!found || found.from > from)
found = { from, to };
});
return found;
}
class MyEditor {
constructor(e) {
this.e = e;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.view = this.e.cm;
}
getCursor() {
return this.e.getCursor();
}
getLine(n) {
return this.e.getLine(n);
}
lastLine() {
return this.e.lastLine();
}
listSelections() {
return this.e.listSelections();
}
getRange(from, to) {
return this.e.getRange(from, to);
}
replaceRange(replacement, from, to) {
return this.e.replaceRange(replacement, from, to);
}
setSelections(selections) {
this.e.setSelections(selections);
}
setValue(text) {
this.e.setValue(text);
}
getValue() {
return this.e.getValue();
}
offsetToPos(offset) {
return this.e.offsetToPos(offset);
}
posToOffset(pos) {
return this.e.posToOffset(pos);
}
fold(n) {
const { view } = this;
const l = view.lineBlockAt(view.state.doc.line(n + 1).from);
const range = language.foldable(view.state, l.from, l.to);
if (!range || range.from === range.to) {
return;
}
view.dispatch({ effects: [language.foldEffect.of(range)] });
}
unfold(n) {
const { view } = this;
const l = view.lineBlockAt(view.state.doc.line(n + 1).from);
const range = foldInside(view, l.from, l.to);
if (!range) {
return;
}
view.dispatch({ effects: [language.unfoldEffect.of(range)] });
}
getAllFoldedLines() {
const c = language.foldedRanges(this.view.state).iter();
const res = [];
while (c.value) {
res.push(this.offsetToPos(c.from).line);
c.next();
}
return res;
}
triggerOnKeyDown(e) {
view.runScopeHandlers(this.view, e, "editor");
}
getZoomRange() {
if (!window.ObsidianZoomPlugin) {
return null;
}
return window.ObsidianZoomPlugin.getZoomRange(this.e);
}
zoomOut() {
if (!window.ObsidianZoomPlugin) {
return;
}
window.ObsidianZoomPlugin.zoomOut(this.e);
}
zoomIn(line) {
if (!window.ObsidianZoomPlugin) {
return;
}
window.ObsidianZoomPlugin.zoomIn(this.e, line);
}
tryRefreshZoom(line) {
if (!window.ObsidianZoomPlugin) {
return;
}
if (window.ObsidianZoomPlugin.refreshZoom) {
window.ObsidianZoomPlugin.refreshZoom(this.e);
}
else {
window.ObsidianZoomPlugin.zoomIn(this.e, line);
}
}
}
function createKeymapRunCallback(config) {
const check = config.check || (() => true);
const { run } = config;
return (view) => {
const editor = getEditorFromState(view.state);
if (!check(editor)) {
return false;
}
const { shouldUpdate, shouldStopPropagation } = run(editor);
return shouldUpdate || shouldStopPropagation;
};
}
class ArrowLeftAndCtrlArrowLeftBehaviourOverride {
constructor(plugin, settings, imeDetector, operationPerformer) {
this.plugin = plugin;
this.settings = settings;
this.imeDetector = imeDetector;
this.operationPerformer = operationPerformer;
this.check = () => {
return (this.settings.keepCursorWithinContent !== "never" &&
!this.imeDetector.isOpened());
};
this.run = (editor) => {
return this.operationPerformer.perform((root) => new MoveCursorToPreviousUnfoldedLine(root), editor);
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(view.keymap.of([
{
key: "ArrowLeft",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
{
win: "c-ArrowLeft",
linux: "c-ArrowLeft",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
]));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
function cmpPos(a, b) {
return a.line - b.line || a.ch - b.ch;
}
function maxPos(a, b) {
return cmpPos(a, b) < 0 ? b : a;
}
function minPos(a, b) {
return cmpPos(a, b) < 0 ? a : b;
}
function isRangesIntersects(a, b) {
return cmpPos(a[1], b[0]) >= 0 && cmpPos(a[0], b[1]) <= 0;
}
function recalculateNumericBullets(root) {
function visit(parent) {
let index = 1;
for (const child of parent.getChildren()) {
if (/\d+\./.test(child.getBullet())) {
child.replateBullet(`${index++}.`);
}
visit(child);
}
}
visit(root);
}
let idSeq = 0;
class List {
constructor(root, indent, bullet, optionalCheckbox, spaceAfterBullet, firstLine, foldRoot) {
this.root = root;
this.indent = indent;
this.bullet = bullet;
this.optionalCheckbox = optionalCheckbox;
this.spaceAfterBullet = spaceAfterBullet;
this.foldRoot = foldRoot;
this.parent = null;
this.children = [];
this.notesIndent = null;
this.lines = [];
this.id = idSeq++;
this.lines.push(firstLine);
}
getID() {
return this.id;
}
getNotesIndent() {
return this.notesIndent;
}
setNotesIndent(notesIndent) {
if (this.notesIndent !== null) {
throw new Error(`Notes indent already provided`);
}
this.notesIndent = notesIndent;
}
addLine(text) {
if (this.notesIndent === null) {
throw new Error(`Unable to add line, notes indent should be provided first`);
}
this.lines.push(text);
}
replaceLines(lines) {
if (lines.length > 1 && this.notesIndent === null) {
throw new Error(`Unable to add line, notes indent should be provided first`);
}
this.lines = lines;
}
getLineCount() {
return this.lines.length;
}
getRoot() {
return this.root;
}
getChildren() {
return this.children.concat();
}
getLinesInfo() {
const startLine = this.root.getContentLinesRangeOf(this)[0];
return this.lines.map((row, i) => {
const line = startLine + i;
const startCh = i === 0 ? this.getContentStartCh() : this.notesIndent.length;
const endCh = startCh + row.length;
return {
text: row,
from: { line, ch: startCh },
to: { line, ch: endCh },
};
});
}
getLines() {
return this.lines.concat();
}
getFirstLineContentStart() {
const startLine = this.root.getContentLinesRangeOf(this)[0];
return {
line: startLine,
ch: this.getContentStartCh(),
};
}
getFirstLineContentStartAfterCheckbox() {
const startLine = this.root.getContentLinesRangeOf(this)[0];
return {
line: startLine,
ch: this.getContentStartCh() + this.getCheckboxLength(),
};
}
getLastLineContentEnd() {
const endLine = this.root.getContentLinesRangeOf(this)[1];
const endCh = this.lines.length === 1
? this.getContentStartCh() + this.lines[0].length
: this.notesIndent.length + this.lines[this.lines.length - 1].length;
return {
line: endLine,
ch: endCh,
};
}
getContentEndIncludingChildren() {
return this.getLastChild().getLastLineContentEnd();
}
getLastChild() {
let lastChild = this;
while (!lastChild.isEmpty()) {
lastChild = lastChild.getChildren().last();
}
return lastChild;
}
getContentStartCh() {
return this.indent.length + this.bullet.length + 1;
}
isFolded() {
if (this.foldRoot) {
return true;
}
if (this.parent) {
return this.parent.isFolded();
}
return false;
}
isFoldRoot() {
return this.foldRoot;
}
getTopFoldRoot() {
let tmp = this;
let foldRoot = null;
while (tmp) {
if (tmp.isFoldRoot()) {
foldRoot = tmp;
}
tmp = tmp.parent;
}
return foldRoot;
}
getLevel() {
if (!this.parent) {
return 0;
}
return this.parent.getLevel() + 1;
}
unindentContent(from, till) {
this.indent = this.indent.slice(0, from) + this.indent.slice(till);
if (this.notesIndent !== null) {
this.notesIndent =
this.notesIndent.slice(0, from) + this.notesIndent.slice(till);
}
for (const child of this.children) {
child.unindentContent(from, till);
}
}
indentContent(indentPos, indentChars) {
this.indent =
this.indent.slice(0, indentPos) +
indentChars +
this.indent.slice(indentPos);
if (this.notesIndent !== null) {
this.notesIndent =
this.notesIndent.slice(0, indentPos) +
indentChars +
this.notesIndent.slice(indentPos);
}
for (const child of this.children) {
child.indentContent(indentPos, indentChars);
}
}
getFirstLineIndent() {
return this.indent;
}
getBullet() {
return this.bullet;
}
getSpaceAfterBullet() {
return this.spaceAfterBullet;
}
getCheckboxLength() {
return this.optionalCheckbox.length;
}
replateBullet(bullet) {
this.bullet = bullet;
}
getParent() {
return this.parent;
}
addBeforeAll(list) {
this.children.unshift(list);
list.parent = this;
}
addAfterAll(list) {
this.children.push(list);
list.parent = this;
}
removeChild(list) {
const i = this.children.indexOf(list);
this.children.splice(i, 1);
list.parent = null;
}
addBefore(before, list) {
const i = this.children.indexOf(before);
this.children.splice(i, 0, list);
list.parent = this;
}
addAfter(before, list) {
const i = this.children.indexOf(before);
this.children.splice(i + 1, 0, list);
list.parent = this;
}
getPrevSiblingOf(list) {
const i = this.children.indexOf(list);
return i > 0 ? this.children[i - 1] : null;
}
getNextSiblingOf(list) {
const i = this.children.indexOf(list);
return i >= 0 && i < this.children.length ? this.children[i + 1] : null;
}
isEmpty() {
return this.children.length === 0;
}
print() {
let res = "";
for (let i = 0; i < this.lines.length; i++) {
res +=
i === 0
? this.indent + this.bullet + this.spaceAfterBullet
: this.notesIndent;
res += this.lines[i];
res += "\n";
}
for (const child of this.children) {
res += child.print();
}
return res;
}
clone(newRoot) {
const clone = new List(newRoot, this.indent, this.bullet, this.optionalCheckbox, this.spaceAfterBullet, "", this.foldRoot);
clone.id = this.id;
clone.lines = this.lines.concat();
clone.notesIndent = this.notesIndent;
for (const child of this.children) {
clone.addAfterAll(child.clone(newRoot));
}
return clone;
}
}
class Root {
constructor(start, end, selections) {
this.start = start;
this.end = end;
this.rootList = new List(this, "", "", "", "", "", false);
this.selections = [];
this.replaceSelections(selections);
}
getRootList() {
return this.rootList;
}
getContentRange() {
return [this.getContentStart(), this.getContentEnd()];
}
getContentStart() {
return Object.assign({}, this.start);
}
getContentEnd() {
return Object.assign({}, this.end);
}
getSelections() {
return this.selections.map((s) => ({
anchor: Object.assign({}, s.anchor),
head: Object.assign({}, s.head),
}));
}
hasSingleCursor() {
if (!this.hasSingleSelection()) {
return false;
}
const selection = this.selections[0];
return (selection.anchor.line === selection.head.line &&
selection.anchor.ch === selection.head.ch);
}
hasSingleSelection() {
return this.selections.length === 1;
}
getSelection() {
const selection = this.selections[this.selections.length - 1];
const from = selection.anchor.ch > selection.head.ch
? selection.head.ch
: selection.anchor.ch;
const to = selection.anchor.ch > selection.head.ch
? selection.anchor.ch
: selection.head.ch;
return Object.assign(Object.assign({}, selection), { from,
to });
}
getCursor() {
return Object.assign({}, this.selections[this.selections.length - 1].head);
}
replaceCursor(cursor) {
this.selections = [{ anchor: cursor, head: cursor }];
}
replaceSelections(selections) {
if (selections.length < 1) {
throw new Error(`Unable to create Root without selections`);
}
this.selections = selections;
}
getListUnderCursor() {
return this.getListUnderLine(this.getCursor().line);
}
getListUnderLine(line) {
if (line < this.start.line || line > this.end.line) {
return;
}
let result = null;
let index = this.start.line;
const visitArr = (ll) => {
for (const l of ll) {
const listFromLine = index;
const listTillLine = listFromLine + l.getLineCount() - 1;
if (line >= listFromLine && line <= listTillLine) {
result = l;
}
else {
index = listTillLine + 1;
visitArr(l.getChildren());
}
if (result !== null) {
return;
}
}
};
visitArr(this.rootList.getChildren());
return result;
}
getContentLinesRangeOf(list) {
let result = null;
let line = this.start.line;
const visitArr = (ll) => {
for (const l of ll) {
const listFromLine = line;
const listTillLine = listFromLine + l.getLineCount() - 1;
if (l === list) {
result = [listFromLine, listTillLine];
}
else {
line = listTillLine + 1;
visitArr(l.getChildren());
}
if (result !== null) {
return;
}
}
};
visitArr(this.rootList.getChildren());
return result;
}
getChildren() {
return this.rootList.getChildren();
}
print() {
let res = "";
for (const child of this.rootList.getChildren()) {
res += child.print();
}
return res.replace(/\n$/, "");
}
clone() {
const clone = new Root(Object.assign({}, this.start), Object.assign({}, this.end), this.getSelections());
clone.rootList = this.rootList.clone(clone);
return clone;
}
}
class DeleteTillPreviousLineContentEnd {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
const list = root.getListUnderCursor();
const cursor = root.getCursor();
const lines = list.getLinesInfo();
const lineNo = lines.findIndex((l) => cursor.ch === l.from.ch && cursor.line === l.from.line);
if (lineNo === 0) {
this.mergeWithPreviousItem(root, cursor, list);
}
else if (lineNo > 0) {
this.mergeNotes(root, cursor, list, lines, lineNo);
}
}
mergeNotes(root, cursor, list, lines, lineNo) {
this.stopPropagation = true;
this.updated = true;
const prevLineNo = lineNo - 1;
root.replaceCursor({
line: cursor.line - 1,
ch: lines[prevLineNo].text.length + lines[prevLineNo].from.ch,
});
lines[prevLineNo].text += lines[lineNo].text;
lines.splice(lineNo, 1);
list.replaceLines(lines.map((l) => l.text));
}
mergeWithPreviousItem(root, cursor, list) {
if (root.getChildren()[0] === list && list.isEmpty()) {
return;
}
this.stopPropagation = true;
const prev = root.getListUnderLine(cursor.line - 1);
if (!prev) {
return;
}
const bothAreEmpty = prev.isEmpty() && list.isEmpty();
const prevIsEmptyAndSameLevel = prev.isEmpty() && !list.isEmpty() && prev.getLevel() === list.getLevel();
const listIsEmptyAndPrevIsParent = list.isEmpty() && prev.getLevel() === list.getLevel() - 1;
if (bothAreEmpty || prevIsEmptyAndSameLevel || listIsEmptyAndPrevIsParent) {
this.updated = true;
const parent = list.getParent();
const prevEnd = prev.getLastLineContentEnd();
if (!prev.getNotesIndent() && list.getNotesIndent()) {
prev.setNotesIndent(prev.getFirstLineIndent() +
list.getNotesIndent().slice(list.getFirstLineIndent().length));
}
const oldLines = prev.getLines();
const newLines = list.getLines();
oldLines[oldLines.length - 1] += newLines[0];
const resultLines = oldLines.concat(newLines.slice(1));
prev.replaceLines(resultLines);
parent.removeChild(list);
for (const c of list.getChildren()) {
list.removeChild(c);
prev.addAfterAll(c);
}
root.replaceCursor(prevEnd);
recalculateNumericBullets(root);
}
}
}
class BackspaceBehaviourOverride {
constructor(plugin, settings, imeDetector, operationPerformer) {
this.plugin = plugin;
this.settings = settings;
this.imeDetector = imeDetector;
this.operationPerformer = operationPerformer;
this.check = () => {
return (this.settings.keepCursorWithinContent !== "never" &&
!this.imeDetector.isOpened());
};
this.run = (editor) => {
return this.operationPerformer.perform((root) => new DeleteTillPreviousLineContentEnd(root), editor);
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(view.keymap.of([
{
key: "Backspace",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
]));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
const BETTER_LISTS_BODY_CLASS = "outliner-plugin-better-lists";
class BetterListsStyles {
constructor(settings, obsidianSettings) {
this.settings = settings;
this.obsidianSettings = obsidianSettings;
this.updateBodyClass = () => {
const shouldExists = this.obsidianSettings.isDefaultThemeEnabled() &&
this.settings.betterListsStyles;
const exists = document.body.classList.contains(BETTER_LISTS_BODY_CLASS);
if (shouldExists && !exists) {
document.body.classList.add(BETTER_LISTS_BODY_CLASS);
}
if (!shouldExists && exists) {
document.body.classList.remove(BETTER_LISTS_BODY_CLASS);
}
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.updateBodyClass();
this.updateBodyClassInterval = window.setInterval(() => {
this.updateBodyClass();
}, 1000);
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () {
clearInterval(this.updateBodyClassInterval);
document.body.classList.remove(BETTER_LISTS_BODY_CLASS);
});
}
}
class SelectAllContent {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleSelection()) {
return;
}
const selection = root.getSelections()[0];
const [rootStart, rootEnd] = root.getContentRange();
const selectionFrom = minPos(selection.anchor, selection.head);
const selectionTo = maxPos(selection.anchor, selection.head);
if (selectionFrom.line < rootStart.line ||
selectionTo.line > rootEnd.line) {
return false;
}
if (selectionFrom.line === rootStart.line &&
selectionFrom.ch === rootStart.ch &&
selectionTo.line === rootEnd.line &&
selectionTo.ch === rootEnd.ch) {
return false;
}
const list = root.getListUnderCursor();
const contentStart = list.getFirstLineContentStartAfterCheckbox();
const contentEnd = list.getLastLineContentEnd();
if (selectionFrom.line < contentStart.line ||
selectionTo.line > contentEnd.line) {
return false;
}
this.stopPropagation = true;
this.updated = true;
if (selectionFrom.line === contentStart.line &&
selectionFrom.ch === contentStart.ch &&
selectionTo.line === contentEnd.line &&
selectionTo.ch === contentEnd.ch) {
// select whole list
root.replaceSelections([{ anchor: rootStart, head: rootEnd }]);
}
else {
// select whole line
root.replaceSelections([{ anchor: contentStart, head: contentEnd }]);
}
return true;
}
}
class CtrlAAndCmdABehaviourOverride {
constructor(plugin, settings, imeDetector, operationPerformer) {
this.plugin = plugin;
this.settings = settings;
this.imeDetector = imeDetector;
this.operationPerformer = operationPerformer;
this.check = () => {
return (this.settings.overrideSelectAllBehaviour && !this.imeDetector.isOpened());
};
this.run = (editor) => {
return this.operationPerformer.perform((root) => new SelectAllContent(root), editor);
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(view.keymap.of([
{
key: "c-a",
mac: "m-a",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
]));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
class DeleteTillNextLineContentStart {
constructor(root) {
this.root = root;
this.deleteTillPreviousLineContentEnd =
new DeleteTillPreviousLineContentEnd(root);
}
shouldStopPropagation() {
return this.deleteTillPreviousLineContentEnd.shouldStopPropagation();
}
shouldUpdate() {
return this.deleteTillPreviousLineContentEnd.shouldUpdate();
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
const list = root.getListUnderCursor();
const cursor = root.getCursor();
const lines = list.getLinesInfo();
const lineNo = lines.findIndex((l) => cursor.ch === l.to.ch && cursor.line === l.to.line);
if (lineNo === lines.length - 1) {
const nextLine = lines[lineNo].to.line + 1;
const nextList = root.getListUnderLine(nextLine);
if (!nextList) {
return;
}
root.replaceCursor(nextList.getFirstLineContentStart());
this.deleteTillPreviousLineContentEnd.perform();
}
else if (lineNo >= 0) {
root.replaceCursor(lines[lineNo + 1].from);
this.deleteTillPreviousLineContentEnd.perform();
}
}
}
class DeleteBehaviourOverride {
constructor(plugin, settings, imeDetector, operationPerformer) {
this.plugin = plugin;
this.settings = settings;
this.imeDetector = imeDetector;
this.operationPerformer = operationPerformer;
this.check = () => {
return (this.settings.keepCursorWithinContent !== "never" &&
!this.imeDetector.isOpened());
};
this.run = (editor) => {
return this.operationPerformer.perform((root) => new DeleteTillNextLineContentStart(root), editor);
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(view.keymap.of([
{
key: "Delete",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
]));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
class MoveListToDifferentPosition {
constructor(root, listToMove, placeToMove, whereToMove, defaultIndentChars) {
this.root = root;
this.listToMove = listToMove;
this.placeToMove = placeToMove;
this.whereToMove = whereToMove;
this.defaultIndentChars = defaultIndentChars;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
if (this.listToMove === this.placeToMove) {
return;
}
this.stopPropagation = true;
this.updated = true;
const cursorAnchor = this.calculateCursorAnchor();
this.moveList();
this.changeIndent();
this.restoreCursor(cursorAnchor);
recalculateNumericBullets(this.root);
}
calculateCursorAnchor() {
const cursorLine = this.root.getCursor().line;
const lines = [
this.listToMove.getFirstLineContentStart().line,
this.listToMove.getLastLineContentEnd().line,
this.placeToMove.getFirstLineContentStart().line,
this.placeToMove.getLastLineContentEnd().line,
];
const listStartLine = Math.min(...lines);
const listEndLine = Math.max(...lines);
if (cursorLine < listStartLine || cursorLine > listEndLine) {
return null;
}
const cursor = this.root.getCursor();
const cursorList = this.root.getListUnderLine(cursor.line);
const cursorListStart = cursorList.getFirstLineContentStart();
const lineDiff = cursor.line - cursorListStart.line;
const chDiff = cursor.ch - cursorListStart.ch;
return { cursorList, lineDiff, chDiff };
}
moveList() {
this.listToMove.getParent().removeChild(this.listToMove);
switch (this.whereToMove) {
case "before":
this.placeToMove
.getParent()
.addBefore(this.placeToMove, this.listToMove);
break;
case "after":
this.placeToMove
.getParent()
.addAfter(this.placeToMove, this.listToMove);
break;
case "inside":
this.placeToMove.addBeforeAll(this.listToMove);
break;
}
}
changeIndent() {
const oldIndent = this.listToMove.getFirstLineIndent();
const newIndent = this.whereToMove === "inside"
? this.placeToMove.getFirstLineIndent() + this.defaultIndentChars
: this.placeToMove.getFirstLineIndent();
this.listToMove.unindentContent(0, oldIndent.length);
this.listToMove.indentContent(0, newIndent);
}
restoreCursor(cursorAnchor) {
if (cursorAnchor) {
const cursorListStart = cursorAnchor.cursorList.getFirstLineContentStart();
this.root.replaceCursor({
line: cursorListStart.line + cursorAnchor.lineDiff,
ch: cursorListStart.ch + cursorAnchor.chDiff,
});
}
else {
// When you move a list, the screen scrolls to the cursor.
// It is better to move the cursor into the viewport than let the screen scroll.
this.root.replaceCursor(this.listToMove.getLastLineContentEnd());
}
}
}
const BODY_CLASS = "outliner-plugin-dnd";
class DragAndDrop {
constructor(plugin, settings, obisidian, parser, operationPerformer) {
this.plugin = plugin;
this.settings = settings;
this.obisidian = obisidian;
this.parser = parser;
this.operationPerformer = operationPerformer;
this.preStart = null;
this.state = null;
this.handleSettingsChange = () => {
if (!isFeatureSupported()) {
return;
}
if (this.settings.dragAndDrop) {
document.body.classList.add(BODY_CLASS);
}
else {
document.body.classList.remove(BODY_CLASS);
}
};
this.handleMouseDown = (e) => {
if (!isFeatureSupported() ||
!this.settings.dragAndDrop ||
!isClickOnBullet(e)) {
return;
}
const view = getEditorViewFromHTMLElement(e.target);
if (!view) {
return;
}
e.preventDefault();
e.stopPropagation();
this.preStart = {
x: e.x,
y: e.y,
view,
};
};
this.handleMouseMove = (e) => {
if (this.preStart) {
this.startDragging();
}
if (this.state) {
this.detectAndDrawDropZone(e.x, e.y);
}
};
this.handleMouseUp = () => {
if (this.preStart) {
this.preStart = null;
}
if (this.state) {
this.stopDragging();
}
};
this.handleKeyDown = (e) => {
if (this.state && e.code === "Escape") {
this.cancelDragging();
}
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension([
draggingLinesStateField,
droppingLinesStateField,
]);
this.enableFeatureToggle();
this.createDropZone();
this.addEventListeners();
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () {
this.removeEventListeners();
this.removeDropZone();
this.disableFeatureToggle();
});
}
enableFeatureToggle() {
this.settings.onChange(this.handleSettingsChange);
this.handleSettingsChange();
}
disableFeatureToggle() {
this.settings.removeCallback(this.handleSettingsChange);
document.body.classList.remove(BODY_CLASS);
}
createDropZone() {
this.dropZone = document.createElement("div");
this.dropZone.classList.add("outliner-plugin-drop-zone");
this.dropZone.style.display = "none";
document.body.appendChild(this.dropZone);
}
removeDropZone() {
document.body.removeChild(this.dropZone);
this.dropZone = null;
}
addEventListeners() {
document.addEventListener("mousedown", this.handleMouseDown, {
capture: true,
});
document.addEventListener("mousemove", this.handleMouseMove);
document.addEventListener("mouseup", this.handleMouseUp);
document.addEventListener("keydown", this.handleKeyDown);
}
removeEventListeners() {
document.removeEventListener("mousedown", this.handleMouseDown, {
capture: true,
});
document.removeEventListener("mousemove", this.handleMouseMove);
document.removeEventListener("mouseup", this.handleMouseUp);
document.removeEventListener("keydown", this.handleKeyDown);
}
startDragging() {
const { x, y, view } = this.preStart;
this.preStart = null;
const editor = getEditorFromState(view.state);
const pos = editor.offsetToPos(view.posAtCoords({ x, y }));
const root = this.parser.parse(editor, pos);
const list = root.getListUnderLine(pos.line);
const state = new DragAndDropState(view, editor, root, list);
if (!state.hasDropVariants()) {
return;
}
this.state = state;
this.highlightDraggingLines();
}
detectAndDrawDropZone(x, y) {
this.state.calculateNearestDropVariant(x, y);
this.drawDropZone();
}
cancelDragging() {
this.state.dropVariant = null;
this.stopDragging();
}
stopDragging() {
this.unhightlightDraggingLines();
this.hideDropZone();
this.applyChanges();
this.state = null;
}
applyChanges() {
if (!this.state.dropVariant) {
return;
}
const { state } = this;
const { dropVariant, editor, root, list } = state;
const newRoot = this.parser.parse(editor, root.getContentStart());
if (!isSameRoots(root, newRoot)) {
new obsidian.Notice(`The item cannot be moved. The page content changed during the move.`, 5000);
return;
}
this.operationPerformer.eval(root, new MoveListToDifferentPosition(root, list, dropVariant.placeToMove, dropVariant.whereToMove, this.obisidian.getDefaultIndentChars()), editor);
}
highlightDraggingLines() {
const { state } = this;
const { list, editor, view } = state;
const lines = [];
const fromLine = list.getFirstLineContentStart().line;
const tillLine = list.getContentEndIncludingChildren().line;
for (let i = fromLine; i <= tillLine; i++) {
lines.push(editor.posToOffset({ line: i, ch: 0 }));
}
view.dispatch({
effects: [dndStarted.of(lines)],
});
document.body.classList.add("outliner-plugin-dragging");
}
unhightlightDraggingLines() {
document.body.classList.remove("outliner-plugin-dragging");
this.state.view.dispatch({
effects: [dndEnded.of()],
});
}
drawDropZone() {
const { state } = this;
const { view, editor, list, dropVariant } = state;
const width = Math.round(view.contentDOM.offsetWidth -
(dropVariant.left -
view.coordsAtPos(editor.posToOffset({
line: list.getFirstLineContentStart().line,
ch: 0,
})).left));
this.dropZone.style.display = "block";
this.dropZone.style.top = dropVariant.top + "px";
this.dropZone.style.left = dropVariant.left + "px";
this.dropZone.style.width = width + "px";
if (dropVariant.whereToMove === "before" &&
!this.dropZone.classList.contains("outliner-plugin-drop-zone-before")) {
this.dropZone.classList.remove("outliner-plugin-drop-zone-after");
this.dropZone.classList.add("outliner-plugin-drop-zone-before");
}
else if ((dropVariant.whereToMove === "after" ||
dropVariant.whereToMove === "inside") &&
!this.dropZone.classList.contains("outliner-plugin-drop-zone-after")) {
this.dropZone.classList.remove("outliner-plugin-drop-zone-before");
this.dropZone.classList.add("outliner-plugin-drop-zone-after");
}
const newParent = dropVariant.whereToMove === "inside"
? dropVariant.placeToMove
: dropVariant.placeToMove.getParent();
const newParentIsRootList = !newParent.getParent();
this.state.view.dispatch({
effects: [
dndMoved.of(newParentIsRootList
? null
: editor.posToOffset({
line: newParent.getFirstLineContentStart().line,
ch: 0,
})),
],
});
}
hideDropZone() {
this.dropZone.style.display = "none";
}
}
class DragAndDropState {
constructor(view, editor, root, list) {
this.view = view;
this.editor = editor;
this.root = root;
this.list = list;
this.dropVariants = new Map();
this.dropVariant = null;
this.collectDropVariants();
}
getDropVariants() {
return Array.from(this.dropVariants.values());
}
hasDropVariants() {
return this.dropVariants.size > 0;
}
calculateNearestDropVariant(x, y) {
const { view, editor } = this;
this.dropVariant = this.getDropVariants()
.map((v) => {
const { placeToMove } = v;
switch (v.whereToMove) {
case "before":
case "after":
v.left = Math.round(view.coordsAtPos(editor.posToOffset({
line: placeToMove.getFirstLineContentStart().line,
ch: placeToMove.getFirstLineIndent().length,
})).left);
break;
case "inside":
v.left = Math.round(view.coordsAtPos(editor.posToOffset({
line: placeToMove.getFirstLineContentStart().line,
ch: placeToMove.getFirstLineIndent().length,
})).left +
view.defaultCharacterWidth * 2);
break;
}
switch (v.whereToMove) {
case "before":
v.top = Math.round(view.coordsAtPos(editor.posToOffset(placeToMove.getFirstLineContentStart())).top);
break;
case "after":
case "inside":
v.top = Math.round(view.coordsAtPos(editor.posToOffset(placeToMove.getContentEndIncludingChildren())).top + view.defaultLineHeight);
break;
}
v.dist = Math.abs(Math.hypot(y - v.top, x - v.left));
return v;
})
.sort((a, b) => {
return a.dist - b.dist;
})
.first();
}
addDropVariant(v) {
this.dropVariants.set(`${v.line} ${v.level}`, v);
}
collectDropVariants() {
const visit = (lists) => {
for (const placeToMove of lists) {
const lineBefore = placeToMove.getFirstLineContentStart().line;
const lineAfter = placeToMove.getContentEndIncludingChildren().line + 1;
const level = placeToMove.getLevel();
this.addDropVariant({
line: lineBefore,
level,
left: 0,
top: 0,
dist: 0,
placeToMove,
whereToMove: "before",
});
this.addDropVariant({
line: lineAfter,
level,
left: 0,
top: 0,
dist: 0,
placeToMove,
whereToMove: "after",
});
if (placeToMove === this.list) {
continue;
}
if (placeToMove.isEmpty()) {
this.addDropVariant({
line: lineAfter,
level: level + 1,
left: 0,
top: 0,
dist: 0,
placeToMove,
whereToMove: "inside",
});
}
else {
visit(placeToMove.getChildren());
}
}
};
visit(this.root.getChildren());
}
}
const dndStarted = state.StateEffect.define({
map: (lines, change) => lines.map((l) => change.mapPos(l)),
});
const dndMoved = state.StateEffect.define({
map: (line, change) => (line !== null ? change.mapPos(line) : line),
});
const dndEnded = state.StateEffect.define();
const draggingLineDecoration = view.Decoration.line({
class: "outliner-plugin-dragging-line",
});
const droppingLineDecoration = view.Decoration.line({
class: "outliner-plugin-dropping-line",
});
const draggingLinesStateField = state.StateField.define({
create: () => view.Decoration.none,
update: (dndState, tr) => {
dndState = dndState.map(tr.changes);
for (const e of tr.effects) {
if (e.is(dndStarted)) {
dndState = dndState.update({
add: e.value.map((l) => draggingLineDecoration.range(l, l)),
});
}
if (e.is(dndEnded)) {
dndState = view.Decoration.none;
}
}
return dndState;
},
provide: (f) => view.EditorView.decorations.from(f),
});
const droppingLinesStateField = state.StateField.define({
create: () => view.Decoration.none,
update: (dndDroppingState, tr) => {
dndDroppingState = dndDroppingState.map(tr.changes);
for (const e of tr.effects) {
if (e.is(dndMoved)) {
dndDroppingState =
e.value === null
? view.Decoration.none
: view.Decoration.set(droppingLineDecoration.range(e.value, e.value));
}
if (e.is(dndEnded)) {
dndDroppingState = view.Decoration.none;
}
}
return dndDroppingState;
},
provide: (f) => view.EditorView.decorations.from(f),
});
function getEditorViewFromHTMLElement(e) {
while (e && !e.classList.contains("cm-editor")) {
e = e.parentElement;
}
if (!e) {
return null;
}
return view.EditorView.findFromDOM(e);
}
function isClickOnBullet(e) {
let el = e.target;
while (el) {
if (el.classList.contains("cm-formatting-list") ||
el.classList.contains("cm-fold-indicator") ||
el.classList.contains("task-list-item-checkbox")) {
return true;
}
el = el.parentElement;
}
return false;
}
function isSameRoots(a, b) {
const [aStart, aEnd] = a.getContentRange();
const [bStart, bEnd] = b.getContentRange();
if (cmpPos(aStart, bStart) !== 0 || cmpPos(aEnd, bEnd) !== 0) {
return false;
}
return a.print() === b.print();
}
function isFeatureSupported() {
return obsidian.Platform.isDesktop;
}
class KeepCursorOutsideFoldedLines {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
const cursor = root.getCursor();
const list = root.getListUnderCursor();
if (!list.isFolded()) {
return;
}
const foldRoot = list.getTopFoldRoot();
const firstLineEnd = foldRoot.getLinesInfo()[0].to;
if (cursor.line > firstLineEnd.line) {
this.updated = true;
this.stopPropagation = true;
root.replaceCursor(firstLineEnd);
}
}
}
class KeepCursorWithinListContent {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
const cursor = root.getCursor();
const list = root.getListUnderCursor();
const contentStart = list.getFirstLineContentStartAfterCheckbox();
const linePrefix = contentStart.line === cursor.line
? contentStart.ch
: list.getNotesIndent().length;
if (cursor.ch < linePrefix) {
this.updated = true;
this.stopPropagation = true;
root.replaceCursor({
line: cursor.line,
ch: linePrefix,
});
}
}
}
class EditorSelectionsBehaviourOverride {
constructor(plugin, settings, parser, operationPerformer) {
this.plugin = plugin;
this.settings = settings;
this.parser = parser;
this.operationPerformer = operationPerformer;
this.transactionExtender = (tr) => {
if (this.settings.keepCursorWithinContent === "never" || !tr.selection) {
return null;
}
const editor = getEditorFromState(tr.startState);
setTimeout(() => {
this.handleSelectionsChanges(editor);
}, 0);
return null;
};
this.handleSelectionsChanges = (editor) => {
const root = this.parser.parse(editor);
if (!root) {
return;
}
{
const { shouldStopPropagation } = this.operationPerformer.eval(root, new KeepCursorOutsideFoldedLines(root), editor);
if (shouldStopPropagation) {
return;
}
}
this.operationPerformer.eval(root, new KeepCursorWithinListContent(root), editor);
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(state.EditorState.transactionExtender.of(this.transactionExtender));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
const checkboxRe = `\\[[^\\[\\]]\\][ \t]`;
function isEmptyLineOrEmptyCheckbox(line) {
return line === "" || line === "[ ] ";
}
class CreateNewItem {
constructor(root, defaultIndentChars, getZoomRange) {
this.root = root;
this.defaultIndentChars = defaultIndentChars;
this.getZoomRange = getZoomRange;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleSelection()) {
return;
}
const selection = root.getSelection();
if (!selection || selection.anchor.line !== selection.head.line) {
return;
}
const list = root.getListUnderCursor();
const lines = list.getLinesInfo();
if (lines.length === 1 && isEmptyLineOrEmptyCheckbox(lines[0].text)) {
return;
}
const cursor = root.getCursor();
const lineUnderCursor = lines.find((l) => l.from.line === cursor.line);
if (cursor.ch < lineUnderCursor.from.ch) {
return;
}
const { oldLines, newLines } = lines.reduce((acc, line) => {
if (cursor.line > line.from.line) {
acc.oldLines.push(line.text);
}
else if (cursor.line === line.from.line) {
const left = line.text.slice(0, selection.from - line.from.ch);
const right = line.text.slice(selection.to - line.from.ch);
acc.oldLines.push(left);
acc.newLines.push(right);
}
else if (cursor.line < line.from.line) {
acc.newLines.push(line.text);
}
return acc;
}, {
oldLines: [],
newLines: [],
});
const codeBlockBacticks = oldLines.join("\n").split("```").length - 1;
const isInsideCodeblock = codeBlockBacticks > 0 && codeBlockBacticks % 2 !== 0;
if (isInsideCodeblock) {
return;
}
this.stopPropagation = true;
this.updated = true;
const zoomRange = this.getZoomRange.getZoomRange();
const listIsZoomingRoot = Boolean(zoomRange &&
list.getFirstLineContentStart().line >= zoomRange.from.line &&
list.getLastLineContentEnd().line <= zoomRange.from.line);
const hasChildren = !list.isEmpty();
const childIsFolded = list.isFoldRoot();
const endPos = list.getLastLineContentEnd();
const endOfLine = cursor.line === endPos.line && cursor.ch === endPos.ch;
const onChildLevel = listIsZoomingRoot || (hasChildren && !childIsFolded && endOfLine);
const indent = onChildLevel
? hasChildren
? list.getChildren()[0].getFirstLineIndent()
: list.getFirstLineIndent() + this.defaultIndentChars
: list.getFirstLineIndent();
const bullet = onChildLevel && hasChildren
? list.getChildren()[0].getBullet()
: list.getBullet();
const spaceAfterBullet = onChildLevel && hasChildren
? list.getChildren()[0].getSpaceAfterBullet()
: list.getSpaceAfterBullet();
const prefix = oldLines[0].match(checkboxRe) ? "[ ] " : "";
const newList = new List(list.getRoot(), indent, bullet, prefix, spaceAfterBullet, prefix + newLines.shift(), false);
if (newLines.length > 0) {
newList.setNotesIndent(list.getNotesIndent());
for (const line of newLines) {
newList.addLine(line);
}
}
if (onChildLevel) {
list.addBeforeAll(newList);
}
else {
if (!childIsFolded || !endOfLine) {
const children = list.getChildren();
for (const child of children) {
list.removeChild(child);
newList.addAfterAll(child);
}
}
list.getParent().addAfter(list, newList);
}
list.replaceLines(oldLines);
const newListStart = newList.getFirstLineContentStart();
root.replaceCursor({
line: newListStart.line,
ch: newListStart.ch + prefix.length,
});
recalculateNumericBullets(root);
}
}
class OutdentList {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
this.stopPropagation = true;
const list = root.getListUnderCursor();
const parent = list.getParent();
const grandParent = parent.getParent();
if (!grandParent) {
return;
}
this.updated = true;
const listStartLineBefore = root.getContentLinesRangeOf(list)[0];
const indentRmFrom = parent.getFirstLineIndent().length;
const indentRmTill = list.getFirstLineIndent().length;
parent.removeChild(list);
grandParent.addAfter(parent, list);
list.unindentContent(indentRmFrom, indentRmTill);
const listStartLineAfter = root.getContentLinesRangeOf(list)[0];
const lineDiff = listStartLineAfter - listStartLineBefore;
const chDiff = indentRmTill - indentRmFrom;
const cursor = root.getCursor();
root.replaceCursor({
line: cursor.line + lineDiff,
ch: cursor.ch - chDiff,
});
recalculateNumericBullets(root);
}
}
class OutdentListIfItsEmpty {
constructor(root) {
this.root = root;
this.outdentList = new OutdentList(root);
}
shouldStopPropagation() {
return this.outdentList.shouldStopPropagation();
}
shouldUpdate() {
return this.outdentList.shouldUpdate();
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
const list = root.getListUnderCursor();
const lines = list.getLines();
if (lines.length > 1 ||
!isEmptyLineOrEmptyCheckbox(lines[0]) ||
list.getLevel() === 1) {
return;
}
this.outdentList.perform();
}
}
class EnterBehaviourOverride {
constructor(plugin, settings, imeDetector, obsidianSettings, parser, operationPerformer) {
this.plugin = plugin;
this.settings = settings;
this.imeDetector = imeDetector;
this.obsidianSettings = obsidianSettings;
this.parser = parser;
this.operationPerformer = operationPerformer;
this.check = () => {
return this.settings.overrideEnterBehaviour && !this.imeDetector.isOpened();
};
this.run = (editor) => {
const root = this.parser.parse(editor);
if (!root) {
return {
shouldUpdate: false,
shouldStopPropagation: false,
};
}
{
const res = this.operationPerformer.eval(root, new OutdentListIfItsEmpty(root), editor);
if (res.shouldStopPropagation) {
return res;
}
}
{
const defaultIndentChars = this.obsidianSettings.getDefaultIndentChars();
const zoomRange = editor.getZoomRange();
const getZoomRange = {
getZoomRange: () => zoomRange,
};
const res = this.operationPerformer.eval(root, new CreateNewItem(root, defaultIndentChars, getZoomRange), editor);
if (res.shouldUpdate && zoomRange) {
editor.tryRefreshZoom(zoomRange.from.line);
}
return res;
}
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(state.Prec.highest(view.keymap.of([
{
key: "Enter",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
])));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
function createEditorCallback(cb) {
return (editor) => {
const myEditor = new MyEditor(editor);
const shouldStopPropagation = cb(myEditor);
if (!shouldStopPropagation &&
window.event &&
window.event.type === "keydown") {
myEditor.triggerOnKeyDown(window.event);
}
};
}
class ListsFoldingCommands {
constructor(plugin, obsidianSettings) {
this.plugin = plugin;
this.obsidianSettings = obsidianSettings;
this.fold = (editor) => {
return this.setFold(editor, "fold");
};
this.unfold = (editor) => {
return this.setFold(editor, "unfold");
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.addCommand({
id: "fold",
icon: "chevrons-down-up",
name: "Fold the list",
editorCallback: createEditorCallback(this.fold),
hotkeys: [
{
modifiers: ["Mod"],
key: "ArrowUp",
},
],
});
this.plugin.addCommand({
id: "unfold",
icon: "chevrons-up-down",
name: "Unfold the list",
editorCallback: createEditorCallback(this.unfold),
hotkeys: [
{
modifiers: ["Mod"],
key: "ArrowDown",
},
],
});
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
setFold(editor, type) {
if (!this.obsidianSettings.getFoldSettings().foldIndent) {
new obsidian.Notice(`Unable to ${type} because folding is disabled. Please enable "Fold indent" in Obsidian settings.`, 5000);
return true;
}
const cursor = editor.getCursor();
if (type === "fold") {
editor.fold(cursor.line);
}
else {
editor.unfold(cursor.line);
}
return true;
}
}
class IndentList {
constructor(root, defaultIndentChars) {
this.root = root;
this.defaultIndentChars = defaultIndentChars;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
this.stopPropagation = true;
const list = root.getListUnderCursor();
const parent = list.getParent();
const prev = parent.getPrevSiblingOf(list);
if (!prev) {
return;
}
this.updated = true;
const listStartLineBefore = root.getContentLinesRangeOf(list)[0];
const indentPos = list.getFirstLineIndent().length;
let indentChars = "";
if (indentChars === "" && !prev.isEmpty()) {
indentChars = prev
.getChildren()[0]
.getFirstLineIndent()
.slice(prev.getFirstLineIndent().length);
}
if (indentChars === "") {
indentChars = list
.getFirstLineIndent()
.slice(parent.getFirstLineIndent().length);
}
if (indentChars === "" && !list.isEmpty()) {
indentChars = list.getChildren()[0].getFirstLineIndent();
}
if (indentChars === "") {
indentChars = this.defaultIndentChars;
}
parent.removeChild(list);
prev.addAfterAll(list);
list.indentContent(indentPos, indentChars);
const listStartLineAfter = root.getContentLinesRangeOf(list)[0];
const lineDiff = listStartLineAfter - listStartLineBefore;
const cursor = root.getCursor();
root.replaceCursor({
line: cursor.line + lineDiff,
ch: cursor.ch + indentChars.length,
});
recalculateNumericBullets(root);
}
}
class MoveListDown {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
this.stopPropagation = true;
const list = root.getListUnderCursor();
const parent = list.getParent();
const grandParent = parent.getParent();
const next = parent.getNextSiblingOf(list);
const listStartLineBefore = root.getContentLinesRangeOf(list)[0];
if (!next && grandParent) {
const newParent = grandParent.getNextSiblingOf(parent);
if (newParent) {
this.updated = true;
parent.removeChild(list);
newParent.addBeforeAll(list);
}
}
else if (next) {
this.updated = true;
parent.removeChild(list);
parent.addAfter(next, list);
}
if (!this.updated) {
return;
}
const listStartLineAfter = root.getContentLinesRangeOf(list)[0];
const lineDiff = listStartLineAfter - listStartLineBefore;
const cursor = root.getCursor();
root.replaceCursor({
line: cursor.line + lineDiff,
ch: cursor.ch,
});
recalculateNumericBullets(root);
}
}
class MoveListUp {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
this.stopPropagation = true;
const list = root.getListUnderCursor();
const parent = list.getParent();
const grandParent = parent.getParent();
const prev = parent.getPrevSiblingOf(list);
const listStartLineBefore = root.getContentLinesRangeOf(list)[0];
if (!prev && grandParent) {
const newParent = grandParent.getPrevSiblingOf(parent);
if (newParent) {
this.updated = true;
parent.removeChild(list);
newParent.addAfterAll(list);
}
}
else if (prev) {
this.updated = true;
parent.removeChild(list);
parent.addBefore(prev, list);
}
if (!this.updated) {
return;
}
const listStartLineAfter = root.getContentLinesRangeOf(list)[0];
const lineDiff = listStartLineAfter - listStartLineBefore;
const cursor = root.getCursor();
root.replaceCursor({
line: cursor.line + lineDiff,
ch: cursor.ch,
});
recalculateNumericBullets(root);
}
}
class ListsMovementCommands {
constructor(plugin, obsidianSettings, operationPerformer) {
this.plugin = plugin;
this.obsidianSettings = obsidianSettings;
this.operationPerformer = operationPerformer;
this.moveListDown = (editor) => {
const { shouldStopPropagation } = this.operationPerformer.perform((root) => new MoveListDown(root), editor);
return shouldStopPropagation;
};
this.moveListUp = (editor) => {
const { shouldStopPropagation } = this.operationPerformer.perform((root) => new MoveListUp(root), editor);
return shouldStopPropagation;
};
this.indentList = (editor) => {
const { shouldStopPropagation } = this.operationPerformer.perform((root) => new IndentList(root, this.obsidianSettings.getDefaultIndentChars()), editor);
return shouldStopPropagation;
};
this.outdentList = (editor) => {
const { shouldStopPropagation } = this.operationPerformer.perform((root) => new OutdentList(root), editor);
return shouldStopPropagation;
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.addCommand({
id: "move-list-item-up",
icon: "arrow-up",
name: "Move list and sublists up",
editorCallback: createEditorCallback(this.moveListUp),
hotkeys: [
{
modifiers: ["Mod", "Shift"],
key: "ArrowUp",
},
],
});
this.plugin.addCommand({
id: "move-list-item-down",
icon: "arrow-down",
name: "Move list and sublists down",
editorCallback: createEditorCallback(this.moveListDown),
hotkeys: [
{
modifiers: ["Mod", "Shift"],
key: "ArrowDown",
},
],
});
this.plugin.addCommand({
id: "indent-list",
icon: "indent",
name: "Indent the list and sublists",
editorCallback: createEditorCallback(this.indentList),
hotkeys: [],
});
this.plugin.addCommand({
id: "outdent-list",
icon: "outdent",
name: "Outdent the list and sublists",
editorCallback: createEditorCallback(this.outdentList),
hotkeys: [],
});
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
class DeleteTillCurrentLineContentStart {
constructor(root) {
this.root = root;
this.stopPropagation = false;
this.updated = false;
}
shouldStopPropagation() {
return this.stopPropagation;
}
shouldUpdate() {
return this.updated;
}
perform() {
const { root } = this;
if (!root.hasSingleCursor()) {
return;
}
this.stopPropagation = true;
this.updated = true;
const cursor = root.getCursor();
const list = root.getListUnderCursor();
const lines = list.getLinesInfo();
const lineNo = lines.findIndex((l) => l.from.line === cursor.line);
lines[lineNo].text = lines[lineNo].text.slice(cursor.ch - lines[lineNo].from.ch);
list.replaceLines(lines.map((l) => l.text));
root.replaceCursor(lines[lineNo].from);
}
}
class MetaBackspaceBehaviourOverride {
constructor(plugin, settings, imeDetector, operationPerformer) {
this.plugin = plugin;
this.settings = settings;
this.imeDetector = imeDetector;
this.operationPerformer = operationPerformer;
this.check = () => {
return (this.settings.keepCursorWithinContent !== "never" &&
!this.imeDetector.isOpened());
};
this.run = (editor) => {
return this.operationPerformer.perform((root) => new DeleteTillCurrentLineContentStart(root), editor);
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(view.keymap.of([
{
mac: "m-Backspace",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
]));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
class ReleaseNotesModal extends obsidian.Modal {
constructor(plugin, title, content, cb) {
super(plugin.app);
this.plugin = plugin;
this.title = title;
this.content = content;
this.cb = cb;
}
onOpen() {
return __awaiter(this, void 0, void 0, function* () {
this.titleEl.setText(this.title);
obsidian.MarkdownRenderer.renderMarkdown(this.content, this.contentEl, "", this.plugin);
});
}
onClose() {
this.cb();
}
}
function compareReleases(a, b) {
const [aMajor, aMinor, aPatch] = a.split(".", 3).map(Number);
const [bMajor, bMinor, bPatch] = b.split(".", 3).map(Number);
if (aMajor === bMajor) {
if (aMinor === bMinor) {
return aPatch - bPatch;
}
return aMinor - bMinor;
}
return aMajor - bMajor;
}
function parseChangelog() {
const markdown = "## 4.5.0\n\n### Drag-and-Drop (Experimental)\n\nNow you can drag and drop items using your mouse! 🎉\n\nThis feature is experimental and is disabled by default. To enable this feature, open the plugin settings and turn on the `Drag-and-Drop (Experimental)` setting.\n\nIf you find a bug, please report the [issue](https://github.com/vslinko/obsidian-outliner/issues). Leave your other feedback [here](https://github.com/vslinko/obsidian-outliner/discussions/190).\n\n<img src=\"https://raw.githubusercontent.com/vslinko/obsidian-outliner/main/demo3.gif\" style=\"max-width: 100%\" />\n";
const releaseNotes = [];
let version;
let content = "";
for (const line of markdown.split("\n")) {
const versionHeaderMatches = /^#+\s+(\d+\.\d+\.\d+)$/.exec(line);
if (versionHeaderMatches) {
if (version && content.trim().length > 0) {
releaseNotes.push([version, content]);
}
version = versionHeaderMatches[1];
content = line;
content += "\n";
}
else {
content += line;
content += "\n";
}
}
if (version && content.trim().length > 0) {
releaseNotes.push([version, content]);
}
return releaseNotes;
}
class ReleaseNotesAnnouncement {
constructor(plugin, settings) {
this.plugin = plugin;
this.settings = settings;
this.modal = null;
this.showModal = (previousRelease = null) => {
let releaseNotes = "";
for (const [version, content] of parseChangelog()) {
if (compareReleases(version, previousRelease || "0.0.0") > 0) {
releaseNotes += content;
}
}
if (releaseNotes.trim().length === 0) {
return;
}
const modalTitle = `Welcome to Obsidian Outliner ${"4.6.7"}`;
this.modal = new ReleaseNotesModal(this.plugin, modalTitle, releaseNotes, this.handleClose);
this.modal.open();
};
this.handleClose = () => __awaiter(this, void 0, void 0, function* () {
if (!this.modal) {
return;
}
this.settings.previousRelease = "4.6.7";
yield this.settings.save();
});
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.addCommand({
id: "show-release-notes",
name: "Show Release Notes",
callback: this.showModal,
});
this.showModal(this.settings.previousRelease);
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.modal) {
return;
}
const modal = this.modal;
this.modal = null;
modal.close();
});
}
}
class ObsidianOutlinerPluginSettingTab extends obsidian.PluginSettingTab {
constructor(app, plugin, settings) {
super(app, plugin);
this.settings = settings;
}
display() {
const { containerEl } = this;
containerEl.empty();
new obsidian.Setting(containerEl)
.setName("Stick the cursor to the content")
.setDesc("Don't let the cursor move to the bullet position.")
.addDropdown((dropdown) => {
dropdown
.addOptions({
never: "Never",
"bullet-only": "Stick cursor out of bullets",
"bullet-and-checkbox": "Stick cursor out of bullets and checkboxes",
})
.setValue(this.settings.keepCursorWithinContent)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.keepCursorWithinContent = value;
yield this.settings.save();
}));
});
new obsidian.Setting(containerEl)
.setName("Enhance the Tab key")
.setDesc("Make Tab and Shift-Tab behave the same as other outliners.")
.addToggle((toggle) => {
toggle
.setValue(this.settings.overrideTabBehaviour)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.overrideTabBehaviour = value;
yield this.settings.save();
}));
});
new obsidian.Setting(containerEl)
.setName("Enhance the Enter key")
.setDesc("Make the Enter key behave the same as other outliners.")
.addToggle((toggle) => {
toggle
.setValue(this.settings.overrideEnterBehaviour)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.overrideEnterBehaviour = value;
yield this.settings.save();
}));
});
new obsidian.Setting(containerEl)
.setName("Enhance the Ctrl+A or Cmd+A behavior")
.setDesc("Press the hotkey once to select the current list item. Press the hotkey twice to select the entire list.")
.addToggle((toggle) => {
toggle
.setValue(this.settings.overrideSelectAllBehaviour)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.overrideSelectAllBehaviour = value;
yield this.settings.save();
}));
});
new obsidian.Setting(containerEl)
.setName("Improve the style of your lists")
.setDesc("Styles are only compatible with built-in Obsidian themes and may not be compatible with other themes.")
.addToggle((toggle) => {
toggle
.setValue(this.settings.betterListsStyles)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.betterListsStyles = value;
yield this.settings.save();
}));
});
new obsidian.Setting(containerEl)
.setName("Draw vertical indentation lines")
.addToggle((toggle) => {
toggle.setValue(this.settings.verticalLines).onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.verticalLines = value;
yield this.settings.save();
}));
});
new obsidian.Setting(containerEl)
.setName("Vertical indentation line click action")
.addDropdown((dropdown) => {
dropdown
.addOptions({
none: "None",
"zoom-in": "Zoom In",
"toggle-folding": "Toggle Folding",
})
.setValue(this.settings.verticalLinesAction)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.verticalLinesAction = value;
yield this.settings.save();
}));
});
new obsidian.Setting(containerEl)
.setName("Drag-and-Drop (Experimental)")
.addToggle((toggle) => {
toggle.setValue(this.settings.dragAndDrop).onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.dragAndDrop = value;
yield this.settings.save();
}));
});
new obsidian.Setting(containerEl)
.setName("Debug mode")
.setDesc("Open DevTools (Command+Option+I or Control+Shift+I) to copy the debug logs.")
.addToggle((toggle) => {
toggle.setValue(this.settings.debug).onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.debug = value;
yield this.settings.save();
}));
});
}
}
class SettingsTab {
constructor(plugin, settings) {
this.plugin = plugin;
this.settings = settings;
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.addSettingTab(new ObsidianOutlinerPluginSettingTab(this.plugin.app, this.plugin, this.settings));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
class ShiftTabBehaviourOverride {
constructor(plugin, imeDetector, settings, operationPerformer) {
this.plugin = plugin;
this.imeDetector = imeDetector;
this.settings = settings;
this.operationPerformer = operationPerformer;
this.check = () => {
return this.settings.overrideTabBehaviour && !this.imeDetector.isOpened();
};
this.run = (editor) => {
return this.operationPerformer.perform((root) => new OutdentList(root), editor);
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(state.Prec.highest(view.keymap.of([
{
key: "s-Tab",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
])));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
class TabBehaviourOverride {
constructor(plugin, imeDetector, obsidianSettings, settings, operationPerformer) {
this.plugin = plugin;
this.imeDetector = imeDetector;
this.obsidianSettings = obsidianSettings;
this.settings = settings;
this.operationPerformer = operationPerformer;
this.check = () => {
return this.settings.overrideTabBehaviour && !this.imeDetector.isOpened();
};
this.run = (editor) => {
return this.operationPerformer.perform((root) => new IndentList(root, this.obsidianSettings.getDefaultIndentChars()), editor);
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.plugin.registerEditorExtension(state.Prec.highest(view.keymap.of([
{
key: "Tab",
run: createKeymapRunCallback({
check: this.check,
run: this.run,
}),
},
])));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () { });
}
}
const VERTICAL_LINES_BODY_CLASS = "outliner-plugin-vertical-lines";
class VerticalLinesPluginValue {
constructor(settings, obsidianSettings, parser, view) {
this.settings = settings;
this.obsidianSettings = obsidianSettings;
this.parser = parser;
this.view = view;
this.lineElements = [];
this.waitForEditor = () => {
const editor = getEditorFromState(this.view.state);
if (!editor) {
setTimeout(this.waitForEditor, 0);
return;
}
this.editor = editor;
this.scheduleRecalculate();
};
this.onScroll = (e) => {
const { scrollLeft, scrollTop } = e.target;
this.scroller.scrollTo(scrollLeft, scrollTop);
};
this.scheduleRecalculate = () => {
clearTimeout(this.scheduled);
this.scheduled = setTimeout(this.calculate, 0);
};
this.calculate = () => {
this.lines = [];
if (this.settings.verticalLines &&
this.obsidianSettings.isDefaultThemeEnabled() &&
this.view.viewportLineBlocks.length > 0 &&
this.view.visibleRanges.length > 0) {
const fromLine = this.editor.offsetToPos(this.view.viewport.from).line;
const toLine = this.editor.offsetToPos(this.view.viewport.to).line;
const lists = this.parser.parseRange(this.editor, fromLine, toLine);
for (const list of lists) {
this.lastLine = list.getContentEnd().line;
for (const c of list.getChildren()) {
this.recursive(c);
}
}
this.lines.sort((a, b) => a.top === b.top ? a.left - b.left : a.top - b.top);
}
this.updateDom();
};
this.onClick = (e) => {
e.preventDefault();
const line = this.lines[Number(e.target.dataset.index)];
switch (this.settings.verticalLinesAction) {
case "zoom-in":
this.zoomIn(line);
break;
case "toggle-folding":
this.toggleFolding(line);
break;
}
};
this.view.scrollDOM.addEventListener("scroll", this.onScroll);
this.settings.onChange(this.scheduleRecalculate);
this.prepareDom();
this.waitForEditor();
}
prepareDom() {
this.contentContainer = document.createElement("div");
this.contentContainer.classList.add("outliner-plugin-list-lines-content-container");
this.scroller = document.createElement("div");
this.scroller.classList.add("outliner-plugin-list-lines-scroller");
this.scroller.appendChild(this.contentContainer);
this.view.dom.appendChild(this.scroller);
}
update(update) {
if (update.docChanged ||
update.viewportChanged ||
update.geometryChanged ||
update.transactions.some((tr) => tr.reconfigured)) {
this.scheduleRecalculate();
}
}
getNextSibling(list) {
let listTmp = list;
let p = listTmp.getParent();
while (p) {
const nextSibling = p.getNextSiblingOf(listTmp);
if (nextSibling) {
return nextSibling;
}
listTmp = p;
p = listTmp.getParent();
}
return null;
}
recursive(list, parentCtx = {}) {
const children = list.getChildren();
if (children.length === 0) {
return;
}
const fromOffset = this.editor.posToOffset({
line: list.getFirstLineContentStart().line,
ch: list.getFirstLineIndent().length,
});
const nextSibling = this.getNextSibling(list);
const tillOffset = this.editor.posToOffset({
line: nextSibling
? nextSibling.getFirstLineContentStart().line - 1
: this.lastLine,
ch: 0,
});
let visibleFrom = this.view.visibleRanges[0].from;
let visibleTo = this.view.visibleRanges[this.view.visibleRanges.length - 1].to;
const zoomRange = this.editor.getZoomRange();
if (zoomRange) {
visibleFrom = Math.max(visibleFrom, this.editor.posToOffset(zoomRange.from));
visibleTo = Math.min(visibleTo, this.editor.posToOffset(zoomRange.to));
}
if (fromOffset > visibleTo || tillOffset < visibleFrom) {
return;
}
const coords = this.view.coordsAtPos(fromOffset, 1);
if (parentCtx.rootLeft === undefined) {
parentCtx.rootLeft = coords.left;
}
const left = Math.floor(coords.right - parentCtx.rootLeft);
const top = visibleFrom > 0 && fromOffset < visibleFrom
? -20
: this.view.lineBlockAt(fromOffset).top;
const bottom = tillOffset > visibleTo
? this.view.lineBlockAt(visibleTo - 1).bottom
: this.view.lineBlockAt(tillOffset).bottom;
const height = bottom - top;
if (height > 0 && !list.isFolded()) {
const nextSibling = list.getParent().getNextSiblingOf(list);
const hasNextSibling = !!nextSibling &&
this.editor.posToOffset(nextSibling.getFirstLineContentStart()) <=
visibleTo;
this.lines.push({
top,
left,
height: `calc(${height}px ${hasNextSibling ? "- 1.5em" : "- 2em"})`,
list,
});
}
for (const child of children) {
if (!child.isEmpty()) {
this.recursive(child, parentCtx);
}
}
}
zoomIn(line) {
const editor = getEditorFromState(this.view.state);
editor.zoomIn(line.list.getFirstLineContentStart().line);
}
toggleFolding(line) {
const { list } = line;
if (list.isEmpty()) {
return;
}
let needToUnfold = true;
const linesToToggle = [];
for (const c of list.getChildren()) {
if (c.isEmpty()) {
continue;
}
if (!c.isFolded()) {
needToUnfold = false;
}
linesToToggle.push(c.getFirstLineContentStart().line);
}
const editor = getEditorFromState(this.view.state);
for (const l of linesToToggle) {
if (needToUnfold) {
editor.unfold(l);
}
else {
editor.fold(l);
}
}
}
updateDom() {
const cmScroll = this.view.scrollDOM;
const cmContent = this.view.contentDOM;
const cmContentContainer = cmContent.parentElement;
const cmSizer = cmContentContainer.parentElement;
/**
* Obsidian can add additional elements into Content Manager.
* The most obvious case is the 'embedded-backlinks' core plugin that adds a menu inside a Content Manager.
* We must take heights of all of these elements into account
* to be able to calculate the correct size of lines' container.
*/
let cmSizerChildrenSumHeight = 0;
for (let i = 0; i < cmSizer.children.length; i++) {
cmSizerChildrenSumHeight += cmSizer.children[i].clientHeight;
}
this.scroller.style.top = cmScroll.offsetTop + "px";
this.contentContainer.style.height = cmSizerChildrenSumHeight + "px";
this.contentContainer.style.marginLeft =
cmContentContainer.offsetLeft + "px";
this.contentContainer.style.marginTop =
cmContent.firstElementChild.offsetTop - 24 + "px";
for (let i = 0; i < this.lines.length; i++) {
if (this.lineElements.length === i) {
const e = document.createElement("div");
e.classList.add("outliner-plugin-list-line");
e.dataset.index = String(i);
e.addEventListener("mousedown", this.onClick);
this.contentContainer.appendChild(e);
this.lineElements.push(e);
}
const l = this.lines[i];
const e = this.lineElements[i];
e.style.top = l.top + "px";
e.style.left = l.left + "px";
e.style.height = l.height;
e.style.display = "block";
}
for (let i = this.lines.length; i < this.lineElements.length; i++) {
const e = this.lineElements[i];
e.style.top = "0px";
e.style.left = "0px";
e.style.height = "0px";
e.style.display = "none";
}
}
destroy() {
this.settings.removeCallback(this.scheduleRecalculate);
this.view.scrollDOM.removeEventListener("scroll", this.onScroll);
this.view.dom.removeChild(this.scroller);
clearTimeout(this.scheduled);
}
}
class VerticalLines {
constructor(plugin, settings, obsidianSettings, parser) {
this.plugin = plugin;
this.settings = settings;
this.obsidianSettings = obsidianSettings;
this.parser = parser;
this.updateBodyClass = () => {
const shouldExists = this.obsidianSettings.isDefaultThemeEnabled() &&
this.settings.verticalLines;
const exists = document.body.classList.contains(VERTICAL_LINES_BODY_CLASS);
if (shouldExists && !exists) {
document.body.classList.add(VERTICAL_LINES_BODY_CLASS);
}
if (!shouldExists && exists) {
document.body.classList.remove(VERTICAL_LINES_BODY_CLASS);
}
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.updateBodyClass();
this.updateBodyClassInterval = window.setInterval(() => {
this.updateBodyClass();
}, 1000);
this.plugin.registerEditorExtension(view.ViewPlugin.define((view) => new VerticalLinesPluginValue(this.settings, this.obsidianSettings, this.parser, view)));
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () {
clearInterval(this.updateBodyClassInterval);
document.body.classList.remove(VERTICAL_LINES_BODY_CLASS);
});
}
}
class ChangesApplicator {
apply(editor, prevRoot, newRoot) {
const changes = this.calculateChanges(editor, prevRoot, newRoot);
if (changes) {
const { replacement, changeFrom, changeTo } = changes;
const { unfold, fold } = this.calculateFoldingOprations(prevRoot, newRoot, changeFrom, changeTo);
for (const line of unfold) {
editor.unfold(line);
}
editor.replaceRange(replacement, changeFrom, changeTo);
for (const line of fold) {
editor.fold(line);
}
}
editor.setSelections(newRoot.getSelections());
}
calculateChanges(editor, prevRoot, newRoot) {
const rootRange = prevRoot.getContentRange();
const oldString = editor.getRange(rootRange[0], rootRange[1]);
const newString = newRoot.print();
const changeFrom = Object.assign({}, rootRange[0]);
const changeTo = Object.assign({}, rootRange[1]);
let oldTmp = oldString;
let newTmp = newString;
while (true) {
const nlIndex = oldTmp.lastIndexOf("\n");
if (nlIndex < 0) {
break;
}
const oldLine = oldTmp.slice(nlIndex);
const newLine = newTmp.slice(-oldLine.length);
if (oldLine !== newLine) {
break;
}
oldTmp = oldTmp.slice(0, -oldLine.length);
newTmp = newTmp.slice(0, -oldLine.length);
const nlIndex2 = oldTmp.lastIndexOf("\n");
changeTo.ch =
nlIndex2 >= 0 ? oldTmp.length - nlIndex2 - 1 : oldTmp.length;
changeTo.line--;
}
while (true) {
const nlIndex = oldTmp.indexOf("\n");
if (nlIndex < 0) {
break;
}
const oldLine = oldTmp.slice(0, nlIndex + 1);
const newLine = newTmp.slice(0, oldLine.length);
if (oldLine !== newLine) {
break;
}
changeFrom.line++;
oldTmp = oldTmp.slice(oldLine.length);
newTmp = newTmp.slice(oldLine.length);
}
if (oldTmp === newTmp) {
return null;
}
return {
replacement: newTmp,
changeFrom,
changeTo,
};
}
calculateFoldingOprations(prevRoot, newRoot, changeFrom, changeTo) {
const changedRange = [changeFrom, changeTo];
const prevLists = getAllChildren(prevRoot);
const newLists = getAllChildren(newRoot);
const unfold = [];
const fold = [];
for (const prevList of prevLists.values()) {
if (!prevList.isFoldRoot()) {
continue;
}
const newList = newLists.get(prevList.getID());
if (!newList) {
continue;
}
const prevListRange = [
prevList.getFirstLineContentStart(),
prevList.getContentEndIncludingChildren(),
];
if (isRangesIntersects(prevListRange, changedRange)) {
unfold.push(prevList.getFirstLineContentStart().line);
fold.push(newList.getFirstLineContentStart().line);
}
}
unfold.sort((a, b) => b - a);
fold.sort((a, b) => b - a);
return { unfold, fold };
}
}
function getAllChildrenReduceFn(acc, child) {
acc.set(child.getID(), child);
child.getChildren().reduce(getAllChildrenReduceFn, acc);
return acc;
}
function getAllChildren(root) {
return root.getChildren().reduce(getAllChildrenReduceFn, new Map());
}
class IMEDetector {
constructor() {
this.composition = false;
this.onCompositionStart = () => {
this.composition = true;
};
this.onCompositionEnd = () => {
this.composition = false;
};
}
load() {
return __awaiter(this, void 0, void 0, function* () {
document.addEventListener("compositionstart", this.onCompositionStart);
document.addEventListener("compositionend", this.onCompositionEnd);
});
}
unload() {
return __awaiter(this, void 0, void 0, function* () {
document.removeEventListener("compositionend", this.onCompositionEnd);
document.removeEventListener("compositionstart", this.onCompositionStart);
});
}
isOpened() {
return this.composition && obsidian.Platform.isDesktop;
}
}
class Logger {
constructor(settings) {
this.settings = settings;
}
log(method, ...args) {
if (!this.settings.debug) {
return;
}
console.info(method, ...args);
}
bind(method) {
return (...args) => this.log(method, ...args);
}
}
function getHiddenObsidianConfig(app) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return app.vault.config;
}
class ObsidianSettings {
constructor(app) {
this.app = app;
}
isLegacyEditorEnabled() {
const config = Object.assign({ legacyEditor: false }, getHiddenObsidianConfig(this.app));
return config.legacyEditor;
}
isDefaultThemeEnabled() {
const config = Object.assign({ cssTheme: "" }, getHiddenObsidianConfig(this.app));
return config.cssTheme === "";
}
getTabsSettings() {
return Object.assign({ useTab: true, tabSize: 4 }, getHiddenObsidianConfig(this.app));
}
getFoldSettings() {
return Object.assign({ foldIndent: true }, getHiddenObsidianConfig(this.app));
}
getDefaultIndentChars() {
const { useTab, tabSize } = this.getTabsSettings();
return useTab ? "\t" : new Array(tabSize).fill(" ").join("");
}
}
class OperationPerformer {
constructor(parser, changesApplicator) {
this.parser = parser;
this.changesApplicator = changesApplicator;
}
eval(root, op, editor) {
const prevRoot = root.clone();
op.perform();
if (op.shouldUpdate()) {
this.changesApplicator.apply(editor, prevRoot, root);
}
return {
shouldUpdate: op.shouldUpdate(),
shouldStopPropagation: op.shouldStopPropagation(),
};
}
perform(cb, editor, cursor = editor.getCursor()) {
const root = this.parser.parse(editor, cursor);
if (!root) {
return { shouldUpdate: false, shouldStopPropagation: false };
}
const op = cb(root);
return this.eval(root, op, editor);
}
}
const bulletSignRe = `(?:[-*+]|\\d+\\.)`;
const optionalCheckboxRe = `(?:${checkboxRe})?`;
const listItemWithoutSpacesRe = new RegExp(`^${bulletSignRe}( |\t)`);
const listItemRe = new RegExp(`^[ \t]*${bulletSignRe}( |\t)`);
const stringWithSpacesRe = new RegExp(`^[ \t]+`);
const parseListItemRe = new RegExp(`^([ \t]*)(${bulletSignRe})( |\t)(${optionalCheckboxRe})(.*)$`);
class Parser {
constructor(logger, settings) {
this.logger = logger;
this.settings = settings;
}
parseRange(editor, fromLine = 0, toLine = editor.lastLine()) {
const lists = [];
for (let i = fromLine; i <= toLine; i++) {
const line = editor.getLine(i);
if (i === fromLine || this.isListItem(line)) {
const list = this.parseWithLimits(editor, i, fromLine, toLine);
if (list) {
lists.push(list);
i = list.getContentEnd().line;
}
}
}
return lists;
}
parse(editor, cursor = editor.getCursor()) {
return this.parseWithLimits(editor, cursor.line, 0, editor.lastLine());
}
parseWithLimits(editor, parsingStartLine, limitFrom, limitTo) {
const d = this.logger.bind("parseList");
const error = (msg) => {
d(msg);
return null;
};
const line = editor.getLine(parsingStartLine);
let listLookingPos = null;
if (this.isListItem(line)) {
listLookingPos = parsingStartLine;
}
else if (this.isLineWithIndent(line)) {
let listLookingPosSearch = parsingStartLine - 1;
while (listLookingPosSearch >= 0) {
const line = editor.getLine(listLookingPosSearch);
if (this.isListItem(line)) {
listLookingPos = listLookingPosSearch;
break;
}
else if (this.isLineWithIndent(line)) {
listLookingPosSearch--;
}
else {
break;
}
}
}
if (listLookingPos === null) {
return null;
}
let listStartLine = null;
let listStartLineLookup = listLookingPos;
while (listStartLineLookup >= 0) {
const line = editor.getLine(listStartLineLookup);
if (!this.isListItem(line) && !this.isLineWithIndent(line)) {
break;
}
if (this.isListItemWithoutSpaces(line)) {
listStartLine = listStartLineLookup;
if (listStartLineLookup <= limitFrom) {
break;
}
}
listStartLineLookup--;
}
if (listStartLine === null) {
return null;
}
let listEndLine = listLookingPos;
let listEndLineLookup = listLookingPos;
while (listEndLineLookup <= editor.lastLine()) {
const line = editor.getLine(listEndLineLookup);
if (!this.isListItem(line) && !this.isLineWithIndent(line)) {
break;
}
if (!this.isEmptyLine(line)) {
listEndLine = listEndLineLookup;
}
if (listEndLineLookup >= limitTo) {
listEndLine = limitTo;
break;
}
listEndLineLookup++;
}
if (listStartLine > parsingStartLine || listEndLine < parsingStartLine) {
return null;
}
// if the last line contains only spaces and that's incorrect indent, then ignore the last line
// https://github.com/vslinko/obsidian-outliner/issues/368
if (listEndLine > listStartLine) {
const lastLine = editor.getLine(listEndLine);
if (lastLine.trim().length === 0) {
const prevLine = editor.getLine(listEndLine - 1);
const [, prevLineIndent] = /^(\s*)/.exec(prevLine);
if (!lastLine.startsWith(prevLineIndent)) {
listEndLine--;
}
}
}
const root = new Root({ line: listStartLine, ch: 0 }, { line: listEndLine, ch: editor.getLine(listEndLine).length }, editor.listSelections().map((r) => ({
anchor: { line: r.anchor.line, ch: r.anchor.ch },
head: { line: r.head.line, ch: r.head.ch },
})));
let currentParent = root.getRootList();
let currentList = null;
let currentIndent = "";
const foldedLines = editor.getAllFoldedLines();
for (let l = listStartLine; l <= listEndLine; l++) {
const line = editor.getLine(l);
const matches = parseListItemRe.exec(line);
if (matches) {
const [, indent, bullet, spaceAfterBullet] = matches;
let [, , , , optionalCheckbox, content] = matches;
content = optionalCheckbox + content;
if (this.settings.keepCursorWithinContent !== "bullet-and-checkbox") {
optionalCheckbox = "";
}
const compareLength = Math.min(currentIndent.length, indent.length);
const indentSlice = indent.slice(0, compareLength);
const currentIndentSlice = currentIndent.slice(0, compareLength);
if (indentSlice !== currentIndentSlice) {
const expected = currentIndentSlice
.replace(/ /g, "S")
.replace(/\t/g, "T");
const got = indentSlice.replace(/ /g, "S").replace(/\t/g, "T");
return error(`Unable to parse list: expected indent "${expected}", got "${got}"`);
}
if (indent.length > currentIndent.length) {
currentParent = currentList;
currentIndent = indent;
}
else if (indent.length < currentIndent.length) {
while (currentParent.getFirstLineIndent().length >= indent.length &&
currentParent.getParent()) {
currentParent = currentParent.getParent();
}
currentIndent = indent;
}
const foldRoot = foldedLines.includes(l);
currentList = new List(root, indent, bullet, optionalCheckbox, spaceAfterBullet, content, foldRoot);
currentParent.addAfterAll(currentList);
}
else if (this.isLineWithIndent(line)) {
if (!currentList) {
return error(`Unable to parse list: expected list item, got empty line`);
}
const indentToCheck = currentList.getNotesIndent() || currentIndent;
if (line.indexOf(indentToCheck) !== 0) {
const expected = indentToCheck.replace(/ /g, "S").replace(/\t/g, "T");
const got = line
.match(/^[ \t]*/)[0]
.replace(/ /g, "S")
.replace(/\t/g, "T");
return error(`Unable to parse list: expected indent "${expected}", got "${got}"`);
}
if (!currentList.getNotesIndent()) {
const matches = line.match(/^[ \t]+/);
if (!matches || matches[0].length <= currentIndent.length) {
if (/^\s+$/.test(line)) {
continue;
}
return error(`Unable to parse list: expected some indent, got no indent`);
}
currentList.setNotesIndent(matches[0]);
}
currentList.addLine(line.slice(currentList.getNotesIndent().length));
}
else {
return error(`Unable to parse list: expected list item or note, got "${line}"`);
}
}
return root;
}
isEmptyLine(line) {
return line.length === 0;
}
isLineWithIndent(line) {
return stringWithSpacesRe.test(line);
}
isListItem(line) {
return listItemRe.test(line);
}
isListItemWithoutSpaces(line) {
return listItemWithoutSpacesRe.test(line);
}
}
const DEFAULT_SETTINGS = {
styleLists: true,
debug: false,
stickCursor: "bullet-and-checkbox",
betterEnter: true,
betterTab: true,
selectAll: true,
listLines: false,
listLineAction: "toggle-folding",
dndExperiment: false,
previousRelease: null,
};
class Settings {
constructor(storage) {
this.storage = storage;
this.callbacks = new Set();
}
get keepCursorWithinContent() {
// Adaptor for users migrating from older version of the plugin.
if (this.values.stickCursor === true) {
return "bullet-and-checkbox";
}
else if (this.values.stickCursor === false) {
return "never";
}
return this.values.stickCursor;
}
set keepCursorWithinContent(value) {
this.set("stickCursor", value);
}
get overrideTabBehaviour() {
return this.values.betterTab;
}
set overrideTabBehaviour(value) {
this.set("betterTab", value);
}
get overrideEnterBehaviour() {
return this.values.betterEnter;
}
set overrideEnterBehaviour(value) {
this.set("betterEnter", value);
}
get overrideSelectAllBehaviour() {
return this.values.selectAll;
}
set overrideSelectAllBehaviour(value) {
this.set("selectAll", value);
}
get betterListsStyles() {
return this.values.styleLists;
}
set betterListsStyles(value) {
this.set("styleLists", value);
}
get verticalLines() {
return this.values.listLines;
}
set verticalLines(value) {
this.set("listLines", value);
}
get verticalLinesAction() {
return this.values.listLineAction;
}
set verticalLinesAction(value) {
this.set("listLineAction", value);
}
get dragAndDrop() {
return this.values.dndExperiment;
}
set dragAndDrop(value) {
this.set("dndExperiment", value);
}
get debug() {
return this.values.debug;
}
set debug(value) {
this.set("debug", value);
}
get previousRelease() {
return this.values.previousRelease;
}
set previousRelease(value) {
this.set("previousRelease", value);
}
onChange(cb) {
this.callbacks.add(cb);
}
removeCallback(cb) {
this.callbacks.delete(cb);
}
reset() {
for (const [k, v] of Object.entries(DEFAULT_SETTINGS)) {
this.set(k, v);
}
}
load() {
return __awaiter(this, void 0, void 0, function* () {
this.values = Object.assign({}, DEFAULT_SETTINGS, yield this.storage.loadData());
});
}
save() {
return __awaiter(this, void 0, void 0, function* () {
yield this.storage.saveData(this.values);
});
}
set(key, value) {
this.values[key] = value;
for (const cb of this.callbacks) {
cb();
}
}
}
class ObsidianOutlinerPlugin extends obsidian.Plugin {
onload() {
return __awaiter(this, void 0, void 0, function* () {
console.log(`Loading obsidian-outliner`);
yield this.prepareSettings();
this.obsidianSettings = new ObsidianSettings(this.app);
this.logger = new Logger(this.settings);
this.parser = new Parser(this.logger, this.settings);
this.changesApplicator = new ChangesApplicator();
this.operationPerformer = new OperationPerformer(this.parser, this.changesApplicator);
this.imeDetector = new IMEDetector();
yield this.imeDetector.load();
this.features = [
// service features
new ReleaseNotesAnnouncement(this, this.settings),
new SettingsTab(this, this.settings),
// general features
new ListsMovementCommands(this, this.obsidianSettings, this.operationPerformer),
new ListsFoldingCommands(this, this.obsidianSettings),
// features based on settings.keepCursorWithinContent
new EditorSelectionsBehaviourOverride(this, this.settings, this.parser, this.operationPerformer),
new ArrowLeftAndCtrlArrowLeftBehaviourOverride(this, this.settings, this.imeDetector, this.operationPerformer),
new BackspaceBehaviourOverride(this, this.settings, this.imeDetector, this.operationPerformer),
new MetaBackspaceBehaviourOverride(this, this.settings, this.imeDetector, this.operationPerformer),
new DeleteBehaviourOverride(this, this.settings, this.imeDetector, this.operationPerformer),
// features based on settings.overrideTabBehaviour
new TabBehaviourOverride(this, this.imeDetector, this.obsidianSettings, this.settings, this.operationPerformer),
new ShiftTabBehaviourOverride(this, this.imeDetector, this.settings, this.operationPerformer),
// features based on settings.overrideEnterBehaviour
new EnterBehaviourOverride(this, this.settings, this.imeDetector, this.obsidianSettings, this.parser, this.operationPerformer),
// features based on settings.overrideSelectAllBehaviour
new CtrlAAndCmdABehaviourOverride(this, this.settings, this.imeDetector, this.operationPerformer),
// features based on settings.betterListsStyles
new BetterListsStyles(this.settings, this.obsidianSettings),
// features based on settings.verticalLines
new VerticalLines(this, this.settings, this.obsidianSettings, this.parser),
// features based on settings.dragAndDrop
new DragAndDrop(this, this.settings, this.obsidianSettings, this.parser, this.operationPerformer),
];
for (const feature of this.features) {
yield feature.load();
}
});
}
onunload() {
return __awaiter(this, void 0, void 0, function* () {
console.log(`Unloading obsidian-outliner`);
yield this.imeDetector.unload();
for (const feature of this.features) {
yield feature.unload();
}
});
}
prepareSettings() {
return __awaiter(this, void 0, void 0, function* () {
this.settings = new Settings(this);
yield this.settings.load();
});
}
}
module.exports = ObsidianOutlinerPlugin;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy9vcGVyYXRpb25zL01vdmVDdXJzb3JUb1ByZXZpb3VzVW5mb2xkZWRMaW5lLnRzIiwic3JjL2VkaXRvci9pbmRleC50cyIsInNyYy91dGlscy9jcmVhdGVLZXltYXBSdW5DYWxsYmFjay50cyIsInNyYy9mZWF0dXJlcy9BcnJvd0xlZnRBbmRDdHJsQXJyb3dMZWZ0QmVoYXZpb3VyT3ZlcnJpZGUudHMiLCJzcmMvcm9vdC9pbmRleC50cyIsInNyYy9vcGVyYXRpb25zL0RlbGV0ZVRpbGxQcmV2aW91c0xpbmVDb250ZW50RW5kLnRzIiwic3JjL2ZlYXR1cmVzL0JhY2tzcGFjZUJlaGF2aW91ck92ZXJyaWRlLnRzIiwic3JjL2ZlYXR1cmVzL0JldHRlckxpc3RzU3R5bGVzLnRzIiwic3JjL29wZXJhdGlvbnMvU2VsZWN0QWxsQ29udGVudC50cyIsInNyYy9mZWF0dXJlcy9DdHJsQUFuZENtZEFCZWhhdmlvdXJPdmVycmlkZS50cyIsInNyYy9vcGVyYXRpb25zL0RlbGV0ZVRpbGxOZXh0TGluZUNvbnRlbnRTdGFydC50cyIsInNyYy9mZWF0dXJlcy9EZWxldGVCZWhhdmlvdXJPdmVycmlkZS50cyIsInNyYy9vcGVyYXRpb25zL01vdmVMaXN0VG9EaWZmZXJlbnRQb3NpdGlvbi50cyIsInNyYy9mZWF0dXJlcy9EcmFnQW5kRHJvcC50cyIsInNyYy9vcGVyYXRpb25zL0tlZXBDdXJzb3JPdXRzaWRlRm9sZGVkTGluZXMudHMiLCJzcmMvb3BlcmF0aW9ucy9LZWVwQ3Vyc29yV2l0aGluTGlzdENvbnRlbnQudHMiLCJzcmMvZmVhdHVyZXMvRWRpdG9yU2VsZWN0aW9uc0JlaGF2aW91ck92ZXJyaWRlLnRzIiwic3JjL3V0aWxzL2NoZWNrYm94UmUudHMiLCJzcmMvdXRpbHMvaXNFbXB0eUxpbmVPckVtcHR5Q2hlY2tib3gudHMiLCJzcmMvb3BlcmF0aW9ucy9DcmVhdGVOZXdJdGVtLnRzIiwic3JjL29wZXJhdGlvbnMvT3V0ZGVudExpc3QudHMiLCJzcmMvb3BlcmF0aW9ucy9PdXRkZW50TGlzdElmSXRzRW1wdHkudHMiLCJzcmMvZmVhdHVyZXMvRW50ZXJCZWhhdmlvdXJPdmVycmlkZS50cyIsInNyYy91dGlscy9jcmVhdGVFZGl0b3JDYWxsYmFjay50cyIsInNyYy9mZWF0dXJlcy9MaXN0c0ZvbGRpbmdDb21tYW5kcy50cyIsInNyYy9vcGVyYXRpb25zL0luZGVudExpc3QudHMiLCJzcmMvb3BlcmF0aW9ucy9Nb3ZlTGlzdERvd24udHMiLCJzcmMvb3BlcmF0aW9ucy9Nb3ZlTGlzdFVwLnRzIiwic3JjL2ZlYXR1cmVzL0xpc3RzTW92ZW1lbnRDb21tYW5kcy50cyIsInNyYy9vcGVyYXRpb25zL0RlbGV0ZVRpbGxDdXJyZW50TGluZUNvbnRlbnRTdGFydC50cyIsInNyYy9mZWF0dXJlcy9NZXRhQmFja3NwYWNlQmVoYXZpb3VyT3ZlcnJpZGUudHMiLCJzcmMvZmVhdHVyZXMvUmVsZWFzZU5vdGVzQW5ub3VuY2VtZW50LnRzIiwic3JjL2ZlYXR1cmVzL1NldHRpbmdzVGFiLnRzIiwic3JjL2ZlYXR1cmVzL1NoaWZ0VGFiQmVoYXZpb3VyT3ZlcnJpZGUudHMiLCJzcmMvZmVhdHVyZXMvVGFiQmVoYXZpb3VyT3ZlcnJpZGUudHMiLCJzcmMvZmVhdHVyZXMvVmVydGljYWxMaW5lcy50cyIsInNyYy9zZXJ2aWNlcy9DaGFuZ2VzQXBwbGljYXRvci50cyIsInNyYy9zZXJ2aWNlcy9JTUVEZXRlY3Rvci50cyIsInNyYy9zZXJ2aWNlcy9Mb2dnZXIudHMiLCJzcmMvc2VydmljZXMvT2JzaWRpYW5TZXR0aW5ncy50cyIsInNyYy9zZXJ2aWNlcy9PcGVyYXRpb25QZXJmb3JtZXIudHMiLCJzcmMvc2VydmljZXMvUGFyc2VyLnRzIiwic3JjL3NlcnZpY2VzL1NldHRpbmdzLnRzIiwic3JjL09ic2lkaWFuT3V0bGluZXJQbHVnaW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKlxyXG5Db3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi5cclxuXHJcblBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueVxyXG5wdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQuXHJcblxyXG5USEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUyBXSVRIXHJcblJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWVxyXG5BTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SIEFOWSBTUEVDSUFMLCBESVJFQ1QsXHJcbklORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMgV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTVxyXG5MT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUlxyXG5PVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SXHJcblBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuXHJcbioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqICovXHJcbi8qIGdsb2JhbCBSZWZsZWN0LCBQcm9taXNlICovXHJcblxyXG52YXIgZXh0ZW5kU3RhdGljcyA9IGZ1bmN0aW9uKGQsIGIpIHtcclxuICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHxcclxuICAgICAgICAoeyBfX3Byb3RvX186IFtdIH0gaW5zdGFuY2VvZiBBcnJheSAmJiBmdW5jdGlvbiAoZCwgYikgeyBkLl9fcHJvdG9fXyA9IGI7IH0pIHx8XHJcbiAgICAgICAgZnVuY3Rpb24gKGQsIGIpIHsgZm9yICh2YXIgcCBpbiBiKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGIsIHApKSBkW3BdID0gYltwXTsgfTtcclxuICAgIHJldHVybiBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG59O1xyXG5cclxuZXhwb3J0IGZ