From ee591b1f7e04259feaa5d7a75972d64fb1b96075 Mon Sep 17 00:00:00 2001 From: CrazyJasonwell Date: Wed, 2 Jul 2025 22:43:15 +0800 Subject: [PATCH] vault backup: 2025-07-02 22:43:15 --- .obsidian/plugins/obsidian-git/data.json | 2 +- .obsidian/plugins/obsidian-outliner/main.js | 3418 +++++++++++++++++ .../plugins/obsidian-outliner/manifest.json | 10 + .../plugins/obsidian-outliner/styles.css | 105 + 4 files changed, 3534 insertions(+), 1 deletion(-) create mode 100644 .obsidian/plugins/obsidian-outliner/main.js create mode 100644 .obsidian/plugins/obsidian-outliner/manifest.json create mode 100644 .obsidian/plugins/obsidian-outliner/styles.css diff --git a/.obsidian/plugins/obsidian-git/data.json b/.obsidian/plugins/obsidian-git/data.json index 0dd2b5da..d6a46e12 100644 --- a/.obsidian/plugins/obsidian-git/data.json +++ b/.obsidian/plugins/obsidian-git/data.json @@ -3,7 +3,7 @@ "autoCommitMessage": "vault backup: {{date}}", "commitMessageScript": "", "commitDateFormat": "YYYY-MM-DD HH:mm:ss", - "autoSaveInterval": 5, + "autoSaveInterval": 10, "autoPushInterval": 0, "autoPullInterval": 0, "autoPullOnBoot": true, diff --git a/.obsidian/plugins/obsidian-outliner/main.js b/.obsidian/plugins/obsidian-outliner/main.js new file mode 100644 index 00000000..b1cbd9c6 --- /dev/null +++ b/.obsidian/plugins/obsidian-outliner/main.js @@ -0,0 +1,3418 @@ +'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. +***************************************************************************** */ +/* global Reflect, Promise, SuppressedError, Symbol, Iterator */ + + +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()); + }); +} + +typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}; + +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(); + const listUnderSelectionFrom = root.getListUnderLine(selectionFrom.line); + const listStart = listUnderSelectionFrom.getFirstLineContentStartAfterCheckbox(); + const listEnd = listUnderSelectionFrom.getContentEndIncludingChildren(); + this.stopPropagation = true; + this.updated = true; + if (selectionFrom.line === contentStart.line && + selectionFrom.ch === contentStart.ch && + selectionTo.line === contentEnd.line && + selectionTo.ch === contentEnd.ch) { + if (list.getChildren().length) { + // select sub lists + root.replaceSelections([ + { anchor: contentStart, head: list.getContentEndIncludingChildren() }, + ]); + } + else { + // select whole list + root.replaceSelections([{ anchor: rootStart, head: rootEnd }]); + } + } + else if (listStart.ch == selectionFrom.ch && + listEnd.line == selectionTo.line && + listEnd.ch == selectionTo.ch) { + // select whole list + root.replaceSelections([{ anchor: rootStart, head: rootEnd }]); + } + else if ((selectionFrom.line > contentStart.line || + (selectionFrom.line == contentStart.line && + selectionFrom.ch >= contentStart.ch)) && + (selectionTo.line < contentEnd.line || + (selectionTo.line == contentEnd.line && + selectionTo.ch <= contentEnd.ch))) { + // select whole line + root.replaceSelections([{ anchor: contentStart, head: contentEnd }]); + } + else { + this.stopPropagation = false; + this.updated = false; + return false; + } + 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.dropZonePadding = document.createElement("div"); + this.dropZonePadding.classList.add("outliner-plugin-drop-zone-padding"); + this.dropZone = document.createElement("div"); + this.dropZone.classList.add("outliner-plugin-drop-zone"); + this.dropZone.style.display = "none"; + this.dropZone.appendChild(this.dropZonePadding); + document.body.appendChild(this.dropZone); + } + removeDropZone() { + document.body.removeChild(this.dropZone); + this.dropZonePadding = null; + 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, dropVariant } = state; + const newParent = dropVariant.whereToMove === "inside" + ? dropVariant.placeToMove + : dropVariant.placeToMove.getParent(); + const newParentIsRootList = !newParent.getParent(); + { + const width = Math.round(view.contentDOM.offsetWidth - + (dropVariant.left - this.state.leftPadding)); + 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"; + } + { + const level = newParent.getLevel(); + const indentWidth = this.state.tabWidth; + const width = indentWidth * level; + const dashPadding = 3; + const dashWidth = indentWidth - dashPadding; + const color = getComputedStyle(document.body).getPropertyValue("--color-accent"); + this.dropZonePadding.style.width = `${width}px`; + this.dropZonePadding.style.marginLeft = `-${width}px`; + this.dropZonePadding.style.backgroundImage = `url('data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20${width}%204%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cline%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%22${width}%22%20y2%3D%220%22%20stroke%3D%22${color}%22%20stroke-width%3D%228%22%20stroke-dasharray%3D%22${dashWidth}%20${dashPadding}%22%2F%3E%3C%2Fsvg%3E')`; + } + 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.leftPadding = 0; + this.tabWidth = 0; + this.collectDropVariants(); + this.calculateLeftPadding(); + this.calculateTabWidth(); + } + getDropVariants() { + return Array.from(this.dropVariants.values()); + } + hasDropVariants() { + return this.dropVariants.size > 0; + } + calculateNearestDropVariant(x, y) { + const { view, editor } = this; + const dropVariants = this.getDropVariants(); + const possibleDropVariants = []; + for (const v of dropVariants) { + const { placeToMove } = v; + const positionAfterList = v.whereToMove === "after" || v.whereToMove === "inside"; + const line = positionAfterList + ? placeToMove.getContentEndIncludingChildren().line + : placeToMove.getFirstLineContentStart().line; + const linePos = editor.posToOffset({ + line, + ch: 0, + }); + const coords = view.coordsAtPos(linePos, -1); + if (!coords) { + continue; + } + v.left = this.leftPadding + (v.level - 1) * this.tabWidth; + v.top = coords.top; + if (positionAfterList) { + v.top += view.lineBlockAt(linePos).height; + } + // Better vertical alignment + v.top -= 8; + possibleDropVariants.push(v); + } + const nearestLineTop = possibleDropVariants + .sort((a, b) => Math.abs(y - a.top) - Math.abs(y - b.top)) + .first().top; + const variansOnNearestLine = possibleDropVariants.filter((v) => Math.abs(v.top - nearestLineTop) <= 4); + this.dropVariant = variansOnNearestLine + .sort((a, b) => Math.abs(x - a.left) - Math.abs(x - b.left)) + .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, + placeToMove, + whereToMove: "before", + }); + this.addDropVariant({ + line: lineAfter, + level, + left: 0, + top: 0, + placeToMove, + whereToMove: "after", + }); + if (placeToMove === this.list) { + continue; + } + if (placeToMove.isEmpty()) { + this.addDropVariant({ + line: lineAfter, + level: level + 1, + left: 0, + top: 0, + placeToMove, + whereToMove: "inside", + }); + } + else { + visit(placeToMove.getChildren()); + } + } + }; + visit(this.root.getChildren()); + } + calculateLeftPadding() { + const cmLine = this.view.dom.querySelector("div.cm-line"); + this.leftPadding = cmLine.getBoundingClientRect().left; + } + calculateTabWidth() { + const { view } = this; + const indentDom = view.dom.querySelector(".cm-indent"); + if (indentDom) { + this.tabWidth = indentDom.offsetWidth; + return; + } + const singleIndent = language.indentString(view.state, language.getIndentUnit(view.state)); + for (let i = 1; i <= view.state.doc.lines; i++) { + const line = view.state.doc.line(i); + if (line.text.startsWith(singleIndent)) { + const a = view.coordsAtPos(line.from, -1); + if (!a) { + continue; + } + const b = view.coordsAtPos(line.from + singleIndent.length, -1); + if (!b) { + continue; + } + this.tabWidth = b.left - a.left; + return; + } + } + this.tabWidth = view.defaultCharacterWidth * language.getIndentUnit(view.state); + } +} +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, after = true) { + this.root = root; + this.defaultIndentChars = defaultIndentChars; + this.getZoomRange = getZoomRange; + this.after = after; + 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); + } + } + if (this.after) { + list.getParent().addAfter(list, newList); + } + else { + list.getParent().addBefore(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 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("Vim-mode o/O inserts bullets") + .setDesc("Create a bullet when pressing o or O in Vim mode.") + .addToggle((toggle) => { + toggle + .setValue(this.settings.overrideVimOBehaviour) + .onChange((value) => __awaiter(this, void 0, void 0, function* () { + this.settings.overrideVimOBehaviour = 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").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 SystemInfoModal extends obsidian.Modal { + constructor(app, settings) { + super(app); + this.settings = settings; + } + onOpen() { + return __awaiter(this, void 0, void 0, function* () { + this.titleEl.setText("System Information"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const app = this.app; + const data = { + process: { + arch: process.arch, + platform: process.platform, + }, + app: { + internalPlugins: { + config: app.internalPlugins.config, + }, + isMobile: app.isMobile, + plugins: { + enabledPlugins: Array.from(app.plugins.enabledPlugins), + manifests: Object.keys(app.plugins.manifests).reduce((acc, key) => { + acc[key] = { + version: app.plugins.manifests[key].version, + }; + return acc; + }, {}), + }, + vault: { + config: app.vault.config, + }, + }, + plugin: { + settings: { values: this.settings.getValues() }, + }, + }; + const text = JSON.stringify(data, null, 2); + const pre = this.contentEl.createEl("pre"); + pre.setText(text); + pre.setCssStyles({ + overflow: "scroll", + maxHeight: "300px", + }); + const button = this.contentEl.createEl("button"); + button.setText("Copy and Close"); + button.onClickEvent(() => { + navigator.clipboard.writeText("```json\n" + text + "\n```"); + this.close(); + }); + }); + } +} +class SystemInfo { + constructor(plugin, settings) { + this.plugin = plugin; + this.settings = settings; + this.callback = () => { + const modal = new SystemInfoModal(this.plugin.app, this.settings); + modal.open(); + }; + } + load() { + return __awaiter(this, void 0, void 0, function* () { + this.plugin.addCommand({ + id: "system-info", + name: "Show System Info", + callback: this.callback, + hotkeys: [ + { + modifiers: ["Mod", "Shift", "Alt"], + key: "I", + }, + ], + }); + }); + } + 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 VimOBehaviourOverride { + constructor(plugin, settings, obsidianSettings, parser, operationPerformer) { + this.plugin = plugin; + this.settings = settings; + this.obsidianSettings = obsidianSettings; + this.parser = parser; + this.operationPerformer = operationPerformer; + this.inited = false; + this.handleSettingsChange = () => { + if (!this.settings.overrideVimOBehaviour) { + return; + } + if (!window.CodeMirrorAdapter || !window.CodeMirrorAdapter.Vim) { + console.error("Vim adapter not found"); + return; + } + const vim = window.CodeMirrorAdapter.Vim; + const plugin = this.plugin; + const parser = this.parser; + const obsidianSettings = this.obsidianSettings; + const operationPerformer = this.operationPerformer; + const settings = this.settings; + vim.defineAction("insertLineAfterBullet", (cm, operatorArgs) => { + // Move the cursor to the end of the line + vim.handleEx(cm, "normal! A"); + if (!settings.overrideVimOBehaviour) { + if (operatorArgs.after) { + vim.handleEx(cm, "normal! o"); + } + else { + vim.handleEx(cm, "normal! O"); + } + vim.enterInsertMode(cm); + return; + } + const view = plugin.app.workspace.getActiveViewOfType(obsidian.MarkdownView); + const editor = new MyEditor(view.editor); + const root = parser.parse(editor); + if (!root) { + if (operatorArgs.after) { + vim.handleEx(cm, "normal! o"); + } + else { + vim.handleEx(cm, "normal! O"); + } + vim.enterInsertMode(cm); + return; + } + const defaultIndentChars = obsidianSettings.getDefaultIndentChars(); + const zoomRange = editor.getZoomRange(); + const getZoomRange = { + getZoomRange: () => zoomRange, + }; + const res = operationPerformer.eval(root, new CreateNewItem(root, defaultIndentChars, getZoomRange, operatorArgs.after), editor); + if (res.shouldUpdate && zoomRange) { + editor.tryRefreshZoom(zoomRange.from.line); + } + // Ensure the editor is always left in insert mode + vim.enterInsertMode(cm); + }); + vim.mapCommand("o", "action", "insertLineAfterBullet", {}, { + isEdit: true, + context: "normal", + interlaceInsertRepeat: true, + actionArgs: { after: true }, + }); + vim.mapCommand("O", "action", "insertLineAfterBullet", {}, { + isEdit: true, + context: "normal", + interlaceInsertRepeat: true, + actionArgs: { after: false }, + }); + this.inited = true; + }; + } + load() { + return __awaiter(this, void 0, void 0, function* () { + this.settings.onChange(this.handleSettingsChange); + this.handleSettingsChange(); + }); + } + unload() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.inited) { + return; + } + new obsidian.Notice(`To fully unload obsidian-outliner plugin, please restart the app`, 5000); + }); + } +} + +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, + betterVimO: true, + betterTab: true, + selectAll: true, + listLines: false, + listLineAction: "toggle-folding", + dnd: true, + 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 overrideVimOBehaviour() { + return this.values.betterVimO; + } + set overrideVimOBehaviour(value) { + this.set("betterVimO", 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.dnd; + } + set dragAndDrop(value) { + this.set("dnd", 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); + }); + } + getValues() { + return Object.assign({}, 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), + new SystemInfo(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.overrideVimOBehaviour + new VimOBehaviourOverride(this, this.settings, 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; + + +/* nosourcemap */ \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-outliner/manifest.json b/.obsidian/plugins/obsidian-outliner/manifest.json new file mode 100644 index 00000000..825a32ed --- /dev/null +++ b/.obsidian/plugins/obsidian-outliner/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "obsidian-outliner", + "name": "Outliner", + "version": "4.9.0", + "minAppVersion": "1.8.7", + "description": "Work with your lists like in Workflowy or RoamResearch.", + "author": "Viacheslav Slinko", + "authorUrl": "https://github.com/vslinko", + "isDesktopOnly": false +} diff --git a/.obsidian/plugins/obsidian-outliner/styles.css b/.obsidian/plugins/obsidian-outliner/styles.css new file mode 100644 index 00000000..b1bb8d28 --- /dev/null +++ b/.obsidian/plugins/obsidian-outliner/styles.css @@ -0,0 +1,105 @@ +/* lists and bullets */ +.outliner-plugin-better-lists .cm-s-obsidian .HyperMD-list-line { + /* padding-top: 0.4em; */ +} + +.outliner-plugin-better-lists .cm-formatting-list-ul { + margin-right: 0.3em; +} + +.outliner-plugin-better-lists .list-bullet::after { + width: 0.4em; + height: 0.4em; + background-color: var(--text-muted); +} + +/* lines */ +.outliner-plugin-list-lines-scroller { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: var(--file-margins); + padding-left: 0; + pointer-events: none; + overflow: hidden; +} + +.outliner-plugin-list-lines-content-container { + position: relative; +} + +.outliner-plugin-list-line { + pointer-events: auto; + position: absolute; + width: 5px; + margin-left: 0.5ch; + margin-top: 1em; + z-index: 1; + cursor: pointer; + background: transparent; + background-image: linear-gradient( + to right, + var(--text-faint) 1px, + transparent 1px + ); + background-position-x: 2px; + background-repeat: no-repeat; +} + +.outliner-plugin-better-bullets .outliner-plugin-list-line { + margin-top: 1.4em; +} + +.markdown-source-view.mod-cm6.is-readable-line-width + .outliner-plugin-list-lines-content-container { + max-width: 700px; + margin: auto; +} + +.outliner-plugin-list-line:hover { + background: var(--text-faint); +} + +.outliner-plugin-vertical-lines + .markdown-source-view.mod-cm6 + .cm-hmd-list-indent + .cm-indent::before { + content: none; +} + +/* drag-n-drop */ +.outliner-plugin-dropping-line { + background-color: hsla(var(--interactive-accent-hsl), 0.4); +} + +.outliner-plugin-dragging-line { + opacity: 0.5; + background-color: hsla(var(--interactive-accent-hsl), 0.2); +} + +.outliner-plugin-drop-zone { + width: 300px; + height: 4px; + background: var(--color-accent); + z-index: 999; + position: absolute; + pointer-events: none; +} + +.outliner-plugin-drop-zone-padding { + position: absolute; + height: 4px; +} + +body.outliner-plugin-dnd:not(.outliner-plugin-dragging) .cm-formatting-list, +body.outliner-plugin-dnd:not(.outliner-plugin-dragging) + .cm-fold-indicator + .collapse-indicator { + cursor: grab !important; +} + +html body.outliner-plugin-dnd.outliner-plugin-dragging { + cursor: grabbing !important; +}