3959 lines
543 KiB
JavaScript
3959 lines
543 KiB
JavaScript
|
|
/*
|
||
|
|
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||
|
|
if you want to view the source, please visit the github repository of this plugin
|
||
|
|
*/
|
||
|
|
|
||
|
|
var __create = Object.create;
|
||
|
|
var __defProp = Object.defineProperty;
|
||
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||
|
|
var __getProtoOf = Object.getPrototypeOf;
|
||
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||
|
|
var __commonJS = (cb, mod) => function __require() {
|
||
|
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||
|
|
};
|
||
|
|
var __export = (target, all) => {
|
||
|
|
for (var name in all)
|
||
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
||
|
|
};
|
||
|
|
var __copyProps = (to, from, except, desc) => {
|
||
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
||
|
|
for (let key of __getOwnPropNames(from))
|
||
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
||
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||
|
|
}
|
||
|
|
return to;
|
||
|
|
};
|
||
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
||
|
|
// file that has been converted to a CommonJS file using a Babel-
|
||
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
||
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
||
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||
|
|
mod
|
||
|
|
));
|
||
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||
|
|
|
||
|
|
// node_modules/.pnpm/obsidian-daily-notes-interf_8d9fc24b0fc622d94fd11fa5e9b185b3/node_modules/obsidian-daily-notes-interface/dist/main.js
|
||
|
|
var require_main = __commonJS({
|
||
|
|
"node_modules/.pnpm/obsidian-daily-notes-interf_8d9fc24b0fc622d94fd11fa5e9b185b3/node_modules/obsidian-daily-notes-interface/dist/main.js"(exports) {
|
||
|
|
"use strict";
|
||
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
|
var obsidian = require("obsidian");
|
||
|
|
var DEFAULT_DAILY_NOTE_FORMAT = "YYYY-MM-DD";
|
||
|
|
var DEFAULT_WEEKLY_NOTE_FORMAT = "gggg-[W]ww";
|
||
|
|
var DEFAULT_MONTHLY_NOTE_FORMAT = "YYYY-MM";
|
||
|
|
var DEFAULT_QUARTERLY_NOTE_FORMAT = "YYYY-[Q]Q";
|
||
|
|
var DEFAULT_YEARLY_NOTE_FORMAT = "YYYY";
|
||
|
|
function shouldUsePeriodicNotesSettings(periodicity) {
|
||
|
|
var _a, _b;
|
||
|
|
const periodicNotes = window.app.plugins.getPlugin("periodic-notes");
|
||
|
|
return periodicNotes && ((_b = (_a = periodicNotes.settings) == null ? void 0 : _a[periodicity]) == null ? void 0 : _b.enabled);
|
||
|
|
}
|
||
|
|
function getDailyNoteSettings() {
|
||
|
|
var _a, _b, _c, _d;
|
||
|
|
try {
|
||
|
|
const { internalPlugins, plugins } = window.app;
|
||
|
|
if (shouldUsePeriodicNotesSettings("daily")) {
|
||
|
|
const { format: format2, folder: folder2, template: template2 } = ((_b = (_a = plugins.getPlugin("periodic-notes")) == null ? void 0 : _a.settings) == null ? void 0 : _b.daily) || {};
|
||
|
|
return {
|
||
|
|
format: format2 || DEFAULT_DAILY_NOTE_FORMAT,
|
||
|
|
folder: (folder2 == null ? void 0 : folder2.trim()) || "",
|
||
|
|
template: (template2 == null ? void 0 : template2.trim()) || ""
|
||
|
|
};
|
||
|
|
}
|
||
|
|
const { folder, format, template } = ((_d = (_c = internalPlugins.getPluginById("daily-notes")) == null ? void 0 : _c.instance) == null ? void 0 : _d.options) || {};
|
||
|
|
return {
|
||
|
|
format: format || DEFAULT_DAILY_NOTE_FORMAT,
|
||
|
|
folder: (folder == null ? void 0 : folder.trim()) || "",
|
||
|
|
template: (template == null ? void 0 : template.trim()) || ""
|
||
|
|
};
|
||
|
|
} catch (err) {
|
||
|
|
console.info("No custom daily note settings found!", err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getWeeklyNoteSettings() {
|
||
|
|
var _a, _b, _c, _d, _e, _f, _g;
|
||
|
|
try {
|
||
|
|
const pluginManager = window.app.plugins;
|
||
|
|
const calendarSettings = (_a = pluginManager.getPlugin("calendar")) == null ? void 0 : _a.options;
|
||
|
|
const periodicNotesSettings = (_c = (_b = pluginManager.getPlugin("periodic-notes")) == null ? void 0 : _b.settings) == null ? void 0 : _c.weekly;
|
||
|
|
if (shouldUsePeriodicNotesSettings("weekly")) {
|
||
|
|
return {
|
||
|
|
format: periodicNotesSettings.format || DEFAULT_WEEKLY_NOTE_FORMAT,
|
||
|
|
folder: ((_d = periodicNotesSettings.folder) == null ? void 0 : _d.trim()) || "",
|
||
|
|
template: ((_e = periodicNotesSettings.template) == null ? void 0 : _e.trim()) || ""
|
||
|
|
};
|
||
|
|
}
|
||
|
|
const settings = calendarSettings || {};
|
||
|
|
return {
|
||
|
|
format: settings.weeklyNoteFormat || DEFAULT_WEEKLY_NOTE_FORMAT,
|
||
|
|
folder: ((_f = settings.weeklyNoteFolder) == null ? void 0 : _f.trim()) || "",
|
||
|
|
template: ((_g = settings.weeklyNoteTemplate) == null ? void 0 : _g.trim()) || ""
|
||
|
|
};
|
||
|
|
} catch (err) {
|
||
|
|
console.info("No custom weekly note settings found!", err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getMonthlyNoteSettings() {
|
||
|
|
var _a, _b, _c, _d;
|
||
|
|
const pluginManager = window.app.plugins;
|
||
|
|
try {
|
||
|
|
const settings = shouldUsePeriodicNotesSettings("monthly") && ((_b = (_a = pluginManager.getPlugin("periodic-notes")) == null ? void 0 : _a.settings) == null ? void 0 : _b.monthly) || {};
|
||
|
|
return {
|
||
|
|
format: settings.format || DEFAULT_MONTHLY_NOTE_FORMAT,
|
||
|
|
folder: ((_c = settings.folder) == null ? void 0 : _c.trim()) || "",
|
||
|
|
template: ((_d = settings.template) == null ? void 0 : _d.trim()) || ""
|
||
|
|
};
|
||
|
|
} catch (err) {
|
||
|
|
console.info("No custom monthly note settings found!", err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getQuarterlyNoteSettings() {
|
||
|
|
var _a, _b, _c, _d;
|
||
|
|
const pluginManager = window.app.plugins;
|
||
|
|
try {
|
||
|
|
const settings = shouldUsePeriodicNotesSettings("quarterly") && ((_b = (_a = pluginManager.getPlugin("periodic-notes")) == null ? void 0 : _a.settings) == null ? void 0 : _b.quarterly) || {};
|
||
|
|
return {
|
||
|
|
format: settings.format || DEFAULT_QUARTERLY_NOTE_FORMAT,
|
||
|
|
folder: ((_c = settings.folder) == null ? void 0 : _c.trim()) || "",
|
||
|
|
template: ((_d = settings.template) == null ? void 0 : _d.trim()) || ""
|
||
|
|
};
|
||
|
|
} catch (err) {
|
||
|
|
console.info("No custom quarterly note settings found!", err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getYearlyNoteSettings() {
|
||
|
|
var _a, _b, _c, _d;
|
||
|
|
const pluginManager = window.app.plugins;
|
||
|
|
try {
|
||
|
|
const settings = shouldUsePeriodicNotesSettings("yearly") && ((_b = (_a = pluginManager.getPlugin("periodic-notes")) == null ? void 0 : _a.settings) == null ? void 0 : _b.yearly) || {};
|
||
|
|
return {
|
||
|
|
format: settings.format || DEFAULT_YEARLY_NOTE_FORMAT,
|
||
|
|
folder: ((_c = settings.folder) == null ? void 0 : _c.trim()) || "",
|
||
|
|
template: ((_d = settings.template) == null ? void 0 : _d.trim()) || ""
|
||
|
|
};
|
||
|
|
} catch (err) {
|
||
|
|
console.info("No custom yearly note settings found!", err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function join(...partSegments) {
|
||
|
|
let parts = [];
|
||
|
|
for (let i = 0, l = partSegments.length; i < l; i++) {
|
||
|
|
parts = parts.concat(partSegments[i].split("/"));
|
||
|
|
}
|
||
|
|
const newParts = [];
|
||
|
|
for (let i = 0, l = parts.length; i < l; i++) {
|
||
|
|
const part = parts[i];
|
||
|
|
if (!part || part === ".")
|
||
|
|
continue;
|
||
|
|
else
|
||
|
|
newParts.push(part);
|
||
|
|
}
|
||
|
|
if (parts[0] === "")
|
||
|
|
newParts.unshift("");
|
||
|
|
return newParts.join("/");
|
||
|
|
}
|
||
|
|
function basename(fullPath) {
|
||
|
|
let base = fullPath.substring(fullPath.lastIndexOf("/") + 1);
|
||
|
|
if (base.lastIndexOf(".") != -1)
|
||
|
|
base = base.substring(0, base.lastIndexOf("."));
|
||
|
|
return base;
|
||
|
|
}
|
||
|
|
async function ensureFolderExists(path) {
|
||
|
|
const dirs = path.replace(/\\/g, "/").split("/");
|
||
|
|
dirs.pop();
|
||
|
|
if (dirs.length) {
|
||
|
|
const dir = join(...dirs);
|
||
|
|
if (!window.app.vault.getAbstractFileByPath(dir)) {
|
||
|
|
await window.app.vault.createFolder(dir);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
async function getNotePath(directory, filename) {
|
||
|
|
if (!filename.endsWith(".md")) {
|
||
|
|
filename += ".md";
|
||
|
|
}
|
||
|
|
const path = obsidian.normalizePath(join(directory, filename));
|
||
|
|
await ensureFolderExists(path);
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
async function getTemplateInfo(template) {
|
||
|
|
const { metadataCache, vault } = window.app;
|
||
|
|
const templatePath = obsidian.normalizePath(template);
|
||
|
|
if (templatePath === "/") {
|
||
|
|
return Promise.resolve(["", null]);
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
const templateFile = metadataCache.getFirstLinkpathDest(templatePath, "");
|
||
|
|
const contents = await vault.cachedRead(templateFile);
|
||
|
|
const IFoldInfo = window.app.foldManager.load(templateFile);
|
||
|
|
return [contents, IFoldInfo];
|
||
|
|
} catch (err) {
|
||
|
|
console.error(`Failed to read the daily note template '${templatePath}'`, err);
|
||
|
|
new obsidian.Notice("Failed to read the daily note template");
|
||
|
|
return ["", null];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getDateUID(date, granularity = "day") {
|
||
|
|
const ts = date.clone().startOf(granularity).format();
|
||
|
|
return `${granularity}-${ts}`;
|
||
|
|
}
|
||
|
|
function removeEscapedCharacters(format) {
|
||
|
|
return format.replace(/\[[^\]]*\]/g, "");
|
||
|
|
}
|
||
|
|
function isFormatAmbiguous(format, granularity) {
|
||
|
|
if (granularity === "week") {
|
||
|
|
const cleanFormat = removeEscapedCharacters(format);
|
||
|
|
return /w{1,2}/i.test(cleanFormat) && (/M{1,4}/.test(cleanFormat) || /D{1,4}/.test(cleanFormat));
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
function getDateFromFile(file, granularity) {
|
||
|
|
return getDateFromFilename(file.basename, granularity);
|
||
|
|
}
|
||
|
|
function getDateFromPath(path, granularity) {
|
||
|
|
return getDateFromFilename(basename(path), granularity);
|
||
|
|
}
|
||
|
|
function getDateFromFilename(filename, granularity) {
|
||
|
|
const getSettings = {
|
||
|
|
day: getDailyNoteSettings,
|
||
|
|
week: getWeeklyNoteSettings,
|
||
|
|
month: getMonthlyNoteSettings,
|
||
|
|
quarter: getQuarterlyNoteSettings,
|
||
|
|
year: getYearlyNoteSettings
|
||
|
|
};
|
||
|
|
const format = getSettings[granularity]().format.split("/").pop();
|
||
|
|
const noteDate = window.moment(filename, format, true);
|
||
|
|
if (!noteDate.isValid()) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
if (isFormatAmbiguous(format, granularity)) {
|
||
|
|
if (granularity === "week") {
|
||
|
|
const cleanFormat = removeEscapedCharacters(format);
|
||
|
|
if (/w{1,2}/i.test(cleanFormat)) {
|
||
|
|
return window.moment(
|
||
|
|
filename,
|
||
|
|
// If format contains week, remove day & month formatting
|
||
|
|
format.replace(/M{1,4}/g, "").replace(/D{1,4}/g, ""),
|
||
|
|
false
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return noteDate;
|
||
|
|
}
|
||
|
|
var DailyNotesFolderMissingError = class extends Error {
|
||
|
|
};
|
||
|
|
async function createDailyNote2(date) {
|
||
|
|
const app = window.app;
|
||
|
|
const { vault } = app;
|
||
|
|
const moment2 = window.moment;
|
||
|
|
const { template, format, folder } = getDailyNoteSettings();
|
||
|
|
const [templateContents, IFoldInfo] = await getTemplateInfo(template);
|
||
|
|
const filename = date.format(format);
|
||
|
|
const normalizedPath = await getNotePath(folder, filename);
|
||
|
|
try {
|
||
|
|
const createdFile = await vault.create(normalizedPath, templateContents.replace(/{{\s*date\s*}}/gi, filename).replace(/{{\s*time\s*}}/gi, moment2().format("HH:mm")).replace(/{{\s*title\s*}}/gi, filename).replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi, (_, _timeOrDate, calc, timeDelta, unit, momentFormat) => {
|
||
|
|
const now = moment2();
|
||
|
|
const currentDate = date.clone().set({
|
||
|
|
hour: now.get("hour"),
|
||
|
|
minute: now.get("minute"),
|
||
|
|
second: now.get("second")
|
||
|
|
});
|
||
|
|
if (calc) {
|
||
|
|
currentDate.add(parseInt(timeDelta, 10), unit);
|
||
|
|
}
|
||
|
|
if (momentFormat) {
|
||
|
|
return currentDate.format(momentFormat.substring(1).trim());
|
||
|
|
}
|
||
|
|
return currentDate.format(format);
|
||
|
|
}).replace(/{{\s*yesterday\s*}}/gi, date.clone().subtract(1, "day").format(format)).replace(/{{\s*tomorrow\s*}}/gi, date.clone().add(1, "d").format(format)));
|
||
|
|
app.foldManager.save(createdFile, IFoldInfo);
|
||
|
|
return createdFile;
|
||
|
|
} catch (err) {
|
||
|
|
console.error(`Failed to create file: '${normalizedPath}'`, err);
|
||
|
|
new obsidian.Notice("Unable to create new file.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getDailyNote2(date, dailyNotes) {
|
||
|
|
var _a;
|
||
|
|
return (_a = dailyNotes[getDateUID(date, "day")]) != null ? _a : null;
|
||
|
|
}
|
||
|
|
function getAllDailyNotes2() {
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { folder } = getDailyNoteSettings();
|
||
|
|
const dailyNotesFolder = vault.getAbstractFileByPath(obsidian.normalizePath(folder));
|
||
|
|
if (!dailyNotesFolder) {
|
||
|
|
throw new DailyNotesFolderMissingError("Failed to find daily notes folder");
|
||
|
|
}
|
||
|
|
const dailyNotes = {};
|
||
|
|
obsidian.Vault.recurseChildren(dailyNotesFolder, (note) => {
|
||
|
|
if (note instanceof obsidian.TFile) {
|
||
|
|
const date = getDateFromFile(note, "day");
|
||
|
|
if (date) {
|
||
|
|
const dateString = getDateUID(date, "day");
|
||
|
|
dailyNotes[dateString] = note;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return dailyNotes;
|
||
|
|
}
|
||
|
|
var WeeklyNotesFolderMissingError = class extends Error {
|
||
|
|
};
|
||
|
|
function getDaysOfWeek() {
|
||
|
|
const { moment: moment2 } = window;
|
||
|
|
let weekStart = moment2.localeData()._week.dow;
|
||
|
|
const daysOfWeek = [
|
||
|
|
"sunday",
|
||
|
|
"monday",
|
||
|
|
"tuesday",
|
||
|
|
"wednesday",
|
||
|
|
"thursday",
|
||
|
|
"friday",
|
||
|
|
"saturday"
|
||
|
|
];
|
||
|
|
while (weekStart) {
|
||
|
|
daysOfWeek.push(daysOfWeek.shift());
|
||
|
|
weekStart--;
|
||
|
|
}
|
||
|
|
return daysOfWeek;
|
||
|
|
}
|
||
|
|
function getDayOfWeekNumericalValue(dayOfWeekName) {
|
||
|
|
return getDaysOfWeek().indexOf(dayOfWeekName.toLowerCase());
|
||
|
|
}
|
||
|
|
async function createWeeklyNote2(date) {
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { template, format, folder } = getWeeklyNoteSettings();
|
||
|
|
const [templateContents, IFoldInfo] = await getTemplateInfo(template);
|
||
|
|
const filename = date.format(format);
|
||
|
|
const normalizedPath = await getNotePath(folder, filename);
|
||
|
|
try {
|
||
|
|
const createdFile = await vault.create(normalizedPath, templateContents.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi, (_, _timeOrDate, calc, timeDelta, unit, momentFormat) => {
|
||
|
|
const now = window.moment();
|
||
|
|
const currentDate = date.clone().set({
|
||
|
|
hour: now.get("hour"),
|
||
|
|
minute: now.get("minute"),
|
||
|
|
second: now.get("second")
|
||
|
|
});
|
||
|
|
if (calc) {
|
||
|
|
currentDate.add(parseInt(timeDelta, 10), unit);
|
||
|
|
}
|
||
|
|
if (momentFormat) {
|
||
|
|
return currentDate.format(momentFormat.substring(1).trim());
|
||
|
|
}
|
||
|
|
return currentDate.format(format);
|
||
|
|
}).replace(/{{\s*title\s*}}/gi, filename).replace(/{{\s*time\s*}}/gi, window.moment().format("HH:mm")).replace(/{{\s*(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\s*:(.*?)}}/gi, (_, dayOfWeek, momentFormat) => {
|
||
|
|
const day = getDayOfWeekNumericalValue(dayOfWeek);
|
||
|
|
return date.weekday(day).format(momentFormat.trim());
|
||
|
|
}));
|
||
|
|
window.app.foldManager.save(createdFile, IFoldInfo);
|
||
|
|
return createdFile;
|
||
|
|
} catch (err) {
|
||
|
|
console.error(`Failed to create file: '${normalizedPath}'`, err);
|
||
|
|
new obsidian.Notice("Unable to create new file.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getWeeklyNote2(date, weeklyNotes) {
|
||
|
|
var _a;
|
||
|
|
return (_a = weeklyNotes[getDateUID(date, "week")]) != null ? _a : null;
|
||
|
|
}
|
||
|
|
function getAllWeeklyNotes2() {
|
||
|
|
const weeklyNotes = {};
|
||
|
|
if (!appHasWeeklyNotesPluginLoaded()) {
|
||
|
|
return weeklyNotes;
|
||
|
|
}
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { folder } = getWeeklyNoteSettings();
|
||
|
|
const weeklyNotesFolder = vault.getAbstractFileByPath(obsidian.normalizePath(folder));
|
||
|
|
if (!weeklyNotesFolder) {
|
||
|
|
throw new WeeklyNotesFolderMissingError("Failed to find weekly notes folder");
|
||
|
|
}
|
||
|
|
obsidian.Vault.recurseChildren(weeklyNotesFolder, (note) => {
|
||
|
|
if (note instanceof obsidian.TFile) {
|
||
|
|
const date = getDateFromFile(note, "week");
|
||
|
|
if (date) {
|
||
|
|
const dateString = getDateUID(date, "week");
|
||
|
|
weeklyNotes[dateString] = note;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return weeklyNotes;
|
||
|
|
}
|
||
|
|
var MonthlyNotesFolderMissingError = class extends Error {
|
||
|
|
};
|
||
|
|
async function createMonthlyNote2(date) {
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { template, format, folder } = getMonthlyNoteSettings();
|
||
|
|
const [templateContents, IFoldInfo] = await getTemplateInfo(template);
|
||
|
|
const filename = date.format(format);
|
||
|
|
const normalizedPath = await getNotePath(folder, filename);
|
||
|
|
try {
|
||
|
|
const createdFile = await vault.create(normalizedPath, templateContents.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi, (_, _timeOrDate, calc, timeDelta, unit, momentFormat) => {
|
||
|
|
const now = window.moment();
|
||
|
|
const currentDate = date.clone().set({
|
||
|
|
hour: now.get("hour"),
|
||
|
|
minute: now.get("minute"),
|
||
|
|
second: now.get("second")
|
||
|
|
});
|
||
|
|
if (calc) {
|
||
|
|
currentDate.add(parseInt(timeDelta, 10), unit);
|
||
|
|
}
|
||
|
|
if (momentFormat) {
|
||
|
|
return currentDate.format(momentFormat.substring(1).trim());
|
||
|
|
}
|
||
|
|
return currentDate.format(format);
|
||
|
|
}).replace(/{{\s*date\s*}}/gi, filename).replace(/{{\s*time\s*}}/gi, window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi, filename));
|
||
|
|
window.app.foldManager.save(createdFile, IFoldInfo);
|
||
|
|
return createdFile;
|
||
|
|
} catch (err) {
|
||
|
|
console.error(`Failed to create file: '${normalizedPath}'`, err);
|
||
|
|
new obsidian.Notice("Unable to create new file.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getMonthlyNote2(date, monthlyNotes) {
|
||
|
|
var _a;
|
||
|
|
return (_a = monthlyNotes[getDateUID(date, "month")]) != null ? _a : null;
|
||
|
|
}
|
||
|
|
function getAllMonthlyNotes2() {
|
||
|
|
const monthlyNotes = {};
|
||
|
|
if (!appHasMonthlyNotesPluginLoaded()) {
|
||
|
|
return monthlyNotes;
|
||
|
|
}
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { folder } = getMonthlyNoteSettings();
|
||
|
|
const monthlyNotesFolder = vault.getAbstractFileByPath(obsidian.normalizePath(folder));
|
||
|
|
if (!monthlyNotesFolder) {
|
||
|
|
throw new MonthlyNotesFolderMissingError("Failed to find monthly notes folder");
|
||
|
|
}
|
||
|
|
obsidian.Vault.recurseChildren(monthlyNotesFolder, (note) => {
|
||
|
|
if (note instanceof obsidian.TFile) {
|
||
|
|
const date = getDateFromFile(note, "month");
|
||
|
|
if (date) {
|
||
|
|
const dateString = getDateUID(date, "month");
|
||
|
|
monthlyNotes[dateString] = note;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return monthlyNotes;
|
||
|
|
}
|
||
|
|
var QuarterlyNotesFolderMissingError = class extends Error {
|
||
|
|
};
|
||
|
|
async function createQuarterlyNote2(date) {
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { template, format, folder } = getQuarterlyNoteSettings();
|
||
|
|
const [templateContents, IFoldInfo] = await getTemplateInfo(template);
|
||
|
|
const filename = date.format(format);
|
||
|
|
const normalizedPath = await getNotePath(folder, filename);
|
||
|
|
try {
|
||
|
|
const createdFile = await vault.create(normalizedPath, templateContents.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi, (_, _timeOrDate, calc, timeDelta, unit, momentFormat) => {
|
||
|
|
const now = window.moment();
|
||
|
|
const currentDate = date.clone().set({
|
||
|
|
hour: now.get("hour"),
|
||
|
|
minute: now.get("minute"),
|
||
|
|
second: now.get("second")
|
||
|
|
});
|
||
|
|
if (calc) {
|
||
|
|
currentDate.add(parseInt(timeDelta, 10), unit);
|
||
|
|
}
|
||
|
|
if (momentFormat) {
|
||
|
|
return currentDate.format(momentFormat.substring(1).trim());
|
||
|
|
}
|
||
|
|
return currentDate.format(format);
|
||
|
|
}).replace(/{{\s*date\s*}}/gi, filename).replace(/{{\s*time\s*}}/gi, window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi, filename));
|
||
|
|
window.app.foldManager.save(createdFile, IFoldInfo);
|
||
|
|
return createdFile;
|
||
|
|
} catch (err) {
|
||
|
|
console.error(`Failed to create file: '${normalizedPath}'`, err);
|
||
|
|
new obsidian.Notice("Unable to create new file.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getQuarterlyNote2(date, quarterly) {
|
||
|
|
var _a;
|
||
|
|
return (_a = quarterly[getDateUID(date, "quarter")]) != null ? _a : null;
|
||
|
|
}
|
||
|
|
function getAllQuarterlyNotes2() {
|
||
|
|
const quarterly = {};
|
||
|
|
if (!appHasQuarterlyNotesPluginLoaded()) {
|
||
|
|
return quarterly;
|
||
|
|
}
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { folder } = getQuarterlyNoteSettings();
|
||
|
|
const quarterlyFolder = vault.getAbstractFileByPath(obsidian.normalizePath(folder));
|
||
|
|
if (!quarterlyFolder) {
|
||
|
|
throw new QuarterlyNotesFolderMissingError("Failed to find quarterly notes folder");
|
||
|
|
}
|
||
|
|
obsidian.Vault.recurseChildren(quarterlyFolder, (note) => {
|
||
|
|
if (note instanceof obsidian.TFile) {
|
||
|
|
const date = getDateFromFile(note, "quarter");
|
||
|
|
if (date) {
|
||
|
|
const dateString = getDateUID(date, "quarter");
|
||
|
|
quarterly[dateString] = note;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return quarterly;
|
||
|
|
}
|
||
|
|
var YearlyNotesFolderMissingError = class extends Error {
|
||
|
|
};
|
||
|
|
async function createYearlyNote2(date) {
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { template, format, folder } = getYearlyNoteSettings();
|
||
|
|
const [templateContents, IFoldInfo] = await getTemplateInfo(template);
|
||
|
|
const filename = date.format(format);
|
||
|
|
const normalizedPath = await getNotePath(folder, filename);
|
||
|
|
try {
|
||
|
|
const createdFile = await vault.create(normalizedPath, templateContents.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi, (_, _timeOrDate, calc, timeDelta, unit, momentFormat) => {
|
||
|
|
const now = window.moment();
|
||
|
|
const currentDate = date.clone().set({
|
||
|
|
hour: now.get("hour"),
|
||
|
|
minute: now.get("minute"),
|
||
|
|
second: now.get("second")
|
||
|
|
});
|
||
|
|
if (calc) {
|
||
|
|
currentDate.add(parseInt(timeDelta, 10), unit);
|
||
|
|
}
|
||
|
|
if (momentFormat) {
|
||
|
|
return currentDate.format(momentFormat.substring(1).trim());
|
||
|
|
}
|
||
|
|
return currentDate.format(format);
|
||
|
|
}).replace(/{{\s*date\s*}}/gi, filename).replace(/{{\s*time\s*}}/gi, window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi, filename));
|
||
|
|
window.app.foldManager.save(createdFile, IFoldInfo);
|
||
|
|
return createdFile;
|
||
|
|
} catch (err) {
|
||
|
|
console.error(`Failed to create file: '${normalizedPath}'`, err);
|
||
|
|
new obsidian.Notice("Unable to create new file.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getYearlyNote2(date, yearlyNotes) {
|
||
|
|
var _a;
|
||
|
|
return (_a = yearlyNotes[getDateUID(date, "year")]) != null ? _a : null;
|
||
|
|
}
|
||
|
|
function getAllYearlyNotes2() {
|
||
|
|
const yearlyNotes = {};
|
||
|
|
if (!appHasYearlyNotesPluginLoaded()) {
|
||
|
|
return yearlyNotes;
|
||
|
|
}
|
||
|
|
const { vault } = window.app;
|
||
|
|
const { folder } = getYearlyNoteSettings();
|
||
|
|
const yearlyNotesFolder = vault.getAbstractFileByPath(obsidian.normalizePath(folder));
|
||
|
|
if (!yearlyNotesFolder) {
|
||
|
|
throw new YearlyNotesFolderMissingError("Failed to find yearly notes folder");
|
||
|
|
}
|
||
|
|
obsidian.Vault.recurseChildren(yearlyNotesFolder, (note) => {
|
||
|
|
if (note instanceof obsidian.TFile) {
|
||
|
|
const date = getDateFromFile(note, "year");
|
||
|
|
if (date) {
|
||
|
|
const dateString = getDateUID(date, "year");
|
||
|
|
yearlyNotes[dateString] = note;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return yearlyNotes;
|
||
|
|
}
|
||
|
|
function appHasDailyNotesPluginLoaded() {
|
||
|
|
var _a, _b;
|
||
|
|
const { app } = window;
|
||
|
|
const dailyNotesPlugin = app.internalPlugins.plugins["daily-notes"];
|
||
|
|
if (dailyNotesPlugin && dailyNotesPlugin.enabled) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const periodicNotes = app.plugins.getPlugin("periodic-notes");
|
||
|
|
return periodicNotes && ((_b = (_a = periodicNotes.settings) == null ? void 0 : _a.daily) == null ? void 0 : _b.enabled);
|
||
|
|
}
|
||
|
|
function appHasWeeklyNotesPluginLoaded() {
|
||
|
|
var _a, _b;
|
||
|
|
const { app } = window;
|
||
|
|
if (app.plugins.getPlugin("calendar")) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const periodicNotes = app.plugins.getPlugin("periodic-notes");
|
||
|
|
return periodicNotes && ((_b = (_a = periodicNotes.settings) == null ? void 0 : _a.weekly) == null ? void 0 : _b.enabled);
|
||
|
|
}
|
||
|
|
function appHasMonthlyNotesPluginLoaded() {
|
||
|
|
var _a, _b;
|
||
|
|
const { app } = window;
|
||
|
|
const periodicNotes = app.plugins.getPlugin("periodic-notes");
|
||
|
|
return periodicNotes && ((_b = (_a = periodicNotes.settings) == null ? void 0 : _a.monthly) == null ? void 0 : _b.enabled);
|
||
|
|
}
|
||
|
|
function appHasQuarterlyNotesPluginLoaded() {
|
||
|
|
var _a, _b;
|
||
|
|
const { app } = window;
|
||
|
|
const periodicNotes = app.plugins.getPlugin("periodic-notes");
|
||
|
|
return periodicNotes && ((_b = (_a = periodicNotes.settings) == null ? void 0 : _a.quarterly) == null ? void 0 : _b.enabled);
|
||
|
|
}
|
||
|
|
function appHasYearlyNotesPluginLoaded() {
|
||
|
|
var _a, _b;
|
||
|
|
const { app } = window;
|
||
|
|
const periodicNotes = app.plugins.getPlugin("periodic-notes");
|
||
|
|
return periodicNotes && ((_b = (_a = periodicNotes.settings) == null ? void 0 : _a.yearly) == null ? void 0 : _b.enabled);
|
||
|
|
}
|
||
|
|
function getPeriodicNoteSettings(granularity) {
|
||
|
|
const getSettings = {
|
||
|
|
day: getDailyNoteSettings,
|
||
|
|
week: getWeeklyNoteSettings,
|
||
|
|
month: getMonthlyNoteSettings,
|
||
|
|
quarter: getQuarterlyNoteSettings,
|
||
|
|
year: getYearlyNoteSettings
|
||
|
|
}[granularity];
|
||
|
|
return getSettings();
|
||
|
|
}
|
||
|
|
function createPeriodicNote(granularity, date) {
|
||
|
|
const createFn = {
|
||
|
|
day: createDailyNote2,
|
||
|
|
month: createMonthlyNote2,
|
||
|
|
week: createWeeklyNote2
|
||
|
|
};
|
||
|
|
return createFn[granularity](date);
|
||
|
|
}
|
||
|
|
exports.DEFAULT_DAILY_NOTE_FORMAT = DEFAULT_DAILY_NOTE_FORMAT;
|
||
|
|
exports.DEFAULT_MONTHLY_NOTE_FORMAT = DEFAULT_MONTHLY_NOTE_FORMAT;
|
||
|
|
exports.DEFAULT_QUARTERLY_NOTE_FORMAT = DEFAULT_QUARTERLY_NOTE_FORMAT;
|
||
|
|
exports.DEFAULT_WEEKLY_NOTE_FORMAT = DEFAULT_WEEKLY_NOTE_FORMAT;
|
||
|
|
exports.DEFAULT_YEARLY_NOTE_FORMAT = DEFAULT_YEARLY_NOTE_FORMAT;
|
||
|
|
exports.appHasDailyNotesPluginLoaded = appHasDailyNotesPluginLoaded;
|
||
|
|
exports.appHasMonthlyNotesPluginLoaded = appHasMonthlyNotesPluginLoaded;
|
||
|
|
exports.appHasQuarterlyNotesPluginLoaded = appHasQuarterlyNotesPluginLoaded;
|
||
|
|
exports.appHasWeeklyNotesPluginLoaded = appHasWeeklyNotesPluginLoaded;
|
||
|
|
exports.appHasYearlyNotesPluginLoaded = appHasYearlyNotesPluginLoaded;
|
||
|
|
exports.createDailyNote = createDailyNote2;
|
||
|
|
exports.createMonthlyNote = createMonthlyNote2;
|
||
|
|
exports.createPeriodicNote = createPeriodicNote;
|
||
|
|
exports.createQuarterlyNote = createQuarterlyNote2;
|
||
|
|
exports.createWeeklyNote = createWeeklyNote2;
|
||
|
|
exports.createYearlyNote = createYearlyNote2;
|
||
|
|
exports.getAllDailyNotes = getAllDailyNotes2;
|
||
|
|
exports.getAllMonthlyNotes = getAllMonthlyNotes2;
|
||
|
|
exports.getAllQuarterlyNotes = getAllQuarterlyNotes2;
|
||
|
|
exports.getAllWeeklyNotes = getAllWeeklyNotes2;
|
||
|
|
exports.getAllYearlyNotes = getAllYearlyNotes2;
|
||
|
|
exports.getDailyNote = getDailyNote2;
|
||
|
|
exports.getDailyNoteSettings = getDailyNoteSettings;
|
||
|
|
exports.getDateFromFile = getDateFromFile;
|
||
|
|
exports.getDateFromPath = getDateFromPath;
|
||
|
|
exports.getDateUID = getDateUID;
|
||
|
|
exports.getMonthlyNote = getMonthlyNote2;
|
||
|
|
exports.getMonthlyNoteSettings = getMonthlyNoteSettings;
|
||
|
|
exports.getPeriodicNoteSettings = getPeriodicNoteSettings;
|
||
|
|
exports.getQuarterlyNote = getQuarterlyNote2;
|
||
|
|
exports.getQuarterlyNoteSettings = getQuarterlyNoteSettings;
|
||
|
|
exports.getTemplateInfo = getTemplateInfo;
|
||
|
|
exports.getWeeklyNote = getWeeklyNote2;
|
||
|
|
exports.getWeeklyNoteSettings = getWeeklyNoteSettings;
|
||
|
|
exports.getYearlyNote = getYearlyNote2;
|
||
|
|
exports.getYearlyNoteSettings = getYearlyNoteSettings;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// src/main.ts
|
||
|
|
var main_exports = {};
|
||
|
|
__export(main_exports, {
|
||
|
|
default: () => HomeBasePlugin
|
||
|
|
});
|
||
|
|
module.exports = __toCommonJS(main_exports);
|
||
|
|
var import_obsidian11 = require("obsidian");
|
||
|
|
|
||
|
|
// src/settings.ts
|
||
|
|
var HomeBaseType = /* @__PURE__ */ ((HomeBaseType2) => {
|
||
|
|
HomeBaseType2["File"] = "File";
|
||
|
|
HomeBaseType2["Workspace"] = "Workspace";
|
||
|
|
HomeBaseType2["Random"] = "Random file";
|
||
|
|
HomeBaseType2["RandomFolder"] = "Random in folder";
|
||
|
|
HomeBaseType2["Graph"] = "Graph view";
|
||
|
|
HomeBaseType2["None"] = "Nothing";
|
||
|
|
HomeBaseType2["Journal"] = "Journal";
|
||
|
|
HomeBaseType2["NewNote"] = "New note";
|
||
|
|
HomeBaseType2["DailyNote"] = "Daily Note";
|
||
|
|
HomeBaseType2["WeeklyNote"] = "Weekly Note";
|
||
|
|
HomeBaseType2["MonthlyNote"] = "Monthly Note";
|
||
|
|
HomeBaseType2["QuarterlyNote"] = "Quarterly Note";
|
||
|
|
HomeBaseType2["YearlyNote"] = "Yearly Note";
|
||
|
|
return HomeBaseType2;
|
||
|
|
})(HomeBaseType || {});
|
||
|
|
var DEFAULT_SETTINGS = {
|
||
|
|
// General
|
||
|
|
homeBaseType: "File" /* File */,
|
||
|
|
homeBaseValue: "",
|
||
|
|
openOnStartup: true,
|
||
|
|
openViewMode: "default",
|
||
|
|
openMode: "replace-all",
|
||
|
|
manualOpenMode: "retain",
|
||
|
|
// Tab Behavior
|
||
|
|
replaceNewTab: false,
|
||
|
|
newTabMode: "only-when-empty",
|
||
|
|
// Default: only replace when no tabs are open
|
||
|
|
openWhenAllTabsClosed: true,
|
||
|
|
// Default: open home base when all tabs are closed
|
||
|
|
useDifferentFileForNewTab: false,
|
||
|
|
newTabType: "File" /* File */,
|
||
|
|
newTabValue: "",
|
||
|
|
newTabSeparateMobile: false,
|
||
|
|
mobileNewTabType: "File" /* File */,
|
||
|
|
mobileNewTabValue: "",
|
||
|
|
// UI Features
|
||
|
|
showStickyHomeIcon: false,
|
||
|
|
stickyIconName: "home",
|
||
|
|
hideHomeTabHeader: false,
|
||
|
|
replaceMobileNewTab: false,
|
||
|
|
// Mobile
|
||
|
|
separateMobile: false,
|
||
|
|
mobileHomeBaseType: "File" /* File */,
|
||
|
|
mobileHomeBaseValue: "",
|
||
|
|
// Automation
|
||
|
|
commandOnOpen: "",
|
||
|
|
waitForGitSync: false,
|
||
|
|
gitSyncTimeout: 3,
|
||
|
|
// Default 3 seconds
|
||
|
|
// View behavior
|
||
|
|
revertView: false,
|
||
|
|
autoScroll: false,
|
||
|
|
hideReleaseNotes: false
|
||
|
|
// OFF by default
|
||
|
|
};
|
||
|
|
var VIEW_MODE_OPTIONS = {
|
||
|
|
"default": "Default",
|
||
|
|
"preview": "Reading view",
|
||
|
|
"source": "Source mode",
|
||
|
|
"live": "Live Preview"
|
||
|
|
};
|
||
|
|
var NEW_TAB_MODE_OPTIONS = {
|
||
|
|
"only-when-empty": "Only when no tabs are open",
|
||
|
|
"always": "Always replace new tabs"
|
||
|
|
};
|
||
|
|
var OPENING_MODE_OPTIONS = {
|
||
|
|
"replace-all": "Replace all open notes",
|
||
|
|
"replace-last": "Replace last note",
|
||
|
|
"retain": "Keep open notes"
|
||
|
|
};
|
||
|
|
var UNCHANGEABLE_TYPES = [
|
||
|
|
"Random file" /* Random */,
|
||
|
|
"Graph view" /* Graph */,
|
||
|
|
"Nothing" /* None */,
|
||
|
|
"Daily Note" /* DailyNote */,
|
||
|
|
"Weekly Note" /* WeeklyNote */,
|
||
|
|
"Monthly Note" /* MonthlyNote */,
|
||
|
|
"Quarterly Note" /* QuarterlyNote */,
|
||
|
|
"Yearly Note" /* YearlyNote */
|
||
|
|
];
|
||
|
|
|
||
|
|
// src/ui/settings-tab.ts
|
||
|
|
var import_obsidian4 = require("obsidian");
|
||
|
|
|
||
|
|
// src/ui/file-suggest.ts
|
||
|
|
var import_obsidian = require("obsidian");
|
||
|
|
var SUPPORTED_EXTENSIONS = ["md", "mdx", "canvas", "base"];
|
||
|
|
var FilePathSuggest = class extends import_obsidian.AbstractInputSuggest {
|
||
|
|
constructor(app, inputEl) {
|
||
|
|
super(app, inputEl);
|
||
|
|
this.inputEl = inputEl;
|
||
|
|
}
|
||
|
|
getSuggestions(query) {
|
||
|
|
const lowerQuery = query.toLowerCase();
|
||
|
|
const files = [];
|
||
|
|
this.app.vault.getAllLoadedFiles().forEach((file) => {
|
||
|
|
if (file instanceof import_obsidian.TFile) {
|
||
|
|
if (SUPPORTED_EXTENSIONS.includes(file.extension)) {
|
||
|
|
if (file.path.toLowerCase().includes(lowerQuery) || file.basename.toLowerCase().includes(lowerQuery)) {
|
||
|
|
files.push(file);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
files.sort((a, b) => {
|
||
|
|
const aStartsWith = a.path.toLowerCase().startsWith(lowerQuery);
|
||
|
|
const bStartsWith = b.path.toLowerCase().startsWith(lowerQuery);
|
||
|
|
if (aStartsWith && !bStartsWith) return -1;
|
||
|
|
if (!aStartsWith && bStartsWith) return 1;
|
||
|
|
return a.path.localeCompare(b.path);
|
||
|
|
});
|
||
|
|
return files.slice(0, 20);
|
||
|
|
}
|
||
|
|
renderSuggestion(file, el) {
|
||
|
|
el.addClass("home-base-suggestion-item");
|
||
|
|
const titleEl = el.createEl("div", {
|
||
|
|
cls: "suggestion-title"
|
||
|
|
});
|
||
|
|
titleEl.createEl("span", { text: file.basename });
|
||
|
|
if (file.extension !== "md") {
|
||
|
|
titleEl.createEl("span", {
|
||
|
|
text: file.extension.toUpperCase(),
|
||
|
|
cls: "suggestion-flair"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (file.parent && file.parent.path !== "/") {
|
||
|
|
el.createEl("div", {
|
||
|
|
text: file.parent.path,
|
||
|
|
cls: "suggestion-note"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
selectSuggestion(file) {
|
||
|
|
this.inputEl.value = file.path;
|
||
|
|
this.inputEl.trigger("input");
|
||
|
|
this.close();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var FolderSuggest = class extends import_obsidian.AbstractInputSuggest {
|
||
|
|
constructor(app, inputEl) {
|
||
|
|
super(app, inputEl);
|
||
|
|
this.inputEl = inputEl;
|
||
|
|
}
|
||
|
|
getSuggestions(query) {
|
||
|
|
const lowerQuery = query.toLowerCase();
|
||
|
|
const folders = [];
|
||
|
|
this.app.vault.getAllLoadedFiles().forEach((file) => {
|
||
|
|
if (file instanceof import_obsidian.TFolder) {
|
||
|
|
if (file.path.toLowerCase().includes(lowerQuery)) {
|
||
|
|
folders.push(file);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
folders.sort((a, b) => a.path.localeCompare(b.path));
|
||
|
|
return folders.slice(0, 20);
|
||
|
|
}
|
||
|
|
renderSuggestion(folder, el) {
|
||
|
|
el.createEl("div", { text: folder.path || "/" });
|
||
|
|
}
|
||
|
|
selectSuggestion(folder) {
|
||
|
|
this.inputEl.value = folder.path;
|
||
|
|
this.inputEl.trigger("input");
|
||
|
|
this.close();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var WorkspaceSuggest = class extends import_obsidian.AbstractInputSuggest {
|
||
|
|
constructor(app, inputEl) {
|
||
|
|
super(app, inputEl);
|
||
|
|
this.inputEl = inputEl;
|
||
|
|
}
|
||
|
|
getSuggestions(query) {
|
||
|
|
var _a, _b, _c;
|
||
|
|
const workspacesPlugin = (_b = (_a = this.app.internalPlugins) == null ? void 0 : _a.plugins) == null ? void 0 : _b.workspaces;
|
||
|
|
if (!(workspacesPlugin == null ? void 0 : workspacesPlugin.enabled) || !((_c = workspacesPlugin.instance) == null ? void 0 : _c.workspaces)) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
const workspaces = Object.keys(workspacesPlugin.instance.workspaces);
|
||
|
|
const lowerQuery = query.toLowerCase();
|
||
|
|
return workspaces.filter(
|
||
|
|
(workspace) => workspace.toLowerCase().includes(lowerQuery)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
renderSuggestion(workspace, el) {
|
||
|
|
el.createEl("div", { text: workspace });
|
||
|
|
}
|
||
|
|
selectSuggestion(workspace) {
|
||
|
|
this.inputEl.value = workspace;
|
||
|
|
this.inputEl.trigger("input");
|
||
|
|
this.close();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/ui/command-suggest.ts
|
||
|
|
var import_obsidian2 = require("obsidian");
|
||
|
|
var CommandSuggest = class extends import_obsidian2.AbstractInputSuggest {
|
||
|
|
constructor(app, inputEl) {
|
||
|
|
super(app, inputEl);
|
||
|
|
this.inputEl = inputEl;
|
||
|
|
}
|
||
|
|
getSuggestions(query) {
|
||
|
|
var _a;
|
||
|
|
const lowerQuery = query.toLowerCase();
|
||
|
|
const commands = [];
|
||
|
|
const appWithCommands = this.app;
|
||
|
|
const allCommands = (_a = appWithCommands.commands) == null ? void 0 : _a.commands;
|
||
|
|
if (allCommands) {
|
||
|
|
for (const command of Object.values(allCommands)) {
|
||
|
|
if (command.name.toLowerCase().includes(lowerQuery) || command.id.toLowerCase().includes(lowerQuery)) {
|
||
|
|
commands.push(command);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
commands.sort((a, b) => a.name.localeCompare(b.name));
|
||
|
|
return commands.slice(0, 30);
|
||
|
|
}
|
||
|
|
renderSuggestion(command, el) {
|
||
|
|
el.createEl("div", {
|
||
|
|
text: command.name,
|
||
|
|
cls: "suggestion-title"
|
||
|
|
});
|
||
|
|
el.createEl("small", {
|
||
|
|
text: command.id,
|
||
|
|
cls: "suggestion-note"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
selectSuggestion(command) {
|
||
|
|
this.inputEl.value = command.id;
|
||
|
|
this.inputEl.trigger("input");
|
||
|
|
this.close();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
function getCommandById(app, commandId) {
|
||
|
|
var _a;
|
||
|
|
const appWithCommands = app;
|
||
|
|
const commands = (_a = appWithCommands.commands) == null ? void 0 : _a.commands;
|
||
|
|
return commands == null ? void 0 : commands[commandId];
|
||
|
|
}
|
||
|
|
function executeCommand(app, commandId) {
|
||
|
|
var _a, _b;
|
||
|
|
if (!commandId) return false;
|
||
|
|
const appWithCommands = app;
|
||
|
|
const result = (_b = (_a = appWithCommands.commands) == null ? void 0 : _a.executeCommandById) == null ? void 0 : _b.call(_a, commandId);
|
||
|
|
return result !== false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// src/ui/icon-picker.ts
|
||
|
|
var import_obsidian3 = require("obsidian");
|
||
|
|
var IconPicker = class extends import_obsidian3.Modal {
|
||
|
|
constructor(app, currentIcon, callback) {
|
||
|
|
super(app);
|
||
|
|
this.searchResults = [];
|
||
|
|
this.selectedIcon = currentIcon;
|
||
|
|
this.callback = callback;
|
||
|
|
}
|
||
|
|
onOpen() {
|
||
|
|
this.containerEl.addClass("mod-confirmation");
|
||
|
|
this.modalEl.addClass("iconic-icon-picker");
|
||
|
|
this.setTitle("Change icon");
|
||
|
|
const searchSetting = new import_obsidian3.Setting(this.contentEl);
|
||
|
|
if (!import_obsidian3.Platform.isPhone) {
|
||
|
|
searchSetting.setName("Search");
|
||
|
|
}
|
||
|
|
searchSetting.addSearch((searchField) => {
|
||
|
|
searchField.setPlaceholder("Search icons...").onChange(() => this.updateSearchResults());
|
||
|
|
searchField.inputEl.enterKeyHint = "go";
|
||
|
|
this.searchField = searchField;
|
||
|
|
});
|
||
|
|
if (this.selectedIcon) {
|
||
|
|
this.searchField.setValue(this.selectedIcon);
|
||
|
|
}
|
||
|
|
this.searchResultsSetting = new import_obsidian3.Setting(this.contentEl);
|
||
|
|
this.searchResultsSetting.settingEl.addClass("iconic-search-results");
|
||
|
|
this.searchResultsSetting.settingEl.tabIndex = 0;
|
||
|
|
this.searchResultsSetting.settingEl.addEventListener("wheel", (event) => {
|
||
|
|
if (document.body.hasClass("mod-rtl")) {
|
||
|
|
this.searchResultsSetting.settingEl.scrollLeft -= event.deltaY;
|
||
|
|
} else {
|
||
|
|
this.searchResultsSetting.settingEl.scrollLeft += event.deltaY;
|
||
|
|
}
|
||
|
|
}, { passive: true });
|
||
|
|
const buttonContainer = this.modalEl.createDiv({ cls: "modal-button-container" });
|
||
|
|
new import_obsidian3.ButtonComponent(buttonContainer).setButtonText("Cancel").onClick(() => this.close()).buttonEl.addClass("mod-cancel");
|
||
|
|
new import_obsidian3.ButtonComponent(buttonContainer).setButtonText("Save").setCta().onClick(() => {
|
||
|
|
this.callback(this.selectedIcon);
|
||
|
|
this.close();
|
||
|
|
});
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
this.searchField.inputEl.select();
|
||
|
|
this.updateSearchResults();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update search results based on current query
|
||
|
|
*/
|
||
|
|
updateSearchResults() {
|
||
|
|
const query = this.searchField.getValue().toLowerCase().trim();
|
||
|
|
const fuzzySearch = (0, import_obsidian3.prepareFuzzySearch)(query);
|
||
|
|
const matches = [];
|
||
|
|
const iconIds = (0, import_obsidian3.getIconIds)();
|
||
|
|
if (query) {
|
||
|
|
for (const iconId of iconIds) {
|
||
|
|
const iconName = this.formatIconName(iconId);
|
||
|
|
if (iconId === query || iconId.toLowerCase() === query) {
|
||
|
|
matches.push([0, [iconId, iconName]]);
|
||
|
|
} else {
|
||
|
|
const fuzzyMatch = fuzzySearch(iconName);
|
||
|
|
if (fuzzyMatch) {
|
||
|
|
matches.push([fuzzyMatch.score, [iconId, iconName]]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
for (const iconId of iconIds) {
|
||
|
|
const iconName = this.formatIconName(iconId);
|
||
|
|
matches.push([0, [iconId, iconName]]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
matches.sort(([scoreA], [scoreB]) => scoreA > scoreB ? -1 : 1);
|
||
|
|
this.searchResults.length = 0;
|
||
|
|
const maxResults = 100;
|
||
|
|
for (const [, iconEntry] of matches) {
|
||
|
|
this.searchResults.push(iconEntry);
|
||
|
|
if (this.searchResults.length >= maxResults) break;
|
||
|
|
}
|
||
|
|
this.searchResultsSetting.clear();
|
||
|
|
for (const [iconId, iconName] of this.searchResults) {
|
||
|
|
this.searchResultsSetting.addExtraButton((iconButton) => {
|
||
|
|
iconButton.setTooltip(iconName, {
|
||
|
|
delay: 300,
|
||
|
|
placement: import_obsidian3.Platform.isPhone ? "top" : "bottom"
|
||
|
|
});
|
||
|
|
const iconEl = iconButton.extraSettingsEl;
|
||
|
|
iconEl.addClass("iconic-search-result");
|
||
|
|
iconEl.tabIndex = -1;
|
||
|
|
(0, import_obsidian3.setIcon)(iconEl, iconId);
|
||
|
|
if (iconId === this.selectedIcon) {
|
||
|
|
iconEl.addClass("is-selected");
|
||
|
|
}
|
||
|
|
iconEl.addEventListener("click", () => {
|
||
|
|
this.selectedIcon = iconId;
|
||
|
|
this.callback(iconId);
|
||
|
|
this.close();
|
||
|
|
});
|
||
|
|
if (import_obsidian3.Platform.isPhone) {
|
||
|
|
iconEl.addEventListener("contextmenu", () => {
|
||
|
|
var _a;
|
||
|
|
(_a = navigator.vibrate) == null ? void 0 : _a.call(navigator, 100);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (this.searchResults.length === 0) {
|
||
|
|
this.searchResultsSetting.addExtraButton((button) => {
|
||
|
|
button.extraSettingsEl.addClasses(["iconic-invisible", "iconic-search-result"]);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Format icon ID into readable name
|
||
|
|
*/
|
||
|
|
formatIconName(iconId) {
|
||
|
|
let name = iconId.replace(/^lucide-/, "");
|
||
|
|
name = name.replace(/-/g, " ");
|
||
|
|
return name.split(" ").map(
|
||
|
|
(word) => word.charAt(0).toUpperCase() + word.slice(1)
|
||
|
|
).join(" ");
|
||
|
|
}
|
||
|
|
onClose() {
|
||
|
|
this.contentEl.empty();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/ui/settings-tab.ts
|
||
|
|
var HomeBaseSettingTab = class extends import_obsidian4.PluginSettingTab {
|
||
|
|
constructor(app, plugin) {
|
||
|
|
super(app, plugin);
|
||
|
|
this.icon = "lucide-house";
|
||
|
|
this.plugin = plugin;
|
||
|
|
}
|
||
|
|
display() {
|
||
|
|
const { containerEl } = this;
|
||
|
|
containerEl.empty();
|
||
|
|
const isMobile = this.plugin.settings.separateMobile;
|
||
|
|
const activeType = isMobile ? this.plugin.settings.mobileHomeBaseType : this.plugin.settings.homeBaseType;
|
||
|
|
const activeValue = isMobile ? this.plugin.settings.mobileHomeBaseValue : this.plugin.settings.homeBaseValue;
|
||
|
|
const generalGroup = new import_obsidian4.SettingGroup(containerEl);
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Type").setDesc("What to open as your home base").addDropdown((dropdown) => {
|
||
|
|
let pluginDisabled = false;
|
||
|
|
for (const type of Object.values(HomeBaseType)) {
|
||
|
|
if (!this.plugin.hasRequiredPlugin(type)) {
|
||
|
|
if (type === activeType) {
|
||
|
|
pluginDisabled = true;
|
||
|
|
dropdown.addOption(type, type);
|
||
|
|
} else {
|
||
|
|
dropdown.selectEl.createEl("option", {
|
||
|
|
text: type,
|
||
|
|
attr: { disabled: "true" }
|
||
|
|
});
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
dropdown.addOption(type, type);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
dropdown.setValue(activeType || "File" /* File */).onChange(async (value) => {
|
||
|
|
if (isMobile) {
|
||
|
|
this.plugin.settings.mobileHomeBaseType = value;
|
||
|
|
} else {
|
||
|
|
this.plugin.settings.homeBaseType = value;
|
||
|
|
}
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (pluginDisabled) {
|
||
|
|
setting.descEl.createDiv({
|
||
|
|
text: "The required plugin has not been enabled or configured for this type.",
|
||
|
|
cls: "mod-warning"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (!UNCHANGEABLE_TYPES.includes(activeType)) {
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
let desc = "";
|
||
|
|
let placeholder = "";
|
||
|
|
if (activeType === "File" /* File */) {
|
||
|
|
desc = "The file to open as your home base (supports .md, .mdx, .canvas, .base)";
|
||
|
|
placeholder = "Path to home base file";
|
||
|
|
} else if (activeType === "Workspace" /* Workspace */) {
|
||
|
|
desc = "The workspace to load as your home base";
|
||
|
|
placeholder = "Workspace name";
|
||
|
|
} else if (activeType === "Random in folder" /* RandomFolder */ || activeType === "New note" /* NewNote */) {
|
||
|
|
desc = activeType === "Random in folder" /* RandomFolder */ ? "The folder to pick a random file from" : "The folder to create new notes in";
|
||
|
|
placeholder = "Folder path";
|
||
|
|
} else if (activeType === "Journal" /* Journal */) {
|
||
|
|
desc = "The journal name";
|
||
|
|
placeholder = "Journal name";
|
||
|
|
}
|
||
|
|
setting.setName(activeType === "File" /* File */ ? "File" : activeType === "Workspace" /* Workspace */ ? "Workspace" : activeType === "Random in folder" /* RandomFolder */ || activeType === "New note" /* NewNote */ ? "Folder" : activeType === "Journal" /* Journal */ ? "Journal" : "Value").setDesc(desc).addText((text) => {
|
||
|
|
if (activeType === "File" /* File */) {
|
||
|
|
new FilePathSuggest(this.app, text.inputEl);
|
||
|
|
} else if (activeType === "Workspace" /* Workspace */) {
|
||
|
|
new WorkspaceSuggest(this.app, text.inputEl);
|
||
|
|
} else if (activeType === "Random in folder" /* RandomFolder */ || activeType === "New note" /* NewNote */) {
|
||
|
|
new FolderSuggest(this.app, text.inputEl);
|
||
|
|
}
|
||
|
|
text.setPlaceholder(placeholder).setValue(activeValue || "").onChange(async (value) => {
|
||
|
|
if (isMobile) {
|
||
|
|
this.plugin.settings.mobileHomeBaseValue = value;
|
||
|
|
} else {
|
||
|
|
this.plugin.settings.homeBaseValue = value;
|
||
|
|
}
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Open on startup").setDesc("Open the home base when launching Obsidian").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.openOnStartup).onChange(async (value) => {
|
||
|
|
this.plugin.settings.openOnStartup = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if ((0, import_obsidian4.requireApiVersion)("1.11.0")) {
|
||
|
|
const nativeOpenBehavior = this.plugin.homeService.getNativeOpenBehavior();
|
||
|
|
if (nativeOpenBehavior) {
|
||
|
|
setting.descEl.createDiv({
|
||
|
|
text: `Note: This will override Obsidian's native "Default file to open" setting (currently set to "${nativeOpenBehavior}").`,
|
||
|
|
cls: "mod-warning"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Opening mode (startup)").setDesc("How to handle existing tabs when opening on startup").addDropdown((dropdown) => {
|
||
|
|
for (const [value, label] of Object.entries(OPENING_MODE_OPTIONS)) {
|
||
|
|
dropdown.addOption(value, label);
|
||
|
|
}
|
||
|
|
dropdown.setValue(this.plugin.settings.openMode).onChange(async (value) => {
|
||
|
|
this.plugin.settings.openMode = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Opening mode (manual)").setDesc("How to handle existing tabs when opening manually").addDropdown((dropdown) => {
|
||
|
|
for (const [value, label] of Object.entries(OPENING_MODE_OPTIONS)) {
|
||
|
|
dropdown.addOption(value, label);
|
||
|
|
}
|
||
|
|
dropdown.setValue(this.plugin.settings.manualOpenMode).onChange(async (value) => {
|
||
|
|
this.plugin.settings.manualOpenMode = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
setting.setName("View mode").setDesc("How to open Markdown files").addDropdown((dropdown) => {
|
||
|
|
for (const [value, label] of Object.entries(VIEW_MODE_OPTIONS)) {
|
||
|
|
dropdown.addOption(value, label);
|
||
|
|
}
|
||
|
|
dropdown.setValue(this.plugin.settings.openViewMode).onChange(async (value) => {
|
||
|
|
this.plugin.settings.openViewMode = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Revert view on close").setDesc("When navigating away from the home base, restore the default view").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.revertView).onChange(async (value) => {
|
||
|
|
this.plugin.settings.revertView = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Auto-scroll").setDesc("When opening the home base, scroll to the bottom and focus on the last line").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.autoScroll).onChange(async (value) => {
|
||
|
|
this.plugin.settings.autoScroll = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
generalGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Hide release notes").setDesc("Never display release notes when Obsidian updates").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.hideReleaseNotes).onChange(async (value) => {
|
||
|
|
this.plugin.settings.hideReleaseNotes = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
const tabGroup = new import_obsidian4.SettingGroup(containerEl).setHeading("Tab Behavior");
|
||
|
|
tabGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Open home base when all tabs are closed").setDesc("When you close all tabs, automatically open the home base").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.openWhenAllTabsClosed).onChange(async (value) => {
|
||
|
|
this.plugin.settings.openWhenAllTabsClosed = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
tabGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Replace new tabs").setDesc('Open home base instead of new empty tabs (works independently of "open home base when all tabs are closed")').addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.replaceNewTab).onChange(async (value) => {
|
||
|
|
this.plugin.settings.replaceNewTab = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (this.plugin.settings.replaceNewTab) {
|
||
|
|
tabGroup.addSetting((setting) => {
|
||
|
|
setting.setName("New tab replacement mode").setDesc("When to replace new tabs (only when no tabs are open, or always)").addDropdown((dropdown) => {
|
||
|
|
for (const [value, label] of Object.entries(NEW_TAB_MODE_OPTIONS)) {
|
||
|
|
dropdown.addOption(value, label);
|
||
|
|
}
|
||
|
|
dropdown.setValue(this.plugin.settings.newTabMode).onChange(async (value) => {
|
||
|
|
this.plugin.settings.newTabMode = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
tabGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Use different home base for new tabs").setDesc("Configure a different home base to open for new tabs (instead of the main home base)").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.useDifferentFileForNewTab).onChange(async (value) => {
|
||
|
|
this.plugin.settings.useDifferentFileForNewTab = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (this.plugin.settings.useDifferentFileForNewTab) {
|
||
|
|
const desktopType = this.plugin.settings.newTabType || "File" /* File */;
|
||
|
|
const desktopValue = this.plugin.settings.newTabValue || "";
|
||
|
|
tabGroup.addSetting((setting) => {
|
||
|
|
setting.setName("New tab type").setDesc("What to open for new tabs").addDropdown((dropdown) => {
|
||
|
|
let pluginDisabled = false;
|
||
|
|
for (const type of Object.values(HomeBaseType)) {
|
||
|
|
if (!this.plugin.hasRequiredPlugin(type)) {
|
||
|
|
if (type === desktopType) {
|
||
|
|
pluginDisabled = true;
|
||
|
|
dropdown.addOption(type, type);
|
||
|
|
} else {
|
||
|
|
dropdown.selectEl.createEl("option", {
|
||
|
|
text: type,
|
||
|
|
attr: { disabled: "true" }
|
||
|
|
});
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
dropdown.addOption(type, type);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
dropdown.setValue(desktopType).onChange(async (value) => {
|
||
|
|
this.plugin.settings.newTabType = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (pluginDisabled) {
|
||
|
|
setting.descEl.createDiv({
|
||
|
|
text: "The required plugin has not been enabled or configured for this type.",
|
||
|
|
cls: "mod-warning"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (!UNCHANGEABLE_TYPES.includes(desktopType)) {
|
||
|
|
tabGroup.addSetting((setting) => {
|
||
|
|
let desc = "";
|
||
|
|
let placeholder = "";
|
||
|
|
if (desktopType === "File" /* File */) {
|
||
|
|
desc = "The file to open for new tabs (supports .md, .mdx, .canvas, .base)";
|
||
|
|
placeholder = "Path to new tab file";
|
||
|
|
} else if (desktopType === "Workspace" /* Workspace */) {
|
||
|
|
desc = "The workspace to load for new tabs";
|
||
|
|
placeholder = "Workspace name";
|
||
|
|
} else if (desktopType === "Random in folder" /* RandomFolder */) {
|
||
|
|
desc = "The folder to pick a random file from for new tabs";
|
||
|
|
placeholder = "Folder path";
|
||
|
|
} else if (desktopType === "Journal" /* Journal */) {
|
||
|
|
desc = "The journal name for new tabs";
|
||
|
|
placeholder = "Journal name";
|
||
|
|
}
|
||
|
|
setting.setName(desktopType === "File" /* File */ ? "New tab file" : desktopType === "Workspace" /* Workspace */ ? "New tab workspace" : desktopType === "Random in folder" /* RandomFolder */ ? "New tab folder" : desktopType === "Journal" /* Journal */ ? "New tab journal" : "New tab value").setDesc(desc).addText((text) => {
|
||
|
|
if (desktopType === "File" /* File */) {
|
||
|
|
new FilePathSuggest(this.app, text.inputEl);
|
||
|
|
} else if (desktopType === "Workspace" /* Workspace */) {
|
||
|
|
new WorkspaceSuggest(this.app, text.inputEl);
|
||
|
|
} else if (desktopType === "Random in folder" /* RandomFolder */) {
|
||
|
|
new FolderSuggest(this.app, text.inputEl);
|
||
|
|
}
|
||
|
|
text.setPlaceholder(placeholder).setValue(desktopValue || "").onChange(async (value) => {
|
||
|
|
this.plugin.settings.newTabValue = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const uiGroup = new import_obsidian4.SettingGroup(containerEl).setHeading("UI Features");
|
||
|
|
uiGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Sticky home icon").setDesc("Show a home icon in the tab bar that stays pinned to the left (desktop only)").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.showStickyHomeIcon).onChange(async (value) => {
|
||
|
|
this.plugin.settings.showStickyHomeIcon = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
this.plugin.updateStickyTabIcon();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (this.plugin.settings.showStickyHomeIcon) {
|
||
|
|
uiGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Icon").setDesc("The icon to display in the sticky home icon").addButton((button) => {
|
||
|
|
const iconName = this.plugin.settings.stickyIconName || "home";
|
||
|
|
button.setButtonText("Change icon").setIcon(iconName).onClick(() => {
|
||
|
|
const picker = new IconPicker(
|
||
|
|
this.app,
|
||
|
|
this.plugin.settings.stickyIconName,
|
||
|
|
(icon) => {
|
||
|
|
void (async () => {
|
||
|
|
this.plugin.settings.stickyIconName = icon;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
this.plugin.stickyTabService.update();
|
||
|
|
this.display();
|
||
|
|
})();
|
||
|
|
}
|
||
|
|
);
|
||
|
|
picker.open();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
uiGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Hide tab header").setDesc("Hide the sticky home tab header when it's open, using the sticky icon as the tab indicator").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.hideHomeTabHeader).onChange(async (value) => {
|
||
|
|
this.plugin.settings.hideHomeTabHeader = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
this.plugin.stickyTabService.updateTabHeaders();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
const mobileGroup = new import_obsidian4.SettingGroup(containerEl).setHeading("Mobile");
|
||
|
|
mobileGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Separate mobile home base").setDesc("Use a different home base on mobile devices").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.separateMobile).onChange(async (value) => {
|
||
|
|
this.plugin.settings.separateMobile = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (this.plugin.settings.separateMobile) {
|
||
|
|
mobileGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Mobile home base").setDesc("What to open as your home base on mobile").addDropdown((dropdown) => {
|
||
|
|
const mobileType = this.plugin.settings.mobileHomeBaseType || "File" /* File */;
|
||
|
|
let pluginDisabled = false;
|
||
|
|
for (const type of Object.values(HomeBaseType)) {
|
||
|
|
if (!this.plugin.hasRequiredPlugin(type)) {
|
||
|
|
if (type === mobileType) {
|
||
|
|
pluginDisabled = true;
|
||
|
|
dropdown.addOption(type, type);
|
||
|
|
} else {
|
||
|
|
dropdown.selectEl.createEl("option", {
|
||
|
|
text: type,
|
||
|
|
attr: { disabled: "true" }
|
||
|
|
});
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
dropdown.addOption(type, type);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
dropdown.setValue(mobileType).onChange(async (value) => {
|
||
|
|
this.plugin.settings.mobileHomeBaseType = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (pluginDisabled) {
|
||
|
|
setting.descEl.createDiv({
|
||
|
|
text: "The required plugin has not been enabled or configured for this type.",
|
||
|
|
cls: "mod-warning"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (!UNCHANGEABLE_TYPES.includes(this.plugin.settings.mobileHomeBaseType)) {
|
||
|
|
mobileGroup.addSetting((setting) => {
|
||
|
|
const mobileType = this.plugin.settings.mobileHomeBaseType;
|
||
|
|
let desc = "";
|
||
|
|
let placeholder = "";
|
||
|
|
if (mobileType === "File" /* File */) {
|
||
|
|
desc = "The file to open as your home base on mobile";
|
||
|
|
placeholder = "Path to home base file";
|
||
|
|
} else if (mobileType === "Workspace" /* Workspace */) {
|
||
|
|
desc = "The workspace to load as your home base on mobile";
|
||
|
|
placeholder = "Workspace name";
|
||
|
|
} else if (mobileType === "Random in folder" /* RandomFolder */) {
|
||
|
|
desc = "The folder to pick a random file from on mobile";
|
||
|
|
placeholder = "Folder path";
|
||
|
|
} else if (mobileType === "Journal" /* Journal */) {
|
||
|
|
desc = "The journal name for mobile";
|
||
|
|
placeholder = "Journal name";
|
||
|
|
}
|
||
|
|
setting.setName(mobileType === "File" /* File */ ? "Mobile file" : mobileType === "Workspace" /* Workspace */ ? "Mobile workspace" : mobileType === "Random in folder" /* RandomFolder */ ? "Mobile folder" : mobileType === "Journal" /* Journal */ ? "Mobile journal" : "Mobile value").setDesc(desc).addText((text) => {
|
||
|
|
if (mobileType === "File" /* File */) {
|
||
|
|
new FilePathSuggest(this.app, text.inputEl);
|
||
|
|
} else if (mobileType === "Workspace" /* Workspace */) {
|
||
|
|
new WorkspaceSuggest(this.app, text.inputEl);
|
||
|
|
} else if (mobileType === "Random in folder" /* RandomFolder */) {
|
||
|
|
new FolderSuggest(this.app, text.inputEl);
|
||
|
|
}
|
||
|
|
text.setPlaceholder(placeholder).setValue(this.plugin.settings.mobileHomeBaseValue || "").onChange(async (value) => {
|
||
|
|
this.plugin.settings.mobileHomeBaseValue = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
mobileGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Replace mobile new tab button").setDesc("Change the mobile new tab button to a home icon").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.replaceMobileNewTab).onChange(async (value) => {
|
||
|
|
this.plugin.settings.replaceMobileNewTab = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
this.plugin.updateMobileButton();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (this.plugin.settings.useDifferentFileForNewTab) {
|
||
|
|
mobileGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Separate mobile new tab").setDesc("Use a different new tab file on mobile devices").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.newTabSeparateMobile).onChange(async (value) => {
|
||
|
|
this.plugin.settings.newTabSeparateMobile = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (this.plugin.settings.newTabSeparateMobile) {
|
||
|
|
const mobileType = this.plugin.settings.mobileNewTabType || "File" /* File */;
|
||
|
|
const mobileValue = this.plugin.settings.mobileNewTabValue || "";
|
||
|
|
mobileGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Mobile new tab type").setDesc("What to open for new tabs on mobile").addDropdown((dropdown) => {
|
||
|
|
let pluginDisabled = false;
|
||
|
|
for (const type of Object.values(HomeBaseType)) {
|
||
|
|
if (!this.plugin.hasRequiredPlugin(type)) {
|
||
|
|
if (type === mobileType) {
|
||
|
|
pluginDisabled = true;
|
||
|
|
dropdown.addOption(type, type);
|
||
|
|
} else {
|
||
|
|
dropdown.selectEl.createEl("option", {
|
||
|
|
text: type,
|
||
|
|
attr: { disabled: "true" }
|
||
|
|
});
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
dropdown.addOption(type, type);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
dropdown.setValue(mobileType).onChange(async (value) => {
|
||
|
|
this.plugin.settings.mobileNewTabType = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (pluginDisabled) {
|
||
|
|
setting.descEl.createDiv({
|
||
|
|
text: "The required plugin has not been enabled or configured for this type.",
|
||
|
|
cls: "mod-warning"
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (!UNCHANGEABLE_TYPES.includes(mobileType)) {
|
||
|
|
mobileGroup.addSetting((setting) => {
|
||
|
|
let desc = "";
|
||
|
|
let placeholder = "";
|
||
|
|
if (mobileType === "File" /* File */) {
|
||
|
|
desc = "The file to open for new tabs on mobile";
|
||
|
|
placeholder = "Path to mobile new tab file";
|
||
|
|
} else if (mobileType === "Workspace" /* Workspace */) {
|
||
|
|
desc = "The workspace to load for new tabs on mobile";
|
||
|
|
placeholder = "Workspace name";
|
||
|
|
} else if (mobileType === "Random in folder" /* RandomFolder */) {
|
||
|
|
desc = "The folder to pick a random file from for new tabs on mobile";
|
||
|
|
placeholder = "Folder path";
|
||
|
|
} else if (mobileType === "Journal" /* Journal */) {
|
||
|
|
desc = "The journal name for new tabs on mobile";
|
||
|
|
placeholder = "Journal name";
|
||
|
|
}
|
||
|
|
setting.setName(mobileType === "File" /* File */ ? "Mobile new tab file" : mobileType === "Workspace" /* Workspace */ ? "Mobile new tab workspace" : mobileType === "Random in folder" /* RandomFolder */ ? "Mobile new tab folder" : mobileType === "Journal" /* Journal */ ? "Mobile new tab journal" : "Mobile new tab value").setDesc(desc).addText((text) => {
|
||
|
|
if (mobileType === "File" /* File */) {
|
||
|
|
new FilePathSuggest(this.app, text.inputEl);
|
||
|
|
} else if (mobileType === "Workspace" /* Workspace */) {
|
||
|
|
new WorkspaceSuggest(this.app, text.inputEl);
|
||
|
|
} else if (mobileType === "Random in folder" /* RandomFolder */) {
|
||
|
|
new FolderSuggest(this.app, text.inputEl);
|
||
|
|
}
|
||
|
|
text.setPlaceholder(placeholder).setValue(mobileValue || "").onChange(async (value) => {
|
||
|
|
this.plugin.settings.mobileNewTabValue = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const automationGroup = new import_obsidian4.SettingGroup(containerEl).setHeading("Automation");
|
||
|
|
automationGroup.addSetting((setting) => {
|
||
|
|
const commandId = this.plugin.settings.commandOnOpen;
|
||
|
|
const command = commandId ? getCommandById(this.app, commandId) : void 0;
|
||
|
|
const displayValue = command ? command.name : commandId;
|
||
|
|
setting.setName("Command on open").setDesc("Run an Obsidian command when opening home base").addText((text) => {
|
||
|
|
new CommandSuggest(this.app, text.inputEl);
|
||
|
|
text.setPlaceholder("Search for a command...").setValue(displayValue || "").onChange(async (value) => {
|
||
|
|
this.plugin.settings.commandOnOpen = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
});
|
||
|
|
}).addExtraButton((btn) => {
|
||
|
|
btn.setIcon("x").setTooltip("Clear command").onClick(async () => {
|
||
|
|
this.plugin.settings.commandOnOpen = "";
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
const scrollContainer = containerEl.closest(".vertical-tab-content") || containerEl.closest(".settings-content") || containerEl.parentElement;
|
||
|
|
const scrollTop = (scrollContainer == null ? void 0 : scrollContainer.scrollTop) || 0;
|
||
|
|
this.display();
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (scrollContainer) {
|
||
|
|
scrollContainer.scrollTop = scrollTop;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
automationGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Wait for git sync").setDesc("Wait before creating periodic or journal notes to allow git sync to finish pulling existing notes. Only applies when a note doesn't already exist.").addToggle((toggle) => {
|
||
|
|
toggle.setValue(this.plugin.settings.waitForGitSync).onChange(async (value) => {
|
||
|
|
this.plugin.settings.waitForGitSync = value;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
this.display();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (this.plugin.settings.waitForGitSync) {
|
||
|
|
automationGroup.addSetting((setting) => {
|
||
|
|
setting.setName("Git sync timeout (seconds)").setDesc("How long to wait for git sync to finish before creating a new note").addText((text) => {
|
||
|
|
var _a;
|
||
|
|
text.inputEl.type = "number";
|
||
|
|
text.setPlaceholder("3").setValue(((_a = this.plugin.settings.gitSyncTimeout) == null ? void 0 : _a.toString()) || "3").onChange(async (value) => {
|
||
|
|
const numValue = parseInt(value);
|
||
|
|
if (!isNaN(numValue) && numValue >= 0) {
|
||
|
|
this.plugin.settings.gitSyncTimeout = numValue;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/services/home-service.ts
|
||
|
|
var import_obsidian7 = require("obsidian");
|
||
|
|
|
||
|
|
// src/utils/file-utils.ts
|
||
|
|
var import_obsidian5 = require("obsidian");
|
||
|
|
var SUPPORTED_EXTENSIONS2 = ["md", "mdx", "canvas", "base", "kanban"];
|
||
|
|
function isSupportedExtension(extension) {
|
||
|
|
return SUPPORTED_EXTENSIONS2.includes(extension);
|
||
|
|
}
|
||
|
|
function isMarkdownLike(file) {
|
||
|
|
const ext = file.extension.toLowerCase();
|
||
|
|
return ext === "md" || ext === "mdx";
|
||
|
|
}
|
||
|
|
function getFileByPath(app, path) {
|
||
|
|
const exactMatch = app.vault.getAbstractFileByPath(path);
|
||
|
|
if (exactMatch instanceof import_obsidian5.TFile) {
|
||
|
|
return exactMatch;
|
||
|
|
}
|
||
|
|
const file = app.metadataCache.getFirstLinkpathDest(path, "/");
|
||
|
|
return file;
|
||
|
|
}
|
||
|
|
function trimFileExtension(path) {
|
||
|
|
const lastDot = path.lastIndexOf(".");
|
||
|
|
if (lastDot > 0) {
|
||
|
|
const ext = path.slice(lastDot + 1).toLowerCase();
|
||
|
|
if (SUPPORTED_EXTENSIONS2.includes(ext)) {
|
||
|
|
return path.slice(0, lastDot);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
function pathsEqual(path1, path2) {
|
||
|
|
const norm1 = trimFileExtension(path1).toLowerCase();
|
||
|
|
const norm2 = trimFileExtension(path2).toLowerCase();
|
||
|
|
return norm1 === norm2;
|
||
|
|
}
|
||
|
|
function leafHasFile(leaf, filePath) {
|
||
|
|
var _a, _b;
|
||
|
|
const state = (_b = (_a = leaf.view) == null ? void 0 : _a.getState) == null ? void 0 : _b.call(_a);
|
||
|
|
const leafFile = state == null ? void 0 : state.file;
|
||
|
|
if (!leafFile) return false;
|
||
|
|
return pathsEqual(leafFile, filePath);
|
||
|
|
}
|
||
|
|
|
||
|
|
// src/utils/homebase-resolver.ts
|
||
|
|
var import_obsidian6 = require("obsidian");
|
||
|
|
var import_obsidian_daily_notes_interface = __toESM(require_main(), 1);
|
||
|
|
function randomFile(app, root) {
|
||
|
|
let files = [];
|
||
|
|
if (root) {
|
||
|
|
const resolvedRoot = app.vault.getFolderByPath(root);
|
||
|
|
if (resolvedRoot) {
|
||
|
|
files = getFilesInFolder(resolvedRoot);
|
||
|
|
} else {
|
||
|
|
const allFiles = app.vault.getFiles();
|
||
|
|
const pattern = root.toLowerCase();
|
||
|
|
files = allFiles.filter((f) => {
|
||
|
|
const fileName = f.name.toLowerCase();
|
||
|
|
return fileName === pattern || fileName === pattern.replace(/\.md$/, "");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
files = app.vault.getFiles();
|
||
|
|
}
|
||
|
|
files = files.filter((f) => ["md", "canvas", "base"].includes(f.extension));
|
||
|
|
if (files.length) {
|
||
|
|
const index = Math.floor(Math.random() * files.length);
|
||
|
|
return files[index] || null;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
function getFilesInFolder(folder) {
|
||
|
|
let files = [];
|
||
|
|
for (const item of folder.children) {
|
||
|
|
if (item instanceof import_obsidian6.TFile) {
|
||
|
|
files.push(item);
|
||
|
|
} else if (item instanceof import_obsidian6.TFolder) {
|
||
|
|
files.push(...getFilesInFolder(item));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return files;
|
||
|
|
}
|
||
|
|
function trimFile(file) {
|
||
|
|
if (!file) return "";
|
||
|
|
return file.extension === "md" ? file.path.slice(0, -3) : file.path;
|
||
|
|
}
|
||
|
|
var PERIODIC_INFO = {
|
||
|
|
["Daily Note" /* DailyNote */]: {
|
||
|
|
noun: "day",
|
||
|
|
adjective: "daily",
|
||
|
|
create: import_obsidian_daily_notes_interface.createDailyNote,
|
||
|
|
get: import_obsidian_daily_notes_interface.getDailyNote,
|
||
|
|
getAll: import_obsidian_daily_notes_interface.getAllDailyNotes
|
||
|
|
},
|
||
|
|
["Weekly Note" /* WeeklyNote */]: {
|
||
|
|
noun: "week",
|
||
|
|
adjective: "weekly",
|
||
|
|
create: import_obsidian_daily_notes_interface.createWeeklyNote,
|
||
|
|
get: import_obsidian_daily_notes_interface.getWeeklyNote,
|
||
|
|
getAll: import_obsidian_daily_notes_interface.getAllWeeklyNotes
|
||
|
|
},
|
||
|
|
["Monthly Note" /* MonthlyNote */]: {
|
||
|
|
noun: "month",
|
||
|
|
adjective: "monthly",
|
||
|
|
create: import_obsidian_daily_notes_interface.createMonthlyNote,
|
||
|
|
get: import_obsidian_daily_notes_interface.getMonthlyNote,
|
||
|
|
getAll: import_obsidian_daily_notes_interface.getAllMonthlyNotes
|
||
|
|
},
|
||
|
|
["Quarterly Note" /* QuarterlyNote */]: {
|
||
|
|
noun: "quarter",
|
||
|
|
adjective: "quarterly",
|
||
|
|
create: import_obsidian_daily_notes_interface.createQuarterlyNote,
|
||
|
|
get: import_obsidian_daily_notes_interface.getQuarterlyNote,
|
||
|
|
getAll: import_obsidian_daily_notes_interface.getAllQuarterlyNotes
|
||
|
|
},
|
||
|
|
["Yearly Note" /* YearlyNote */]: {
|
||
|
|
noun: "year",
|
||
|
|
adjective: "yearly",
|
||
|
|
create: import_obsidian_daily_notes_interface.createYearlyNote,
|
||
|
|
get: import_obsidian_daily_notes_interface.getYearlyNote,
|
||
|
|
getAll: import_obsidian_daily_notes_interface.getAllYearlyNotes
|
||
|
|
},
|
||
|
|
["File" /* File */]: null,
|
||
|
|
["Random file" /* Random */]: null,
|
||
|
|
["Random in folder" /* RandomFolder */]: null,
|
||
|
|
["Workspace" /* Workspace */]: null,
|
||
|
|
["Graph view" /* Graph */]: null,
|
||
|
|
["Nothing" /* None */]: null,
|
||
|
|
["Journal" /* Journal */]: null,
|
||
|
|
["New note" /* NewNote */]: null
|
||
|
|
};
|
||
|
|
async function getPeriodicNote(kind, plugin) {
|
||
|
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
||
|
|
if (!window.moment) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const info = PERIODIC_INFO[kind];
|
||
|
|
if (!info) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const date = (0, import_obsidian6.moment)().startOf(info.noun);
|
||
|
|
const communityPlugins = ((_a = plugin.app.plugins) == null ? void 0 : _a.plugins) || {};
|
||
|
|
const periodicNotesPlugin = communityPlugins["periodic-notes"];
|
||
|
|
const isLegacy = !periodicNotesPlugin || (((_b = periodicNotesPlugin.manifest) == null ? void 0 : _b.version) || "0").startsWith("0");
|
||
|
|
let note = null;
|
||
|
|
if (isLegacy) {
|
||
|
|
let all = info.getAll();
|
||
|
|
note = info.get(date, all);
|
||
|
|
if (!note && plugin.settings.waitForGitSync) {
|
||
|
|
new import_obsidian6.Notice(`Home Base: Waiting for git sync (${plugin.settings.gitSyncTimeout}s)...`, 5e3);
|
||
|
|
await delay(plugin.settings.gitSyncTimeout * 1e3);
|
||
|
|
all = info.getAll();
|
||
|
|
note = info.get(date, all);
|
||
|
|
}
|
||
|
|
if (!note) {
|
||
|
|
note = await info.create(date);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
(_d = (_c = periodicNotesPlugin.cache) == null ? void 0 : _c.initialize) == null ? void 0 : _d.call(_c);
|
||
|
|
note = (_f = (_e = periodicNotesPlugin.getPeriodicNote) == null ? void 0 : _e.call(periodicNotesPlugin, info.noun, date)) != null ? _f : null;
|
||
|
|
if (!note && plugin.settings.waitForGitSync) {
|
||
|
|
new import_obsidian6.Notice(`Home Base: Waiting for git sync (${plugin.settings.gitSyncTimeout}s)...`, 5e3);
|
||
|
|
await delay(plugin.settings.gitSyncTimeout * 1e3);
|
||
|
|
(_h = (_g = periodicNotesPlugin.cache) == null ? void 0 : _g.initialize) == null ? void 0 : _h.call(_g);
|
||
|
|
note = (_j = (_i = periodicNotesPlugin.getPeriodicNote) == null ? void 0 : _i.call(periodicNotesPlugin, info.noun, date)) != null ? _j : null;
|
||
|
|
}
|
||
|
|
if (!note) {
|
||
|
|
note = (_l = await ((_k = periodicNotesPlugin.createPeriodicNote) == null ? void 0 : _k.call(periodicNotesPlugin, info.noun, date))) != null ? _l : null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return note ? trimFile(note) : null;
|
||
|
|
}
|
||
|
|
async function getJournalNote(journalName, plugin) {
|
||
|
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
||
|
|
const communityPlugins = ((_a = plugin.app.plugins) == null ? void 0 : _a.plugins) || {};
|
||
|
|
const journals = communityPlugins["journals"];
|
||
|
|
if (!journals) return null;
|
||
|
|
try {
|
||
|
|
const journal = (_b = journals.getJournal) == null ? void 0 : _b.call(journals, journalName);
|
||
|
|
if (!journal) return null;
|
||
|
|
const origAutoCreate = (_d = (_c = journal.config) == null ? void 0 : _c.value) == null ? void 0 : _d.autoCreate;
|
||
|
|
(_e = journals.reprocessNotes) == null ? void 0 : _e.call(journals);
|
||
|
|
if ((_f = journal.config) == null ? void 0 : _f.value) {
|
||
|
|
journal.config.value.autoCreate = true;
|
||
|
|
}
|
||
|
|
await ((_g = journal.autoCreate) == null ? void 0 : _g.call(journal));
|
||
|
|
if ((_h = journal.config) == null ? void 0 : _h.value) {
|
||
|
|
journal.config.value.autoCreate = origAutoCreate;
|
||
|
|
}
|
||
|
|
const today = (0, import_obsidian6.moment)().locale("custom-journal-locale").startOf("day");
|
||
|
|
let note = (_i = journal.get) == null ? void 0 : _i.call(journal, today);
|
||
|
|
if (!note && plugin.settings.waitForGitSync) {
|
||
|
|
new import_obsidian6.Notice(`Home Base: Waiting for git sync (${plugin.settings.gitSyncTimeout}s)...`, 5e3);
|
||
|
|
await delay(plugin.settings.gitSyncTimeout * 1e3);
|
||
|
|
(_j = journals.reprocessNotes) == null ? void 0 : _j.call(journals);
|
||
|
|
note = (_k = journal.get) == null ? void 0 : _k.call(journal, today);
|
||
|
|
}
|
||
|
|
if (!note) return null;
|
||
|
|
const path = (_l = journal.getNotePath) == null ? void 0 : _l.call(journal, note);
|
||
|
|
return path ? path.replace(/\.md$/, "") : null;
|
||
|
|
} catch (e) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function resolvePathSync(type, value, app) {
|
||
|
|
switch (type) {
|
||
|
|
case "File" /* File */:
|
||
|
|
return value || null;
|
||
|
|
case "Daily Note" /* DailyNote */:
|
||
|
|
case "Weekly Note" /* WeeklyNote */:
|
||
|
|
case "Monthly Note" /* MonthlyNote */:
|
||
|
|
case "Quarterly Note" /* QuarterlyNote */:
|
||
|
|
case "Yearly Note" /* YearlyNote */: {
|
||
|
|
const info = PERIODIC_INFO[type];
|
||
|
|
if (info) {
|
||
|
|
const date = (0, import_obsidian6.moment)().startOf(info.noun);
|
||
|
|
const all = info.getAll();
|
||
|
|
const note = info.get(date, all);
|
||
|
|
return note ? trimFile(note) : null;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
default:
|
||
|
|
return type === "Random in folder" /* RandomFolder */ || type === "New note" /* NewNote */ ? null : value || null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
async function computeHomeBasePath(type, value, plugin) {
|
||
|
|
switch (type) {
|
||
|
|
case "File" /* File */:
|
||
|
|
return value || null;
|
||
|
|
case "Random file" /* Random */: {
|
||
|
|
const file = randomFile(plugin.app);
|
||
|
|
return file ? trimFile(file) : null;
|
||
|
|
}
|
||
|
|
case "Random in folder" /* RandomFolder */: {
|
||
|
|
const file = randomFile(plugin.app, value);
|
||
|
|
return file ? trimFile(file) : null;
|
||
|
|
}
|
||
|
|
case "Daily Note" /* DailyNote */:
|
||
|
|
case "Weekly Note" /* WeeklyNote */:
|
||
|
|
case "Monthly Note" /* MonthlyNote */:
|
||
|
|
case "Quarterly Note" /* QuarterlyNote */:
|
||
|
|
case "Yearly Note" /* YearlyNote */:
|
||
|
|
return await getPeriodicNote(type, plugin);
|
||
|
|
case "Journal" /* Journal */:
|
||
|
|
return await getJournalNote(value, plugin);
|
||
|
|
case "New note" /* NewNote */: {
|
||
|
|
const fileManager = plugin.app.fileManager;
|
||
|
|
if (fileManager.createNewFile) {
|
||
|
|
const file = await fileManager.createNewFile(plugin.app.vault.getRoot(), value || "Untitled");
|
||
|
|
return file ? trimFile(file) : null;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
case "Workspace" /* Workspace */:
|
||
|
|
case "Graph view" /* Graph */:
|
||
|
|
case "Nothing" /* None */:
|
||
|
|
return null;
|
||
|
|
default:
|
||
|
|
return value || null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function delay(ms) {
|
||
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||
|
|
}
|
||
|
|
|
||
|
|
// src/services/home-service.ts
|
||
|
|
var LEAF_TYPES = ["markdown", "canvas", "bases", "kanban"];
|
||
|
|
var DETACH_DELAY = 100;
|
||
|
|
var GRAPH_INIT_DELAY = 200;
|
||
|
|
var GRAPH_COMMAND_FALLBACK_DELAY = 300;
|
||
|
|
function equalsCaseless(path1, path2) {
|
||
|
|
const normalize = (p) => p.toLowerCase().replace(/\.md$/, "");
|
||
|
|
return normalize(path1) === normalize(path2);
|
||
|
|
}
|
||
|
|
var HomeBaseService = class {
|
||
|
|
constructor(plugin) {
|
||
|
|
this.ghostLeaves = /* @__PURE__ */ new WeakSet();
|
||
|
|
this.plugin = plugin;
|
||
|
|
this.app = plugin.app;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Open home base with a specific mode (for startup/manual opens)
|
||
|
|
*/
|
||
|
|
async openHomeBaseWithMode(mode, runCommand = true) {
|
||
|
|
var _a;
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
if (homeBaseSettings.type === "Workspace" /* Workspace */) {
|
||
|
|
return this.openWorkspace(homeBaseSettings.value);
|
||
|
|
}
|
||
|
|
if (homeBaseSettings.type === "Graph view" /* Graph */) {
|
||
|
|
if (this.plugin.settings.showStickyHomeIcon) {
|
||
|
|
return this.openHomeBaseInGhostTab({ runCommand });
|
||
|
|
}
|
||
|
|
return this.openGraph();
|
||
|
|
}
|
||
|
|
if (homeBaseSettings.type === "Nothing" /* None */) {
|
||
|
|
if (runCommand) {
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const resolvedPath = await computeHomeBasePath(
|
||
|
|
homeBaseSettings.type,
|
||
|
|
homeBaseSettings.value,
|
||
|
|
this.plugin
|
||
|
|
);
|
||
|
|
if (!resolvedPath) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
let file = this.app.metadataCache.getFirstLinkpathDest(resolvedPath, "/");
|
||
|
|
if (!file) {
|
||
|
|
file = getFileByPath(this.app, resolvedPath);
|
||
|
|
}
|
||
|
|
if (!file) {
|
||
|
|
const untrimmedPath = resolvedPath.endsWith(".md") ? resolvedPath : `${resolvedPath}.md`;
|
||
|
|
file = getFileByPath(this.app, untrimmedPath);
|
||
|
|
if (!file && homeBaseSettings.type === "File" /* File */) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!file) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (mode === "replace-all") {
|
||
|
|
await this.detachAllLeaves();
|
||
|
|
} else if (mode === "replace-last") {
|
||
|
|
const activeLeaf = (_a = this.app.workspace.getActiveViewOfType(import_obsidian7.View)) == null ? void 0 : _a.leaf;
|
||
|
|
if (activeLeaf) {
|
||
|
|
const viewState = activeLeaf.getViewState();
|
||
|
|
if (viewState.pinned !== true) {
|
||
|
|
void activeLeaf.detach();
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, DETACH_DELAY));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const existingLeaf = this.findExistingHomeBaseLeaf(file);
|
||
|
|
if (existingLeaf && mode !== "replace-all") {
|
||
|
|
const viewState = existingLeaf.getViewState();
|
||
|
|
if (viewState.pinned === true && this.plugin.settings.showStickyHomeIcon) {
|
||
|
|
} else {
|
||
|
|
this.app.workspace.setActiveLeaf(existingLeaf);
|
||
|
|
await this.configureView(existingLeaf, file);
|
||
|
|
if (runCommand) {
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const newLeaf = mode === "retain" ? this.app.workspace.getLeaf("tab") : this.app.workspace.getLeaf(false);
|
||
|
|
if (!newLeaf) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
await newLeaf.openFile(file);
|
||
|
|
this.app.workspace.setActiveLeaf(newLeaf);
|
||
|
|
await this.configureView(newLeaf, file);
|
||
|
|
if (runCommand) {
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Open workspace
|
||
|
|
*/
|
||
|
|
async openWorkspace(workspaceName) {
|
||
|
|
var _a, _b, _c;
|
||
|
|
const workspacePlugin = (_b = (_a = this.app.internalPlugins) == null ? void 0 : _a.plugins) == null ? void 0 : _b.workspaces;
|
||
|
|
if (!(workspacePlugin == null ? void 0 : workspacePlugin.enabled) || !((_c = workspacePlugin.instance) == null ? void 0 : _c.loadWorkspace)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
workspacePlugin.instance.loadWorkspace(workspaceName);
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, DETACH_DELAY));
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Open graph view
|
||
|
|
*/
|
||
|
|
async openGraph() {
|
||
|
|
var _a, _b;
|
||
|
|
await ((_b = (_a = this.app.commands) == null ? void 0 : _a.executeCommandById) == null ? void 0 : _b.call(_a, "graph:open"));
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
// Removed deprecated openGraphInGhostTab - now integrated into openHomeBaseInGhostTab
|
||
|
|
/**
|
||
|
|
* Open the home base file
|
||
|
|
* @param options Options for opening
|
||
|
|
*/
|
||
|
|
async openHomeBase(options = {}) {
|
||
|
|
const { runCommand = true } = options;
|
||
|
|
const mode = this.plugin.settings.manualOpenMode;
|
||
|
|
return this.openHomeBaseWithMode(mode, runCommand);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Open home base in an empty leaf (for new tab replacement)
|
||
|
|
*/
|
||
|
|
async openInLeaf(leaf) {
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
return this.openInLeafWithSettings(leaf, homeBaseSettings);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Open a file in an empty leaf with custom settings
|
||
|
|
* @param leaf The leaf to open the file in
|
||
|
|
* @param settings Settings object with type and value
|
||
|
|
* @param isNewTab Whether this is for new tab replacement (skips pinning/ghost tab logic)
|
||
|
|
*/
|
||
|
|
async openInLeafWithSettings(leaf, settings, isNewTab = false) {
|
||
|
|
if (settings.type === "Workspace" /* Workspace */) {
|
||
|
|
await this.openWorkspace(settings.value);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (settings.type === "Graph view" /* Graph */) {
|
||
|
|
await this.openGraph();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (settings.type === "Nothing" /* None */) {
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const resolvedPath = await computeHomeBasePath(
|
||
|
|
settings.type,
|
||
|
|
settings.value,
|
||
|
|
this.plugin
|
||
|
|
);
|
||
|
|
if (!resolvedPath) {
|
||
|
|
console.warn("[Home Base] Could not resolve path for new tab:", settings.type, settings.value);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
let file = this.app.metadataCache.getFirstLinkpathDest(resolvedPath, "/");
|
||
|
|
if (!file) {
|
||
|
|
file = getFileByPath(this.app, resolvedPath);
|
||
|
|
}
|
||
|
|
if (!file && !resolvedPath.endsWith(".md") && !resolvedPath.endsWith(".canvas") && !resolvedPath.endsWith(".base")) {
|
||
|
|
const untrimmedPath = `${resolvedPath}.md`;
|
||
|
|
file = getFileByPath(this.app, untrimmedPath);
|
||
|
|
}
|
||
|
|
if (!file) {
|
||
|
|
console.warn("[Home Base] File not found for new tab:", resolvedPath);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (isNewTab) {
|
||
|
|
console.debug("[Home Base] openInLeafWithSettings: isNewTab=true, bypassing ghost tab logic", {
|
||
|
|
file: file.path,
|
||
|
|
settings
|
||
|
|
});
|
||
|
|
await leaf.openFile(file);
|
||
|
|
await this.configureView(leaf, file);
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const isTrulyEmpty = !leaf.view || leaf.view.getViewType() === "empty";
|
||
|
|
if (this.plugin.settings.showStickyHomeIcon && isTrulyEmpty) {
|
||
|
|
const isRandom = settings.type === "Random file" /* Random */ || settings.type === "Random in folder" /* RandomFolder */ || settings.type === "Daily Note" /* DailyNote */ || settings.type === "Weekly Note" /* WeeklyNote */ || settings.type === "Monthly Note" /* MonthlyNote */ || settings.type === "Yearly Note" /* YearlyNote */;
|
||
|
|
const ghostTab = this.findGhostTab(file, isRandom);
|
||
|
|
if (ghostTab) {
|
||
|
|
void leaf.detach();
|
||
|
|
this.app.workspace.setActiveLeaf(ghostTab);
|
||
|
|
await this.configureView(ghostTab, file);
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
this.ghostLeaves.add(leaf);
|
||
|
|
if (!isRandom) {
|
||
|
|
leaf.setPinned(true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
await leaf.openFile(file);
|
||
|
|
await this.configureView(leaf, file);
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Configure the view mode for a leaf
|
||
|
|
*/
|
||
|
|
async configureView(leaf, file) {
|
||
|
|
const settings = this.plugin.settings;
|
||
|
|
const view = leaf.view;
|
||
|
|
if (!isMarkdownLike(file) || !(view instanceof import_obsidian7.MarkdownView)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const state = view.getState();
|
||
|
|
if (settings.revertView) {
|
||
|
|
this.lastView = new WeakRef(view);
|
||
|
|
}
|
||
|
|
if (settings.autoScroll) {
|
||
|
|
const count = view.editor.lineCount();
|
||
|
|
if (state.mode === "preview") {
|
||
|
|
view.previewMode.applyScroll(count - 4);
|
||
|
|
} else {
|
||
|
|
view.editor.setCursor(count);
|
||
|
|
view.editor.focus();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (settings.openViewMode !== "default") {
|
||
|
|
switch (settings.openViewMode) {
|
||
|
|
case "preview":
|
||
|
|
state.mode = "preview";
|
||
|
|
break;
|
||
|
|
case "source":
|
||
|
|
state.mode = "source";
|
||
|
|
state.source = true;
|
||
|
|
break;
|
||
|
|
case "live":
|
||
|
|
state.mode = "source";
|
||
|
|
state.source = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
await leaf.setViewState({
|
||
|
|
type: "markdown",
|
||
|
|
state
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Revert view to default when navigating away from home base
|
||
|
|
*/
|
||
|
|
async revertView() {
|
||
|
|
const settings = this.plugin.settings;
|
||
|
|
if (!settings.revertView || !this.lastView || settings.openViewMode === "default") {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const view = this.lastView.deref();
|
||
|
|
if (!view) {
|
||
|
|
this.lastView = void 0;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const resolvedPath = await computeHomeBasePath(
|
||
|
|
homeBaseSettings.type,
|
||
|
|
homeBaseSettings.value,
|
||
|
|
this.plugin
|
||
|
|
);
|
||
|
|
if (!resolvedPath) {
|
||
|
|
this.lastView = void 0;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const currentFile = view.file;
|
||
|
|
if (currentFile && equalsCaseless(trimFile(currentFile), resolvedPath)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const state = view.getState();
|
||
|
|
const config = this.app.vault.config;
|
||
|
|
const mode = (config == null ? void 0 : config.defaultViewMode) || "source";
|
||
|
|
const source = (config == null ? void 0 : config.livePreview) !== void 0 ? !config.livePreview : false;
|
||
|
|
if (view.leaf.getViewState().type === "markdown" && (mode !== state.mode || source !== state.source)) {
|
||
|
|
state.mode = mode;
|
||
|
|
state.source = source;
|
||
|
|
await view.leaf.setViewState({ type: "markdown", state, active: true });
|
||
|
|
}
|
||
|
|
this.lastView = void 0;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Run the configured command after opening
|
||
|
|
*/
|
||
|
|
runCommandOnOpen() {
|
||
|
|
const commandId = this.plugin.settings.commandOnOpen;
|
||
|
|
if (commandId) {
|
||
|
|
setTimeout(() => {
|
||
|
|
executeCommand(this.app, commandId);
|
||
|
|
}, 100);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Find an existing leaf that has the home base file open
|
||
|
|
*/
|
||
|
|
findExistingHomeBaseLeaf(file) {
|
||
|
|
if (!file) return null;
|
||
|
|
const homeBasePath = file.path;
|
||
|
|
const leaves = LEAF_TYPES.flatMap(
|
||
|
|
(type) => this.app.workspace.getLeavesOfType(type)
|
||
|
|
);
|
||
|
|
for (const leaf of leaves) {
|
||
|
|
if (leafHasFile(leaf, homeBasePath)) {
|
||
|
|
return leaf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if a leaf is a ghost tab
|
||
|
|
* Ghost tab is identified by being in our internal ghostLeaves set
|
||
|
|
* Only tabs specifically created for the sticky icon are ghost tabs
|
||
|
|
*/
|
||
|
|
isGhostLeaf(leaf) {
|
||
|
|
return this.ghostLeaves.has(leaf);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Find the ghost tab (the one opened via sticky icon)
|
||
|
|
* Ghost tab is identified by being in our internal ghostLeaves set
|
||
|
|
* Only returns existing ghost tabs, doesn't create new ones
|
||
|
|
*/
|
||
|
|
findGhostTab(file, isRandom = false) {
|
||
|
|
if (!file) return null;
|
||
|
|
const homeBasePath = file.path;
|
||
|
|
const leaves = [];
|
||
|
|
this.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
var _a;
|
||
|
|
const viewType = (_a = leaf.view) == null ? void 0 : _a.getViewType();
|
||
|
|
if (viewType && LEAF_TYPES.includes(viewType)) {
|
||
|
|
leaves.push(leaf);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
for (const leaf of leaves) {
|
||
|
|
if (this.ghostLeaves.has(leaf) && leafHasFile(leaf, homeBasePath)) {
|
||
|
|
return leaf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Open home base in ghost tab (for sticky icon)
|
||
|
|
* Ghost tab is pinned and hidden (if setting enabled)
|
||
|
|
* Only one ghost tab should exist at a time
|
||
|
|
* Works for file-based types and Graph view
|
||
|
|
* Note: Random types don't pin (since file changes each time)
|
||
|
|
* Note: Workspace and None don't work (workspace changes layout, None doesn't open anything)
|
||
|
|
*/
|
||
|
|
async openHomeBaseInGhostTab(options = {}) {
|
||
|
|
const { runCommand = true } = options;
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
if (homeBaseSettings.type === "Workspace" /* Workspace */ || homeBaseSettings.type === "Nothing" /* None */) {
|
||
|
|
return this.openHomeBaseWithMode("retain", runCommand);
|
||
|
|
}
|
||
|
|
if (homeBaseSettings.type === "Graph view" /* Graph */) {
|
||
|
|
let ghostTab2 = this.findGraphGhostTab();
|
||
|
|
if (ghostTab2) {
|
||
|
|
this.ghostLeaves.add(ghostTab2);
|
||
|
|
ghostTab2.setPinned(true);
|
||
|
|
this.app.workspace.setActiveLeaf(ghostTab2, { focus: !this.isSettingsModalOpen() });
|
||
|
|
if (runCommand) this.runCommandOnOpen();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const newLeaf = this.app.workspace.getLeaf("tab");
|
||
|
|
if (newLeaf) {
|
||
|
|
await newLeaf.setViewState({ type: "graph", state: {} });
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, GRAPH_INIT_DELAY));
|
||
|
|
this.ghostLeaves.add(newLeaf);
|
||
|
|
newLeaf.setPinned(true);
|
||
|
|
this.app.workspace.setActiveLeaf(newLeaf, { focus: !this.isSettingsModalOpen() });
|
||
|
|
if (runCommand) this.runCommandOnOpen();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
await this.openGraph();
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, GRAPH_COMMAND_FALLBACK_DELAY));
|
||
|
|
ghostTab2 = this.findGraphGhostTab();
|
||
|
|
if (ghostTab2) {
|
||
|
|
this.ghostLeaves.add(ghostTab2);
|
||
|
|
ghostTab2.setPinned(true);
|
||
|
|
this.app.workspace.setActiveLeaf(ghostTab2, { focus: !this.isSettingsModalOpen() });
|
||
|
|
if (runCommand) this.runCommandOnOpen();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const isRandom = homeBaseSettings.type === "Random file" /* Random */ || homeBaseSettings.type === "Random in folder" /* RandomFolder */ || homeBaseSettings.type === "Daily Note" /* DailyNote */ || homeBaseSettings.type === "Weekly Note" /* WeeklyNote */ || homeBaseSettings.type === "Monthly Note" /* MonthlyNote */ || homeBaseSettings.type === "Yearly Note" /* YearlyNote */;
|
||
|
|
if (this.isSettingsModalOpen()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const resolvedPath = await computeHomeBasePath(
|
||
|
|
homeBaseSettings.type,
|
||
|
|
homeBaseSettings.value,
|
||
|
|
this.plugin
|
||
|
|
);
|
||
|
|
if (!resolvedPath) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
let file = this.app.metadataCache.getFirstLinkpathDest(resolvedPath, "/");
|
||
|
|
if (!file) {
|
||
|
|
file = getFileByPath(this.app, resolvedPath);
|
||
|
|
}
|
||
|
|
if (!file && !resolvedPath.endsWith(".md") && !resolvedPath.endsWith(".canvas") && !resolvedPath.endsWith(".base")) {
|
||
|
|
const untrimmedPath = `${resolvedPath}.md`;
|
||
|
|
file = getFileByPath(this.app, untrimmedPath);
|
||
|
|
}
|
||
|
|
if (!file) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const ghostTab = this.findGhostTab(file, isRandom);
|
||
|
|
console.debug("[Home Base] openHomeBaseInGhostTab:", {
|
||
|
|
file: file.path,
|
||
|
|
ghostTabFound: !!ghostTab,
|
||
|
|
isRandom,
|
||
|
|
zenMode: document.body.classList.contains("zenmode-active")
|
||
|
|
});
|
||
|
|
if (ghostTab) {
|
||
|
|
const shouldFocus2 = !this.isSettingsModalOpen();
|
||
|
|
this.app.workspace.setActiveLeaf(ghostTab, { focus: shouldFocus2 });
|
||
|
|
await this.configureView(ghostTab, file);
|
||
|
|
if (runCommand) {
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const newGhostTab = this.app.workspace.getLeaf("tab");
|
||
|
|
this.ghostLeaves.add(newGhostTab);
|
||
|
|
await newGhostTab.openFile(file);
|
||
|
|
if (!isRandom) {
|
||
|
|
newGhostTab.setPinned(true);
|
||
|
|
}
|
||
|
|
setTimeout(() => {
|
||
|
|
this.plugin.stickyTabService.updateTabHeaders();
|
||
|
|
}, 50);
|
||
|
|
const shouldFocus = !this.isSettingsModalOpen();
|
||
|
|
this.app.workspace.setActiveLeaf(newGhostTab, { focus: shouldFocus });
|
||
|
|
await this.configureView(newGhostTab, file);
|
||
|
|
if (runCommand) {
|
||
|
|
this.runCommandOnOpen();
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Find an empty leaf
|
||
|
|
*/
|
||
|
|
findEmptyLeaf() {
|
||
|
|
const leaves = this.app.workspace.getLeavesOfType("empty");
|
||
|
|
return leaves[0] || null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the home base file
|
||
|
|
*/
|
||
|
|
getHomeBaseFile() {
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const path = resolvePathSync(homeBaseSettings.type, homeBaseSettings.value, this.app);
|
||
|
|
if (!path) return null;
|
||
|
|
return getFileByPath(this.app, path);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Fast detach all leaves using changeLayout (like homepage plugin)
|
||
|
|
* This is much faster than iterating and detaching leaves individually
|
||
|
|
*/
|
||
|
|
async detachAllLeaves() {
|
||
|
|
var _a, _b;
|
||
|
|
const layout = this.app.workspace.getLayout();
|
||
|
|
layout.main = {
|
||
|
|
"id": "5324373015726ba8",
|
||
|
|
"type": "split",
|
||
|
|
"children": [{
|
||
|
|
"id": "4509724f8bf84da7",
|
||
|
|
"type": "tabs",
|
||
|
|
"children": [{
|
||
|
|
"id": "e7a7b303c61786dc",
|
||
|
|
"type": "leaf",
|
||
|
|
"state": { "type": "empty", "state": {}, "icon": "lucide-file", "title": "New tab" }
|
||
|
|
}]
|
||
|
|
}],
|
||
|
|
"direction": "vertical"
|
||
|
|
};
|
||
|
|
layout.active = "e7a7b303c61786dc";
|
||
|
|
await this.app.workspace.changeLayout(layout);
|
||
|
|
if (import_obsidian7.Platform.isMobile) {
|
||
|
|
(_b = (_a = this.app.workspace.rightSplit) == null ? void 0 : _a.updateInfo) == null ? void 0 : _b.call(_a);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Close all leaves in the main workspace except the specified one
|
||
|
|
* Simplified approach: iterate all leaves and close those in main workspace
|
||
|
|
*/
|
||
|
|
async closeAllLeavesExcept(exceptLeaf) {
|
||
|
|
const leavesToClose = [];
|
||
|
|
this.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
if (leaf === exceptLeaf) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const view = leaf.view;
|
||
|
|
let container = null;
|
||
|
|
if (view) {
|
||
|
|
const viewAny = view;
|
||
|
|
container = viewAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (!container) {
|
||
|
|
const leafAny = leaf;
|
||
|
|
container = leafAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (container) {
|
||
|
|
const rootWorkspace = container.closest(".workspace-split.mod-vertical.mod-root");
|
||
|
|
const leftSidebar = container.closest(".workspace-split.mod-left-split");
|
||
|
|
const rightSidebar = container.closest(".workspace-split.mod-right-split");
|
||
|
|
if (rootWorkspace && !leftSidebar && !rightSidebar) {
|
||
|
|
leavesToClose.push(leaf);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (exceptLeaf === null) {
|
||
|
|
try {
|
||
|
|
const viewState = leaf.getViewState();
|
||
|
|
if (viewState) {
|
||
|
|
leavesToClose.push(leaf);
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
for (const leaf of leavesToClose) {
|
||
|
|
void leaf.detach();
|
||
|
|
}
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Find an existing graph leaf that should be treated as a ghost tab
|
||
|
|
*/
|
||
|
|
findGraphGhostTab() {
|
||
|
|
let graphLeaves = [];
|
||
|
|
this.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
var _a;
|
||
|
|
if (((_a = leaf.view) == null ? void 0 : _a.getViewType()) === "graph") {
|
||
|
|
graphLeaves.push(leaf);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const pinned = graphLeaves.find((l) => l.getViewState().pinned === true);
|
||
|
|
if (pinned) return pinned;
|
||
|
|
if (graphLeaves.length === 1 && graphLeaves[0]) return graphLeaves[0];
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if the focused tab is the home base
|
||
|
|
*/
|
||
|
|
isFocusedOnHomeBase() {
|
||
|
|
var _a, _b;
|
||
|
|
const activeLeaf = (_a = this.app.workspace.getActiveViewOfType(import_obsidian7.View)) == null ? void 0 : _a.leaf;
|
||
|
|
if (!activeLeaf) return false;
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
if (homeBaseSettings.type === "Graph view" /* Graph */) {
|
||
|
|
return ((_b = activeLeaf.view) == null ? void 0 : _b.getViewType()) === "graph";
|
||
|
|
}
|
||
|
|
const homeBaseFile = this.getHomeBaseFile();
|
||
|
|
if (!homeBaseFile) return false;
|
||
|
|
return leafHasFile(activeLeaf, homeBaseFile.path);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if home base file exists
|
||
|
|
*/
|
||
|
|
homeBaseExists() {
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const path = resolvePathSync(homeBaseSettings.type, homeBaseSettings.value, this.app);
|
||
|
|
if (!path) return false;
|
||
|
|
return getFileByPath(this.app, path) !== null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the native Obsidian open behavior setting (from app.json)
|
||
|
|
* @returns The native setting value or undefined if not supported/found
|
||
|
|
*/
|
||
|
|
getNativeOpenBehavior() {
|
||
|
|
const config = this.app.vault.config;
|
||
|
|
if (!config) return void 0;
|
||
|
|
return config.openBehavior;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if the settings modal is currently open
|
||
|
|
*/
|
||
|
|
isSettingsModalOpen() {
|
||
|
|
const settingsModal = document.querySelector(".modal-container.mod-settings") || document.querySelector(".modal.mod-settings") || document.querySelector(".vertical-tab-content");
|
||
|
|
if (!settingsModal) {
|
||
|
|
const allModals = document.querySelectorAll(".modal-container");
|
||
|
|
for (const modal of Array.from(allModals)) {
|
||
|
|
if (modal.querySelector(".vertical-tab-content") || modal.querySelector(".settings-content") || modal.classList.contains("mod-settings")) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return settingsModal !== null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Set the active file as home base
|
||
|
|
*/
|
||
|
|
async setActiveFileAsHomeBase() {
|
||
|
|
const activeFile = this.app.workspace.getActiveFile();
|
||
|
|
if (!activeFile) return false;
|
||
|
|
if (!isSupportedExtension(activeFile.extension.toLowerCase())) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (this.plugin.settings.separateMobile && import_obsidian7.Platform.isMobile) {
|
||
|
|
this.plugin.settings.mobileHomeBaseType = "File" /* File */;
|
||
|
|
this.plugin.settings.mobileHomeBaseValue = activeFile.path;
|
||
|
|
} else {
|
||
|
|
this.plugin.settings.homeBaseType = "File" /* File */;
|
||
|
|
this.plugin.settings.homeBaseValue = activeFile.path;
|
||
|
|
}
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if active file can be set as home base
|
||
|
|
*/
|
||
|
|
canSetActiveFileAsHomeBase() {
|
||
|
|
const activeFile = this.app.workspace.getActiveFile();
|
||
|
|
if (!activeFile) return false;
|
||
|
|
return isSupportedExtension(activeFile.extension.toLowerCase());
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Restore ghost leaves from previous session
|
||
|
|
* This identifies pinned home base tabs that should be treated as ghost leaves
|
||
|
|
*/
|
||
|
|
restoreGhostLeaves() {
|
||
|
|
if (!this.plugin.settings.showStickyHomeIcon) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const homeBasePath = resolvePathSync(homeBaseSettings.type, homeBaseSettings.value, this.app);
|
||
|
|
if (!homeBasePath && homeBaseSettings.type !== "Graph view" /* Graph */) return;
|
||
|
|
const isRandom = homeBaseSettings.type === "Random file" /* Random */ || homeBaseSettings.type === "Random in folder" /* RandomFolder */ || homeBaseSettings.type === "Daily Note" /* DailyNote */ || homeBaseSettings.type === "Weekly Note" /* WeeklyNote */ || homeBaseSettings.type === "Monthly Note" /* MonthlyNote */ || homeBaseSettings.type === "Yearly Note" /* YearlyNote */;
|
||
|
|
if (isRandom) return;
|
||
|
|
if (homeBaseSettings.type === "Graph view" /* Graph */) {
|
||
|
|
const ghostTab = this.findGraphGhostTab();
|
||
|
|
if (ghostTab && ghostTab.getViewState().pinned === true) {
|
||
|
|
this.ghostLeaves.add(ghostTab);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const leaves = [];
|
||
|
|
this.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
var _a;
|
||
|
|
const viewType = (_a = leaf.view) == null ? void 0 : _a.getViewType();
|
||
|
|
if (viewType && LEAF_TYPES.includes(viewType)) {
|
||
|
|
leaves.push(leaf);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
for (const leaf of leaves) {
|
||
|
|
if (homeBasePath && leafHasFile(leaf, homeBasePath)) {
|
||
|
|
const viewState = leaf.getViewState();
|
||
|
|
if (viewState.pinned === true && !this.ghostLeaves.has(leaf)) {
|
||
|
|
this.ghostLeaves.add(leaf);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/services/new-tab-service.ts
|
||
|
|
var import_obsidian8 = require("obsidian");
|
||
|
|
var STARTUP_RESTORE_DELAY = 500;
|
||
|
|
var DETACH_SETTLE_DELAY = 200;
|
||
|
|
var PLUGIN_RACE_DELAY = 50;
|
||
|
|
var NewTabService = class {
|
||
|
|
constructor(plugin) {
|
||
|
|
this.existingLeaves = /* @__PURE__ */ new WeakSet();
|
||
|
|
this.isStartup = true;
|
||
|
|
this.startupCompleted = false;
|
||
|
|
this.plugin = plugin;
|
||
|
|
this.app = plugin.app;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Track all existing leaves (for new tab detection)
|
||
|
|
* This must be called even when startup is handled elsewhere
|
||
|
|
*/
|
||
|
|
trackExistingLeaves() {
|
||
|
|
this.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
this.existingLeaves.add(leaf);
|
||
|
|
});
|
||
|
|
this.startupCompleted = true;
|
||
|
|
this.isStartup = false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Initialize the service - called when layout is ready
|
||
|
|
*/
|
||
|
|
initialize() {
|
||
|
|
this.trackExistingLeaves();
|
||
|
|
void this.handleStartup();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Handle app startup - open home base if needed
|
||
|
|
* Only called on actual app startup, not plugin reloads
|
||
|
|
*/
|
||
|
|
async handleStartup() {
|
||
|
|
const settings = this.plugin.settings;
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
if (!settings.openOnStartup || !homeBaseSettings.value && homeBaseSettings.type === "File" /* File */) {
|
||
|
|
this.startupCompleted = true;
|
||
|
|
this.isStartup = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (await this.hasUrlParams()) {
|
||
|
|
this.startupCompleted = true;
|
||
|
|
this.isStartup = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, STARTUP_RESTORE_DELAY));
|
||
|
|
if (settings.openMode === "replace-all") {
|
||
|
|
let exceptLeaf = null;
|
||
|
|
if (!settings.hideReleaseNotes) {
|
||
|
|
const allLeaves = this.app.workspace.getLeavesOfType("markdown");
|
||
|
|
for (const leaf of allLeaves) {
|
||
|
|
const view = leaf.view;
|
||
|
|
const markdownView = view;
|
||
|
|
if (markdownView.file) {
|
||
|
|
const file = markdownView.file;
|
||
|
|
const configDir = this.app.vault.configDir;
|
||
|
|
if (file.path.includes("release") || file.path.includes(configDir)) {
|
||
|
|
const container = markdownView.containerEl;
|
||
|
|
if (container && container.querySelector(".release-notes")) {
|
||
|
|
exceptLeaf = leaf;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
await this.plugin.homeService.closeAllLeavesExcept(exceptLeaf);
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, DETACH_SETTLE_DELAY));
|
||
|
|
}
|
||
|
|
if (settings.showStickyHomeIcon) {
|
||
|
|
await this.plugin.homeService.openHomeBaseInGhostTab({
|
||
|
|
runCommand: true
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
await this.plugin.homeService.openHomeBase({
|
||
|
|
replaceActiveLeaf: false,
|
||
|
|
runCommand: true
|
||
|
|
});
|
||
|
|
}
|
||
|
|
this.startupCompleted = true;
|
||
|
|
this.isStartup = false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if the settings modal is currently open
|
||
|
|
*/
|
||
|
|
isSettingsModalOpen() {
|
||
|
|
const settingsModal = document.querySelector(".modal-container.mod-settings") || document.querySelector(".modal.mod-settings") || document.querySelector(".vertical-tab-content");
|
||
|
|
if (!settingsModal) {
|
||
|
|
const allModals = document.querySelectorAll(".modal-container");
|
||
|
|
for (const modal of Array.from(allModals)) {
|
||
|
|
if (modal.querySelector(".vertical-tab-content") || modal.querySelector(".settings-content") || modal.classList.contains("mod-settings")) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return settingsModal !== null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check for URL parameters that indicate Obsidian was opened via a link
|
||
|
|
* Based on obsidian-homepage implementation
|
||
|
|
*/
|
||
|
|
async hasUrlParams() {
|
||
|
|
var _a;
|
||
|
|
const windowAny = window;
|
||
|
|
const capacitor = windowAny.Capacitor;
|
||
|
|
if ((_a = capacitor == null ? void 0 : capacitor.Plugins) == null ? void 0 : _a.App) {
|
||
|
|
try {
|
||
|
|
const launchUrl = await capacitor.Plugins.App.getLaunchUrl();
|
||
|
|
if (launchUrl == null ? void 0 : launchUrl.url) {
|
||
|
|
const url = new URL(launchUrl.url);
|
||
|
|
const params = Array.from(url.searchParams.keys());
|
||
|
|
const action = url.hostname;
|
||
|
|
if (["open", "advanced-uri"].includes(action) && ["file", "filepath", "workspace"].some((e) => params.includes(e))) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const obsAct = windowAny.OBS_ACT;
|
||
|
|
if (obsAct) {
|
||
|
|
const params = Object.keys(obsAct);
|
||
|
|
const action = obsAct.action;
|
||
|
|
if (action && ["open", "advanced-uri"].includes(action) && ["file", "filepath", "workspace"].some((e) => params.includes(e))) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Handle layout change event - check for new empty tabs
|
||
|
|
* Based on new-tab-default-page implementation
|
||
|
|
*/
|
||
|
|
handleLayoutChange() {
|
||
|
|
if (this.isStartup || !this.startupCompleted) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
if (this.existingLeaves.has(leaf)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.existingLeaves.add(leaf);
|
||
|
|
if (!this.isEmptyTab(leaf)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const isOnlyTab = this.isOnlyTab(leaf);
|
||
|
|
if (isOnlyTab && this.plugin.settings.openWhenAllTabsClosed === true) {
|
||
|
|
void this.replaceEmptyTab(leaf, true);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this.plugin.settings.replaceNewTab !== true) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this.plugin.settings.replaceNewTab !== true) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this.plugin.settings.newTabMode === "only-when-empty") {
|
||
|
|
if (!isOnlyTab) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
void this.replaceEmptyTab(leaf, false);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if a leaf is an empty tab
|
||
|
|
* IMPORTANT: Only returns true if the leaf is truly empty (no file opened)
|
||
|
|
* If a file is already opened in the leaf, it's not empty and should NOT be replaced
|
||
|
|
*/
|
||
|
|
isEmptyTab(leaf) {
|
||
|
|
if (!leaf.view) return true;
|
||
|
|
if (leaf.view.getViewType() !== "empty") {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const viewState = leaf.getViewState();
|
||
|
|
if (viewState && viewState.file) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if this is the only tab in the main workspace
|
||
|
|
* Only counts root leaves (main workspace tabs), not sidebar tabs
|
||
|
|
* Based on obsidian-disable-tabs pattern using iterateRootLeaves
|
||
|
|
*/
|
||
|
|
isOnlyTab(leaf) {
|
||
|
|
let tabCount = 0;
|
||
|
|
this.app.workspace.iterateRootLeaves((l) => {
|
||
|
|
tabCount++;
|
||
|
|
});
|
||
|
|
const result = tabCount === 1;
|
||
|
|
console.debug("[Home Base] isOnlyTab:", {
|
||
|
|
leaf,
|
||
|
|
tabCount,
|
||
|
|
isOnlyTab: result
|
||
|
|
});
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Replace an empty tab with the home base or new tab file
|
||
|
|
* @param leaf The leaf to replace
|
||
|
|
* @param isAllTabsClosed Whether this is triggered by "all tabs closed" (true) or "new tab replacement" (false)
|
||
|
|
*/
|
||
|
|
async replaceEmptyTab(leaf, isAllTabsClosed = false) {
|
||
|
|
if (!isAllTabsClosed) {
|
||
|
|
if (this.plugin.settings.replaceNewTab !== true) {
|
||
|
|
console.debug("[Home Base] replaceEmptyTab: replaceNewTab is not true, aborting");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
await new Promise((resolve) => setTimeout(resolve, PLUGIN_RACE_DELAY));
|
||
|
|
if (!isAllTabsClosed) {
|
||
|
|
if (this.plugin.settings.replaceNewTab !== true) {
|
||
|
|
console.debug("[Home Base] replaceEmptyTab: replaceNewTab changed during delay, aborting");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (this.plugin.settings.openWhenAllTabsClosed !== true) {
|
||
|
|
console.debug("[Home Base] replaceEmptyTab: openWhenAllTabsClosed changed during delay, aborting");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!this.isEmptyTab(leaf)) {
|
||
|
|
console.debug("[Home Base] replaceEmptyTab: Tab is no longer empty, skipping");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const settings = isAllTabsClosed ? this.plugin.getHomeBaseSettings() : this.plugin.getNewTabSettings();
|
||
|
|
console.debug("[Home Base] replaceEmptyTab:", {
|
||
|
|
leaf,
|
||
|
|
isAllTabsClosed,
|
||
|
|
settings,
|
||
|
|
replaceNewTab: this.plugin.settings.replaceNewTab,
|
||
|
|
newTabMode: this.plugin.settings.newTabMode,
|
||
|
|
useDifferentFileForNewTab: this.plugin.settings.useDifferentFileForNewTab
|
||
|
|
});
|
||
|
|
const success = await this.plugin.homeService.openInLeafWithSettings(leaf, settings, true);
|
||
|
|
if (!success) {
|
||
|
|
console.warn("[Home Base] Failed to open file:", settings);
|
||
|
|
} else {
|
||
|
|
console.debug("[Home Base] replaceEmptyTab: Successfully opened file");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Force check for empty workspace and open home base
|
||
|
|
*/
|
||
|
|
async openIfEmpty() {
|
||
|
|
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian8.View);
|
||
|
|
const activeLeaf = activeView == null ? void 0 : activeView.leaf;
|
||
|
|
if (!activeLeaf) return;
|
||
|
|
if (this.isEmptyTab(activeLeaf) && this.isOnlyTab(activeLeaf)) {
|
||
|
|
await this.plugin.homeService.openInLeaf(activeLeaf);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/services/sticky-tab-service.ts
|
||
|
|
var import_obsidian9 = require("obsidian");
|
||
|
|
var TAB_HEADER_OPEN_DELAY = 150;
|
||
|
|
var ICON_PLACEMENT_CHECK_INTERVAL = 100;
|
||
|
|
var WINDOW_OPEN_CONTAINER_DELAY = 100;
|
||
|
|
var STICKY_ICON_CLASS = "home-base-sticky-icon";
|
||
|
|
var STICKY_ICON_ACTIVE_CLASS = "home-base-sticky-icon-active";
|
||
|
|
var StickyTabService = class {
|
||
|
|
constructor(plugin) {
|
||
|
|
this.stickyIconEl = null;
|
||
|
|
this.layoutChangeHandler = null;
|
||
|
|
this.tabHeaderUpdateTimeout = null;
|
||
|
|
this.sidebarObserver = null;
|
||
|
|
this.tabHeaderObserver = null;
|
||
|
|
this.plugin = plugin;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update the sticky tab icon based on settings
|
||
|
|
*/
|
||
|
|
update() {
|
||
|
|
if (import_obsidian9.Platform.isMobile) {
|
||
|
|
this.remove();
|
||
|
|
this.updateTabHeaders();
|
||
|
|
this.updateWorkspaceClass(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this.plugin.settings.showStickyHomeIcon) {
|
||
|
|
if (this.stickyIconEl) {
|
||
|
|
const iconName = this.plugin.settings.stickyIconName || "home";
|
||
|
|
(0, import_obsidian9.setIcon)(this.stickyIconEl, iconName);
|
||
|
|
} else {
|
||
|
|
this.create();
|
||
|
|
}
|
||
|
|
this.updateWorkspaceClass(true);
|
||
|
|
} else {
|
||
|
|
this.remove();
|
||
|
|
this.updateTabHeaders();
|
||
|
|
this.updateWorkspaceClass(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Add/remove CSS class on all workspaces to conditionally apply styles
|
||
|
|
*/
|
||
|
|
updateWorkspaceClass(enabled) {
|
||
|
|
const applyToDocument = (doc) => {
|
||
|
|
const mainWorkspace = doc.querySelector(".workspace-split.mod-vertical.mod-root");
|
||
|
|
if (!mainWorkspace) return;
|
||
|
|
if (enabled) {
|
||
|
|
mainWorkspace.classList.add("home-base-sticky-icon-enabled");
|
||
|
|
} else {
|
||
|
|
mainWorkspace.classList.remove("home-base-sticky-icon-enabled");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
applyToDocument(document);
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
var _a, _b;
|
||
|
|
const doc = (_b = (_a = leaf.view) == null ? void 0 : _a.containerEl) == null ? void 0 : _b.ownerDocument;
|
||
|
|
if (doc && doc !== document) {
|
||
|
|
applyToDocument(doc);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Create the sticky home icon
|
||
|
|
*/
|
||
|
|
create() {
|
||
|
|
this.remove();
|
||
|
|
this.stickyIconEl = document.createElement("div");
|
||
|
|
this.stickyIconEl.className = `${STICKY_ICON_CLASS} clickable-icon`;
|
||
|
|
this.stickyIconEl.setAttribute("aria-label", "Open home base");
|
||
|
|
this.stickyIconEl.setAttribute("data-tooltip-position", "bottom");
|
||
|
|
const iconName = this.plugin.settings.stickyIconName || "home";
|
||
|
|
(0, import_obsidian9.setIcon)(this.stickyIconEl, iconName);
|
||
|
|
this.stickyIconEl.addEventListener("click", (e) => {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
void this.plugin.homeService.openHomeBaseInGhostTab({
|
||
|
|
runCommand: true
|
||
|
|
}).then(() => {
|
||
|
|
setTimeout(() => {
|
||
|
|
this.updateTabHeaders();
|
||
|
|
}, TAB_HEADER_OPEN_DELAY);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
this.stickyIconEl.addEventListener("contextmenu", (e) => {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
const menu = new import_obsidian9.Menu();
|
||
|
|
menu.addItem((item) => {
|
||
|
|
item.setTitle("Close home base").setIcon("x").onClick(() => {
|
||
|
|
void this.closeHomeBase(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
menu.addItem((item) => {
|
||
|
|
item.setTitle("Change icon").setIcon("lucide-image-plus").onClick(() => {
|
||
|
|
const picker = new IconPicker(
|
||
|
|
this.plugin.app,
|
||
|
|
this.plugin.settings.stickyIconName,
|
||
|
|
(icon) => {
|
||
|
|
void (async () => {
|
||
|
|
this.plugin.settings.stickyIconName = icon;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
if (this.stickyIconEl) {
|
||
|
|
(0, import_obsidian9.setIcon)(this.stickyIconEl, icon || "home");
|
||
|
|
}
|
||
|
|
})();
|
||
|
|
}
|
||
|
|
);
|
||
|
|
picker.open();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
menu.showAtMouseEvent(e);
|
||
|
|
});
|
||
|
|
const ensureIconInPlace = () => {
|
||
|
|
if (!this.stickyIconEl) return;
|
||
|
|
const mainWorkspace2 = document.querySelector(".workspace-split.mod-vertical.mod-root");
|
||
|
|
if (!mainWorkspace2) return;
|
||
|
|
const tabHeaderContainerInner = mainWorkspace2.querySelector(".workspace-tab-header-container-inner");
|
||
|
|
if (!tabHeaderContainerInner) return;
|
||
|
|
const allIcons = tabHeaderContainerInner.querySelectorAll(`.${STICKY_ICON_CLASS}`);
|
||
|
|
allIcons.forEach((icon) => {
|
||
|
|
if (icon !== this.stickyIconEl) {
|
||
|
|
icon.remove();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (tabHeaderContainerInner.contains(this.stickyIconEl)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
tabHeaderContainerInner.insertBefore(this.stickyIconEl, tabHeaderContainerInner.firstChild);
|
||
|
|
this.updateActiveState();
|
||
|
|
this.updateTabHeaders();
|
||
|
|
this.updateWorkspaceClass(true);
|
||
|
|
this.updateIconPositionForSidebar();
|
||
|
|
this.watchSidebarState();
|
||
|
|
};
|
||
|
|
ensureIconInPlace();
|
||
|
|
const checkInterval = setInterval(() => {
|
||
|
|
if (!this.stickyIconEl || !this.plugin.settings.showStickyHomeIcon) {
|
||
|
|
clearInterval(checkInterval);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
ensureIconInPlace();
|
||
|
|
}, ICON_PLACEMENT_CHECK_INTERVAL);
|
||
|
|
this.stickyIconEl._checkInterval = checkInterval;
|
||
|
|
if (!this.layoutChangeHandler) {
|
||
|
|
this.layoutChangeHandler = () => {
|
||
|
|
if (this.stickyIconEl && this.plugin.settings.showStickyHomeIcon) {
|
||
|
|
ensureIconInPlace();
|
||
|
|
this.updateTabHeaders();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
this.plugin.registerEvent(
|
||
|
|
this.plugin.app.workspace.on("layout-change", this.layoutChangeHandler)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
const mainWorkspace = document.querySelector(".workspace-split.mod-vertical.mod-root");
|
||
|
|
if (mainWorkspace) {
|
||
|
|
const containerObserver = new MutationObserver(() => {
|
||
|
|
if (!this.stickyIconEl || !this.plugin.settings.showStickyHomeIcon) return;
|
||
|
|
const tabHeaderContainerInner = mainWorkspace.querySelector(".workspace-tab-header-container-inner");
|
||
|
|
if (tabHeaderContainerInner) {
|
||
|
|
const allIcons = tabHeaderContainerInner.querySelectorAll(`.${STICKY_ICON_CLASS}`);
|
||
|
|
allIcons.forEach((icon) => {
|
||
|
|
if (icon !== this.stickyIconEl) {
|
||
|
|
icon.remove();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (!tabHeaderContainerInner.contains(this.stickyIconEl)) {
|
||
|
|
tabHeaderContainerInner.insertBefore(this.stickyIconEl, tabHeaderContainerInner.firstChild);
|
||
|
|
this.updateActiveState();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
containerObserver.observe(mainWorkspace, {
|
||
|
|
childList: true,
|
||
|
|
subtree: true
|
||
|
|
// Watch subtree to catch tab container recreation
|
||
|
|
});
|
||
|
|
this.stickyIconEl._containerObserver = containerObserver;
|
||
|
|
}
|
||
|
|
this.setupTabHeaderObserver();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Set up MutationObserver to watch for tab header changes and remove ghost tabs immediately
|
||
|
|
* This prevents the flash when tabs are opened/closed
|
||
|
|
*/
|
||
|
|
setupTabHeaderObserver() {
|
||
|
|
if (this.tabHeaderObserver) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.tabHeaderObserver = new MutationObserver((mutations) => {
|
||
|
|
if (!this.plugin.settings.showStickyHomeIcon || !this.plugin.settings.hideHomeTabHeader) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let hasNewHeaders = false;
|
||
|
|
for (const mutation of mutations) {
|
||
|
|
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
|
||
|
|
for (const node of Array.from(mutation.addedNodes)) {
|
||
|
|
if (node instanceof HTMLElement && node.classList.contains("workspace-tab-header")) {
|
||
|
|
hasNewHeaders = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (hasNewHeaders) break;
|
||
|
|
}
|
||
|
|
if (hasNewHeaders) {
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
const view = leaf.view;
|
||
|
|
let container = null;
|
||
|
|
if (view) {
|
||
|
|
const viewAny = view;
|
||
|
|
container = viewAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (!container) {
|
||
|
|
const leafAny = leaf;
|
||
|
|
container = leafAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (container) {
|
||
|
|
const rootWorkspace = container.closest(".workspace-split.mod-vertical.mod-root");
|
||
|
|
const leftSidebar = container.closest(".workspace-split.mod-left-split");
|
||
|
|
const rightSidebar = container.closest(".workspace-split.mod-right-split");
|
||
|
|
if (rootWorkspace && !leftSidebar && !rightSidebar) {
|
||
|
|
if (this.plugin.homeService.isGhostLeaf(leaf)) {
|
||
|
|
const tabHeader = this.getTabHeaderForLeaf(leaf);
|
||
|
|
if (tabHeader && tabHeader.parentElement) {
|
||
|
|
const parent = tabHeader.parentElement;
|
||
|
|
if (parent && parent.classList.contains("workspace-tab-header-container-inner")) {
|
||
|
|
const tabHeaderExtended = tabHeader;
|
||
|
|
if (parent.contains(tabHeader)) {
|
||
|
|
tabHeaderExtended._homeBaseParent = parent;
|
||
|
|
tabHeaderExtended._homeBaseNextSibling = tabHeader.nextSibling;
|
||
|
|
tabHeader.remove();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const observeContainer = (container) => {
|
||
|
|
var _a;
|
||
|
|
(_a = this.tabHeaderObserver) == null ? void 0 : _a.observe(container, {
|
||
|
|
childList: true,
|
||
|
|
subtree: false
|
||
|
|
});
|
||
|
|
};
|
||
|
|
const observeAllWindows = () => {
|
||
|
|
const containers = document.querySelectorAll(".workspace-tab-header-container-inner");
|
||
|
|
containers.forEach(observeContainer);
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
var _a, _b;
|
||
|
|
const doc = (_b = (_a = leaf.view) == null ? void 0 : _a.containerEl) == null ? void 0 : _b.ownerDocument;
|
||
|
|
if (doc && doc !== document) {
|
||
|
|
const windowContainers = doc.querySelectorAll(".workspace-tab-header-container-inner");
|
||
|
|
windowContainers.forEach(observeContainer);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
observeAllWindows();
|
||
|
|
const setupWorkspaceObserver = (win) => {
|
||
|
|
const doc = win.document;
|
||
|
|
const workspaceObserver = new MutationObserver(() => {
|
||
|
|
const newContainers = doc.querySelectorAll(".workspace-tab-header-container-inner");
|
||
|
|
newContainers.forEach(observeContainer);
|
||
|
|
});
|
||
|
|
const mainWorkspace = doc.querySelector(".workspace-split.mod-vertical.mod-root");
|
||
|
|
if (mainWorkspace) {
|
||
|
|
workspaceObserver.observe(mainWorkspace, {
|
||
|
|
childList: true,
|
||
|
|
subtree: true
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
setupWorkspaceObserver(window);
|
||
|
|
this.plugin.registerEvent(
|
||
|
|
this.plugin.app.workspace.on("window-open", (win) => {
|
||
|
|
const actualWindow = win.win;
|
||
|
|
if (actualWindow instanceof Window) {
|
||
|
|
setupWorkspaceObserver(actualWindow);
|
||
|
|
setTimeout(observeAllWindows, WINDOW_OPEN_CONTAINER_DELAY);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Remove the sticky home icon
|
||
|
|
*/
|
||
|
|
remove() {
|
||
|
|
if (this.tabHeaderUpdateTimeout) {
|
||
|
|
clearTimeout(this.tabHeaderUpdateTimeout);
|
||
|
|
this.tabHeaderUpdateTimeout = null;
|
||
|
|
}
|
||
|
|
if (this.stickyIconEl && this.stickyIconEl._checkInterval) {
|
||
|
|
clearInterval(this.stickyIconEl._checkInterval);
|
||
|
|
}
|
||
|
|
if (this.stickyIconEl && this.stickyIconEl._containerObserver) {
|
||
|
|
this.stickyIconEl._containerObserver.disconnect();
|
||
|
|
}
|
||
|
|
if (this.sidebarObserver) {
|
||
|
|
this.sidebarObserver.disconnect();
|
||
|
|
this.sidebarObserver = null;
|
||
|
|
}
|
||
|
|
if (this.tabHeaderObserver) {
|
||
|
|
this.tabHeaderObserver.disconnect();
|
||
|
|
this.tabHeaderObserver = null;
|
||
|
|
}
|
||
|
|
this.updateWorkspaceClass(false);
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
const view = leaf.view;
|
||
|
|
let container = null;
|
||
|
|
if (view) {
|
||
|
|
const viewAny = view;
|
||
|
|
container = viewAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (!container) {
|
||
|
|
const leafAny = leaf;
|
||
|
|
container = leafAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (container) {
|
||
|
|
const rootWorkspace = container.closest(".workspace-split.mod-vertical.mod-root");
|
||
|
|
const leftSidebar = container.closest(".workspace-split.mod-left-split");
|
||
|
|
const rightSidebar = container.closest(".workspace-split.mod-right-split");
|
||
|
|
if (rootWorkspace && !leftSidebar && !rightSidebar) {
|
||
|
|
const tabHeader = this.getTabHeaderForLeaf(leaf);
|
||
|
|
if (tabHeader) {
|
||
|
|
tabHeader.classList.remove("is-home-base-tab");
|
||
|
|
tabHeader.removeAttribute("data-home-base-ghost");
|
||
|
|
tabHeader.removeAttribute("aria-hidden");
|
||
|
|
const tabHeaderExtended = tabHeader;
|
||
|
|
if (tabHeaderExtended._homeBaseParent && !tabHeaderExtended._homeBaseParent.contains(tabHeader)) {
|
||
|
|
const parent = tabHeaderExtended._homeBaseParent;
|
||
|
|
const nextSibling = tabHeaderExtended._homeBaseNextSibling;
|
||
|
|
if (parent) {
|
||
|
|
if (nextSibling && nextSibling.parentElement === parent) {
|
||
|
|
parent.insertBefore(tabHeader, nextSibling);
|
||
|
|
} else {
|
||
|
|
parent.appendChild(tabHeader);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
delete tabHeaderExtended._homeBaseParent;
|
||
|
|
delete tabHeaderExtended._homeBaseNextSibling;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (this.stickyIconEl) {
|
||
|
|
if (this.stickyIconEl.parentElement) {
|
||
|
|
this.stickyIconEl.remove();
|
||
|
|
}
|
||
|
|
this.stickyIconEl = null;
|
||
|
|
}
|
||
|
|
const cleanupOrphans = (doc) => {
|
||
|
|
doc.querySelectorAll(`.${STICKY_ICON_CLASS}`).forEach((el) => {
|
||
|
|
const stickyEl = el;
|
||
|
|
if (stickyEl._checkInterval) {
|
||
|
|
clearInterval(stickyEl._checkInterval);
|
||
|
|
}
|
||
|
|
if (stickyEl._containerObserver) {
|
||
|
|
stickyEl._containerObserver.disconnect();
|
||
|
|
}
|
||
|
|
el.remove();
|
||
|
|
});
|
||
|
|
};
|
||
|
|
cleanupOrphans(document);
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
var _a, _b;
|
||
|
|
const doc = (_b = (_a = leaf.view) == null ? void 0 : _a.containerEl) == null ? void 0 : _b.ownerDocument;
|
||
|
|
if (doc && doc !== document) {
|
||
|
|
cleanupOrphans(doc);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update the active state of the sticky icon
|
||
|
|
*/
|
||
|
|
updateActiveState() {
|
||
|
|
if (!this.stickyIconEl) return;
|
||
|
|
const isActive = this.plugin.homeService.isFocusedOnHomeBase();
|
||
|
|
if (isActive) {
|
||
|
|
this.stickyIconEl.classList.add(STICKY_ICON_ACTIVE_CLASS);
|
||
|
|
} else {
|
||
|
|
this.stickyIconEl.classList.remove(STICKY_ICON_ACTIVE_CLASS);
|
||
|
|
}
|
||
|
|
this.updateTabHeaders();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if the left sidebar is collapsed
|
||
|
|
* Based on obsidian-oxygen-settings implementation
|
||
|
|
*/
|
||
|
|
isLeftSidebarCollapsed() {
|
||
|
|
const leftSidebar = document.querySelector(".workspace-split.mod-left-split") || document.querySelector(".mod-left-split");
|
||
|
|
if (!leftSidebar) return false;
|
||
|
|
return leftSidebar.classList.contains("is-sidedock-collapsed");
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update icon position based on sidebar state
|
||
|
|
* Note: With inline positioning, icon flows naturally with tabs, so no special positioning needed
|
||
|
|
*/
|
||
|
|
updateIconPositionForSidebar() {
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update icon visibility based on tab bar visibility
|
||
|
|
* REMOVED: JavaScript-based visibility checking was causing issues
|
||
|
|
* Now relies entirely on CSS which is more reliable
|
||
|
|
*/
|
||
|
|
updateIconVisibility() {
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Watch for sidebar state changes and update icon position
|
||
|
|
* Based on obsidian-oxygen-settings implementation
|
||
|
|
*/
|
||
|
|
watchSidebarState() {
|
||
|
|
if (this.sidebarObserver) {
|
||
|
|
this.sidebarObserver.disconnect();
|
||
|
|
}
|
||
|
|
const leftSidebar = document.querySelector(".workspace-split.mod-left-split") || document.querySelector(".mod-left-split");
|
||
|
|
if (!leftSidebar) return;
|
||
|
|
this.sidebarObserver = new MutationObserver((mutations) => {
|
||
|
|
let shouldUpdate = false;
|
||
|
|
mutations.forEach((mutation) => {
|
||
|
|
if (mutation.type === "attributes" && mutation.attributeName === "class") {
|
||
|
|
shouldUpdate = true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (shouldUpdate) {
|
||
|
|
this.updateIconPositionForSidebar();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.sidebarObserver.observe(leftSidebar, {
|
||
|
|
attributes: true,
|
||
|
|
attributeFilter: ["class"]
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Watch for tab bar visibility changes (Oxygen theme auto-hide, focus mode, etc.)
|
||
|
|
* REMOVED: No longer needed - CSS handles all visibility automatically
|
||
|
|
*/
|
||
|
|
watchTabBarVisibility() {
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Toggle the sticky icon visibility
|
||
|
|
*/
|
||
|
|
async toggle() {
|
||
|
|
this.plugin.settings.showStickyHomeIcon = !this.plugin.settings.showStickyHomeIcon;
|
||
|
|
await this.plugin.saveSettings();
|
||
|
|
this.update();
|
||
|
|
this.updateTabHeaders();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update tab headers to hide/show ghost tab
|
||
|
|
* Only works when sticky icon is enabled
|
||
|
|
* Removed debounce - must be immediate to prevent flash
|
||
|
|
*/
|
||
|
|
updateTabHeaders() {
|
||
|
|
if (this.tabHeaderUpdateTimeout) {
|
||
|
|
clearTimeout(this.tabHeaderUpdateTimeout);
|
||
|
|
this.tabHeaderUpdateTimeout = null;
|
||
|
|
}
|
||
|
|
this._doUpdateTabHeaders();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Internal method that actually updates the tab headers
|
||
|
|
*/
|
||
|
|
_doUpdateTabHeaders() {
|
||
|
|
if (!this.plugin.settings.showStickyHomeIcon || !this.plugin.settings.hideHomeTabHeader) {
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
const tabHeader = this.getTabHeaderForLeaf(leaf);
|
||
|
|
if (tabHeader) {
|
||
|
|
tabHeader.classList.remove("is-home-base-tab");
|
||
|
|
tabHeader.removeAttribute("data-home-base-ghost");
|
||
|
|
tabHeader.removeAttribute("aria-hidden");
|
||
|
|
const tabHeaderExtended = tabHeader;
|
||
|
|
if (tabHeaderExtended._homeBaseParent && !tabHeaderExtended._homeBaseParent.contains(tabHeader)) {
|
||
|
|
const parent = tabHeaderExtended._homeBaseParent;
|
||
|
|
const nextSibling = tabHeaderExtended._homeBaseNextSibling;
|
||
|
|
if (parent) {
|
||
|
|
if (nextSibling && nextSibling.parentElement === parent) {
|
||
|
|
parent.insertBefore(tabHeader, nextSibling);
|
||
|
|
} else {
|
||
|
|
parent.appendChild(tabHeader);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
delete tabHeaderExtended._homeBaseParent;
|
||
|
|
delete tabHeaderExtended._homeBaseNextSibling;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const ghostTabHeadersToRemove = [];
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
const view = leaf.view;
|
||
|
|
let container = null;
|
||
|
|
if (view) {
|
||
|
|
const viewAny = view;
|
||
|
|
container = viewAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (!container) {
|
||
|
|
const leafAny = leaf;
|
||
|
|
container = leafAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (container) {
|
||
|
|
const rootWorkspace = container.closest(".workspace-split.mod-vertical.mod-root");
|
||
|
|
const leftSidebar = container.closest(".workspace-split.mod-left-split");
|
||
|
|
const rightSidebar = container.closest(".workspace-split.mod-right-split");
|
||
|
|
if (rootWorkspace && !leftSidebar && !rightSidebar) {
|
||
|
|
if (this.plugin.homeService.isGhostLeaf(leaf)) {
|
||
|
|
const tabHeader = this.getTabHeaderForLeaf(leaf);
|
||
|
|
if (tabHeader && tabHeader.parentElement) {
|
||
|
|
ghostTabHeadersToRemove.push({ tabHeader, leaf });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
ghostTabHeadersToRemove.forEach(({ tabHeader }) => {
|
||
|
|
const parent = tabHeader.parentElement;
|
||
|
|
if (parent && parent.classList.contains("workspace-tab-header-container-inner")) {
|
||
|
|
const tabHeaderExtended = tabHeader;
|
||
|
|
if (parent.contains(tabHeader)) {
|
||
|
|
tabHeaderExtended._homeBaseParent = parent;
|
||
|
|
tabHeaderExtended._homeBaseNextSibling = tabHeader.nextSibling;
|
||
|
|
tabHeader.remove();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const homeBasePath = resolvePathSync(homeBaseSettings.type, homeBaseSettings.value, this.plugin.app);
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
var _a;
|
||
|
|
const view = leaf.view;
|
||
|
|
let container = null;
|
||
|
|
if (view) {
|
||
|
|
const viewAny = view;
|
||
|
|
container = viewAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (!container) {
|
||
|
|
const leafAny = leaf;
|
||
|
|
container = leafAny.containerEl || null;
|
||
|
|
}
|
||
|
|
if (container) {
|
||
|
|
const rootWorkspace = container.closest(".workspace-split.mod-vertical.mod-root");
|
||
|
|
const leftSidebar = container.closest(".workspace-split.mod-left-split");
|
||
|
|
const rightSidebar = container.closest(".workspace-split.mod-right-split");
|
||
|
|
if (rootWorkspace && !leftSidebar && !rightSidebar) {
|
||
|
|
const isGhostTab = this.plugin.homeService.isGhostLeaf(leaf);
|
||
|
|
const tabHeader = this.getTabHeaderForLeaf(leaf);
|
||
|
|
if (!tabHeader) return;
|
||
|
|
const tabHeaderExtended = tabHeader;
|
||
|
|
const isRemoved = tabHeaderExtended._homeBaseParent && !tabHeaderExtended._homeBaseParent.contains(tabHeader);
|
||
|
|
if (!isGhostTab) {
|
||
|
|
if (isRemoved) {
|
||
|
|
const parent = tabHeaderExtended._homeBaseParent;
|
||
|
|
const nextSibling = tabHeaderExtended._homeBaseNextSibling;
|
||
|
|
if (parent) {
|
||
|
|
if (nextSibling && nextSibling.parentElement === parent) {
|
||
|
|
parent.insertBefore(tabHeader, nextSibling);
|
||
|
|
} else {
|
||
|
|
parent.appendChild(tabHeader);
|
||
|
|
}
|
||
|
|
delete tabHeaderExtended._homeBaseParent;
|
||
|
|
delete tabHeaderExtended._homeBaseNextSibling;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const isGraphHome = homeBaseSettings.type === "Graph view" /* Graph */ && ((_a = leaf.view) == null ? void 0 : _a.getViewType()) === "graph";
|
||
|
|
if (homeBasePath && leafHasFile(leaf, homeBasePath) || isGraphHome) {
|
||
|
|
tabHeader.classList.add("is-home-base-tab");
|
||
|
|
} else {
|
||
|
|
tabHeader.classList.remove("is-home-base-tab");
|
||
|
|
}
|
||
|
|
tabHeader.removeAttribute("data-home-base-ghost");
|
||
|
|
tabHeader.removeAttribute("aria-hidden");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the tab header element for a given leaf
|
||
|
|
*/
|
||
|
|
getTabHeaderForLeaf(leaf) {
|
||
|
|
var _a, _b, _c;
|
||
|
|
const leafAny = leaf;
|
||
|
|
if (leafAny.tabHeaderEl) {
|
||
|
|
return leafAny.tabHeaderEl;
|
||
|
|
}
|
||
|
|
const viewType = (_a = leaf.view) == null ? void 0 : _a.getViewType();
|
||
|
|
if (!viewType) return null;
|
||
|
|
const doc = ((_c = (_b = leaf.view) == null ? void 0 : _b.containerEl) == null ? void 0 : _c.ownerDocument) || document;
|
||
|
|
const activeLeaf = this.plugin.app.workspace.getMostRecentLeaf();
|
||
|
|
const isActive = leaf === activeLeaf;
|
||
|
|
const tabHeaders = doc.querySelectorAll(`.workspace-tab-header[data-type="${viewType}"]`);
|
||
|
|
if (isActive) {
|
||
|
|
const activeHeader = doc.querySelector(".workspace-tab-header.is-active");
|
||
|
|
if (activeHeader && activeHeader.getAttribute("data-type") === viewType) {
|
||
|
|
return activeHeader;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const header of Array.from(tabHeaders)) {
|
||
|
|
const headerEl = header;
|
||
|
|
const headerElWithLeaf = headerEl;
|
||
|
|
const headerLeaf = headerElWithLeaf.leaf;
|
||
|
|
if (headerLeaf === leaf) {
|
||
|
|
return headerEl;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (tabHeaders.length === 1) {
|
||
|
|
return tabHeaders[0];
|
||
|
|
}
|
||
|
|
if (isActive) {
|
||
|
|
const activeHeader = doc.querySelector(".workspace-tab-header.is-active");
|
||
|
|
if (activeHeader) {
|
||
|
|
return activeHeader;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Close the home base tab
|
||
|
|
* When called from context menu: Only closes the ghost tab (the "occupied" slot)
|
||
|
|
* Other home base tabs are left alone
|
||
|
|
*/
|
||
|
|
closeHomeBase(actuallyClose = false) {
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const homeBasePath = resolvePathSync(homeBaseSettings.type, homeBaseSettings.value, this.plugin.app);
|
||
|
|
const ghostTabs = [];
|
||
|
|
const allHomeBaseLeaves = [];
|
||
|
|
this.plugin.app.workspace.iterateAllLeaves((leaf) => {
|
||
|
|
const isGhost = this.plugin.homeService.isGhostLeaf(leaf);
|
||
|
|
if (isGhost) {
|
||
|
|
ghostTabs.push(leaf);
|
||
|
|
}
|
||
|
|
if (homeBasePath && leafHasFile(leaf, homeBasePath)) {
|
||
|
|
allHomeBaseLeaves.push(leaf);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (this.plugin.settings.showStickyHomeIcon) {
|
||
|
|
for (const ghostTab of ghostTabs) {
|
||
|
|
void ghostTab.detach();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
for (const leaf of allHomeBaseLeaves) {
|
||
|
|
void leaf.detach();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.updateTabHeaders();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Pin the home base tab
|
||
|
|
*/
|
||
|
|
pinHomeBaseTab() {
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const homeBasePath = resolvePathSync(homeBaseSettings.type, homeBaseSettings.value, this.plugin.app);
|
||
|
|
if (!homeBasePath) return;
|
||
|
|
const homeBaseFile = getFileByPath(this.plugin.app, homeBasePath);
|
||
|
|
if (!homeBaseFile) return;
|
||
|
|
const homeBaseLeaf = this.plugin.homeService.findExistingHomeBaseLeaf(homeBaseFile);
|
||
|
|
if (homeBaseLeaf) {
|
||
|
|
homeBaseLeaf.setPinned(true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Unpin the home base tab
|
||
|
|
*/
|
||
|
|
unpinHomeBaseTab() {
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const homeBasePath = resolvePathSync(homeBaseSettings.type, homeBaseSettings.value, this.plugin.app);
|
||
|
|
if (!homeBasePath) return;
|
||
|
|
const homeBaseFile = getFileByPath(this.plugin.app, homeBasePath);
|
||
|
|
if (!homeBaseFile) return;
|
||
|
|
const homeBaseLeaf = this.plugin.homeService.findExistingHomeBaseLeaf(homeBaseFile);
|
||
|
|
if (homeBaseLeaf) {
|
||
|
|
homeBaseLeaf.setPinned(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if the home base tab is pinned
|
||
|
|
*/
|
||
|
|
isHomeBaseTabPinned() {
|
||
|
|
const homeBaseSettings = this.plugin.getHomeBaseSettings();
|
||
|
|
const homeBasePath = resolvePathSync(homeBaseSettings.type, homeBaseSettings.value, this.plugin.app);
|
||
|
|
if (!homeBasePath) return false;
|
||
|
|
const homeBaseFile = getFileByPath(this.plugin.app, homeBasePath);
|
||
|
|
if (!homeBaseFile) return false;
|
||
|
|
const homeBaseLeaf = this.plugin.homeService.findExistingHomeBaseLeaf(homeBaseFile);
|
||
|
|
if (!homeBaseLeaf) return false;
|
||
|
|
const viewState = homeBaseLeaf.getViewState();
|
||
|
|
return viewState.pinned === true;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/services/mobile-button-service.ts
|
||
|
|
var import_obsidian10 = require("obsidian");
|
||
|
|
var MOBILE_HOME_CLASS = "home-base-mobile-enabled";
|
||
|
|
var MobileButtonService = class {
|
||
|
|
constructor(plugin) {
|
||
|
|
this.plugin = plugin;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update the mobile button based on settings
|
||
|
|
*/
|
||
|
|
update() {
|
||
|
|
if (!import_obsidian10.Platform.isMobile) {
|
||
|
|
this.remove();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this.plugin.settings.replaceMobileNewTab) {
|
||
|
|
this.apply();
|
||
|
|
} else {
|
||
|
|
this.remove();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Apply the mobile button replacement
|
||
|
|
*/
|
||
|
|
apply() {
|
||
|
|
document.body.classList.add(MOBILE_HOME_CLASS);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Remove the mobile button replacement
|
||
|
|
*/
|
||
|
|
remove() {
|
||
|
|
document.body.classList.remove(MOBILE_HOME_CLASS);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// src/migration.ts
|
||
|
|
async function migrateLegacySettings(plugin) {
|
||
|
|
let needsSave = false;
|
||
|
|
const settings = plugin.settings;
|
||
|
|
if (settings.homeBasePath && !plugin.settings.homeBaseValue) {
|
||
|
|
plugin.settings.homeBaseType = "File" /* File */;
|
||
|
|
plugin.settings.homeBaseValue = settings.homeBasePath;
|
||
|
|
needsSave = true;
|
||
|
|
}
|
||
|
|
if (settings.keepExistingTabs !== void 0) {
|
||
|
|
if (plugin.settings.openMode === DEFAULT_SETTINGS.openMode) {
|
||
|
|
plugin.settings.openMode = settings.keepExistingTabs ? "retain" : "replace-all";
|
||
|
|
needsSave = true;
|
||
|
|
}
|
||
|
|
delete settings.keepExistingTabs;
|
||
|
|
needsSave = true;
|
||
|
|
}
|
||
|
|
if (settings.mobileHomeBasePath && !plugin.settings.mobileHomeBaseValue) {
|
||
|
|
plugin.settings.mobileHomeBaseType = "File" /* File */;
|
||
|
|
plugin.settings.mobileHomeBaseValue = settings.mobileHomeBasePath;
|
||
|
|
needsSave = true;
|
||
|
|
}
|
||
|
|
if (settings.mobileHomeBasePath !== void 0) {
|
||
|
|
delete settings.mobileHomeBasePath;
|
||
|
|
needsSave = true;
|
||
|
|
}
|
||
|
|
if (settings.homeBasePath !== void 0) {
|
||
|
|
delete settings.homeBasePath;
|
||
|
|
needsSave = true;
|
||
|
|
}
|
||
|
|
if (needsSave) {
|
||
|
|
await plugin.saveSettings();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// src/main.ts
|
||
|
|
var HOME_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>`;
|
||
|
|
var DOM_READY_DELAY = 100;
|
||
|
|
var TAB_HEADER_UPDATE_DELAY = 150;
|
||
|
|
var FILE_OPEN_ANIMATION_DELAY = 100;
|
||
|
|
var TAB_SWITCH_ANIMATION_DELAY = 100;
|
||
|
|
var STARTUP_COMPLETE_DELAY = 1e3;
|
||
|
|
var HomeBasePlugin = class extends import_obsidian11.Plugin {
|
||
|
|
constructor() {
|
||
|
|
super(...arguments);
|
||
|
|
// Release notes tracking
|
||
|
|
this.newRelease = false;
|
||
|
|
// Track if patched opening behavior already ran
|
||
|
|
this.openingBehaviorRan = false;
|
||
|
|
// Track if we're currently in startup (to prevent handleOpenWhenEmpty from firing)
|
||
|
|
this.isStartup = true;
|
||
|
|
}
|
||
|
|
async onload() {
|
||
|
|
await this.loadSettings();
|
||
|
|
await migrateLegacySettings(this);
|
||
|
|
this.patchOpeningBehavior();
|
||
|
|
(0, import_obsidian11.addIcon)("home-base", HOME_ICON);
|
||
|
|
this.homeService = new HomeBaseService(this);
|
||
|
|
this.newTabService = new NewTabService(this);
|
||
|
|
this.stickyTabService = new StickyTabService(this);
|
||
|
|
this.mobileButtonService = new MobileButtonService(this);
|
||
|
|
this.addRibbonIcon("home", "Open home base", () => {
|
||
|
|
void this.homeService.openHomeBase({
|
||
|
|
replaceActiveLeaf: false,
|
||
|
|
runCommand: true
|
||
|
|
});
|
||
|
|
});
|
||
|
|
this.registerCommands();
|
||
|
|
this.addSettingTab(new HomeBaseSettingTab(this.app, this));
|
||
|
|
this.app.workspace.onLayoutReady(() => {
|
||
|
|
setTimeout(() => {
|
||
|
|
if (this.isSettingsModalOpen()) {
|
||
|
|
this.updateStickyTabIcon();
|
||
|
|
this.updateMobileButton();
|
||
|
|
this.stickyTabService.updateTabHeaders();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!this.openingBehaviorRan) {
|
||
|
|
this.newTabService.initialize();
|
||
|
|
} else {
|
||
|
|
this.newTabService.trackExistingLeaves();
|
||
|
|
}
|
||
|
|
this.homeService.restoreGhostLeaves();
|
||
|
|
setTimeout(() => {
|
||
|
|
this.isStartup = false;
|
||
|
|
}, STARTUP_COMPLETE_DELAY);
|
||
|
|
this.updateStickyTabIcon();
|
||
|
|
this.updateMobileButton();
|
||
|
|
this.stickyTabService.updateTabHeaders();
|
||
|
|
}, DOM_READY_DELAY);
|
||
|
|
});
|
||
|
|
this.registerEvent(
|
||
|
|
this.app.workspace.on("layout-change", async () => {
|
||
|
|
this.newTabService.handleLayoutChange();
|
||
|
|
if (this.settings.revertView) {
|
||
|
|
await this.homeService.revertView();
|
||
|
|
}
|
||
|
|
setTimeout(() => {
|
||
|
|
this.stickyTabService.updateActiveState();
|
||
|
|
this.stickyTabService.updateTabHeaders();
|
||
|
|
this.stickyTabService.updateIconPositionForSidebar();
|
||
|
|
}, TAB_HEADER_UPDATE_DELAY);
|
||
|
|
})
|
||
|
|
);
|
||
|
|
this.registerEvent(
|
||
|
|
this.app.workspace.on("file-open", () => {
|
||
|
|
setTimeout(() => {
|
||
|
|
this.stickyTabService.updateActiveState();
|
||
|
|
this.stickyTabService.updateTabHeaders();
|
||
|
|
}, FILE_OPEN_ANIMATION_DELAY);
|
||
|
|
})
|
||
|
|
);
|
||
|
|
this.registerEvent(
|
||
|
|
this.app.workspace.on("active-leaf-change", () => {
|
||
|
|
setTimeout(() => {
|
||
|
|
this.stickyTabService.updateTabHeaders();
|
||
|
|
}, TAB_SWITCH_ANIMATION_DELAY);
|
||
|
|
})
|
||
|
|
);
|
||
|
|
}
|
||
|
|
onunload() {
|
||
|
|
this.unpatchReleaseNotes();
|
||
|
|
this.unpatchOpeningBehavior();
|
||
|
|
this.stickyTabService.remove();
|
||
|
|
this.mobileButtonService.remove();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Register plugin commands
|
||
|
|
*/
|
||
|
|
registerCommands() {
|
||
|
|
this.addCommand({
|
||
|
|
id: "open",
|
||
|
|
name: "Open",
|
||
|
|
callback: () => {
|
||
|
|
const homeBaseSettings = this.getHomeBaseSettings();
|
||
|
|
if (!homeBaseSettings.value && homeBaseSettings.type === "File" /* File */) {
|
||
|
|
new import_obsidian11.Notice("No home base configured. Set one in settings.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
void this.homeService.openHomeBase({
|
||
|
|
replaceActiveLeaf: false,
|
||
|
|
runCommand: true
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.addCommand({
|
||
|
|
id: "set-current-file",
|
||
|
|
name: "Set current file as home",
|
||
|
|
checkCallback: (checking) => {
|
||
|
|
if (!this.homeService.canSetActiveFileAsHomeBase()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (!checking) {
|
||
|
|
void this.homeService.setActiveFileAsHomeBase().then((success) => {
|
||
|
|
if (success) {
|
||
|
|
const activeFile = this.app.workspace.getActiveFile();
|
||
|
|
new import_obsidian11.Notice(`Home base set to "${activeFile == null ? void 0 : activeFile.name}"`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.addCommand({
|
||
|
|
id: "toggle-sticky-icon",
|
||
|
|
name: "Toggle sticky home icon",
|
||
|
|
callback: async () => {
|
||
|
|
await this.stickyTabService.toggle();
|
||
|
|
const state = this.settings.showStickyHomeIcon ? "enabled" : "disabled";
|
||
|
|
new import_obsidian11.Notice(`Sticky home icon ${state}`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.addCommand({
|
||
|
|
id: "close",
|
||
|
|
name: "Close",
|
||
|
|
callback: () => {
|
||
|
|
this.stickyTabService.closeHomeBase();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Load plugin settings
|
||
|
|
*/
|
||
|
|
async loadSettings() {
|
||
|
|
const data = await this.loadData();
|
||
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, data != null ? data : {});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the active home base settings (mobile or desktop)
|
||
|
|
*/
|
||
|
|
getHomeBaseSettings() {
|
||
|
|
if (this.settings.separateMobile && import_obsidian11.Platform.isMobile) {
|
||
|
|
return {
|
||
|
|
type: this.settings.mobileHomeBaseType || "File" /* File */,
|
||
|
|
value: this.settings.mobileHomeBaseValue || ""
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
type: this.settings.homeBaseType || "File" /* File */,
|
||
|
|
value: this.settings.homeBaseValue || ""
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the active new tab settings (mobile or desktop)
|
||
|
|
* Falls back to home base settings if useDifferentFileForNewTab is disabled
|
||
|
|
*/
|
||
|
|
getNewTabSettings() {
|
||
|
|
if (!this.settings.useDifferentFileForNewTab) {
|
||
|
|
return this.getHomeBaseSettings();
|
||
|
|
}
|
||
|
|
if (this.settings.newTabSeparateMobile && import_obsidian11.Platform.isMobile) {
|
||
|
|
return {
|
||
|
|
type: this.settings.mobileNewTabType || "File" /* File */,
|
||
|
|
value: this.settings.mobileNewTabValue || ""
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
type: this.settings.newTabType || "File" /* File */,
|
||
|
|
value: this.settings.newTabValue || ""
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Save plugin settings
|
||
|
|
*/
|
||
|
|
async saveSettings() {
|
||
|
|
await this.saveData(this.settings);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update the sticky tab icon based on current settings
|
||
|
|
*/
|
||
|
|
updateStickyTabIcon() {
|
||
|
|
this.stickyTabService.update();
|
||
|
|
this.stickyTabService.updateTabHeaders();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Update the mobile button based on current settings
|
||
|
|
*/
|
||
|
|
updateMobileButton() {
|
||
|
|
this.mobileButtonService.update();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if we should skip startup logic (e.g., plugin reload, settings modal open)
|
||
|
|
* This prevents destructive behavior when the plugin is reloaded or settings are open
|
||
|
|
*/
|
||
|
|
shouldSkipStartupLogic() {
|
||
|
|
if (this.isSettingsModalOpen()) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const hasOpenFiles = this.app.workspace.getLeavesOfType("markdown").length > 0 || this.app.workspace.getLeavesOfType("canvas").length > 0 || this.app.workspace.getLeavesOfType("bases").length > 0 || this.app.workspace.getLeavesOfType("empty").length > 0;
|
||
|
|
return hasOpenFiles;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if the settings modal is currently open
|
||
|
|
*/
|
||
|
|
isSettingsModalOpen() {
|
||
|
|
const settingsModal = document.querySelector(".modal-container.mod-settings") || document.querySelector(".modal.mod-settings") || document.querySelector(".vertical-tab-content");
|
||
|
|
if (!settingsModal) {
|
||
|
|
const allModals = document.querySelectorAll(".modal-container");
|
||
|
|
for (const modal of Array.from(allModals)) {
|
||
|
|
if (modal.querySelector(".vertical-tab-content") || modal.querySelector(".settings-content") || modal.classList.contains("mod-settings")) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return settingsModal !== null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Patch runOpeningBehavior for fast startup (like homepage plugin)
|
||
|
|
*/
|
||
|
|
patchOpeningBehavior() {
|
||
|
|
try {
|
||
|
|
this.app.nvOrig_runOpeningBehavior = this.app.runOpeningBehavior;
|
||
|
|
this.app.runOpeningBehavior = async (path) => {
|
||
|
|
const openInitially = this.settings.openOnStartup && !this.hasUrlParams();
|
||
|
|
if (openInitially) {
|
||
|
|
this.openingBehaviorRan = true;
|
||
|
|
const mode = this.settings.openMode;
|
||
|
|
if (mode === "replace-all") {
|
||
|
|
await this.homeService.detachAllLeaves();
|
||
|
|
}
|
||
|
|
if (this.settings.showStickyHomeIcon) {
|
||
|
|
void this.homeService.openHomeBaseInGhostTab({
|
||
|
|
runCommand: true
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
void this.homeService.openHomeBaseWithMode(mode, true);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (this.app.nvOrig_runOpeningBehavior) {
|
||
|
|
await this.app.nvOrig_runOpeningBehavior(path);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.unpatchReleaseNotes();
|
||
|
|
};
|
||
|
|
} catch (e) {
|
||
|
|
console.warn("[Home Base] Failed to patch opening behavior:", e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Unpatch runOpeningBehavior
|
||
|
|
*/
|
||
|
|
unpatchOpeningBehavior() {
|
||
|
|
if (this.app.nvOrig_runOpeningBehavior) {
|
||
|
|
this.app.runOpeningBehavior = this.app.nvOrig_runOpeningBehavior;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Patch showReleaseNotes to track new releases
|
||
|
|
*/
|
||
|
|
patchReleaseNotes() {
|
||
|
|
try {
|
||
|
|
const appAny = this.app;
|
||
|
|
appAny.nvOrig_showReleaseNotes = appAny.showReleaseNotes;
|
||
|
|
appAny.showReleaseNotes = () => {
|
||
|
|
this.newRelease = true;
|
||
|
|
};
|
||
|
|
} catch (e) {
|
||
|
|
console.warn("[Home Base] Failed to patch release notes:", e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Unpatch showReleaseNotes
|
||
|
|
*/
|
||
|
|
unpatchReleaseNotes() {
|
||
|
|
var _a;
|
||
|
|
const appAny = this.app;
|
||
|
|
if (this.newRelease && !this.settings.hideReleaseNotes) {
|
||
|
|
(_a = appAny.nvOrig_showReleaseNotes) == null ? void 0 : _a.call(appAny);
|
||
|
|
}
|
||
|
|
if (appAny.nvOrig_showReleaseNotes) {
|
||
|
|
appAny.showReleaseNotes = appAny.nvOrig_showReleaseNotes;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if a home base type has its required plugin enabled
|
||
|
|
*/
|
||
|
|
hasRequiredPlugin(type) {
|
||
|
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
||
|
|
switch (type) {
|
||
|
|
case "Workspace" /* Workspace */:
|
||
|
|
return ((_c = (_b = (_a = this.app.internalPlugins) == null ? void 0 : _a.plugins) == null ? void 0 : _b.workspaces) == null ? void 0 : _c.enabled) === true;
|
||
|
|
case "Graph view" /* Graph */:
|
||
|
|
return ((_f = (_e = (_d = this.app.internalPlugins) == null ? void 0 : _d.plugins) == null ? void 0 : _e.graph) == null ? void 0 : _f.enabled) === true;
|
||
|
|
case "Journal" /* Journal */:
|
||
|
|
return !!((_h = (_g = this.app.plugins) == null ? void 0 : _g.plugins) == null ? void 0 : _h["journals"]);
|
||
|
|
case "Daily Note" /* DailyNote */:
|
||
|
|
case "Weekly Note" /* WeeklyNote */:
|
||
|
|
case "Monthly Note" /* MonthlyNote */:
|
||
|
|
case "Quarterly Note" /* QuarterlyNote */:
|
||
|
|
case "Yearly Note" /* YearlyNote */:
|
||
|
|
return this.hasRequiredPeriodicity(type);
|
||
|
|
default:
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if periodic notes are available for the given type
|
||
|
|
*/
|
||
|
|
hasRequiredPeriodicity(type) {
|
||
|
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s;
|
||
|
|
if (type === "Daily Note" /* DailyNote */) {
|
||
|
|
const coreDailyNotes = ((_c = (_b = (_a = this.app.internalPlugins) == null ? void 0 : _a.plugins) == null ? void 0 : _b["daily-notes"]) == null ? void 0 : _c.enabled) === true;
|
||
|
|
if (coreDailyNotes) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const periodicNotes2 = (_e = (_d = this.app.plugins) == null ? void 0 : _d.plugins) == null ? void 0 : _e["periodic-notes"];
|
||
|
|
if (periodicNotes2) {
|
||
|
|
const version2 = ((_f = periodicNotes2 == null ? void 0 : periodicNotes2.manifest) == null ? void 0 : _f.version) || "0";
|
||
|
|
const isLegacy2 = version2.startsWith("0");
|
||
|
|
if (isLegacy2) {
|
||
|
|
return ((_h = (_g = periodicNotes2 == null ? void 0 : periodicNotes2.settings) == null ? void 0 : _g["daily"]) == null ? void 0 : _h.enabled) === true;
|
||
|
|
} else {
|
||
|
|
const calendarSet = (_j = (_i = periodicNotes2 == null ? void 0 : periodicNotes2.calendarSetManager) == null ? void 0 : _i.getActiveSet) == null ? void 0 : _j.call(_i);
|
||
|
|
return ((_k = calendarSet == null ? void 0 : calendarSet["day"]) == null ? void 0 : _k.enabled) === true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const periodicNotes = (_m = (_l = this.app.plugins) == null ? void 0 : _l.plugins) == null ? void 0 : _m["periodic-notes"];
|
||
|
|
if (!periodicNotes) return false;
|
||
|
|
const version = ((_n = periodicNotes == null ? void 0 : periodicNotes.manifest) == null ? void 0 : _n.version) || "0";
|
||
|
|
const isLegacy = version.startsWith("0");
|
||
|
|
if (isLegacy) {
|
||
|
|
const periodMap = {
|
||
|
|
["Weekly Note" /* WeeklyNote */]: "weekly",
|
||
|
|
["Monthly Note" /* MonthlyNote */]: "monthly",
|
||
|
|
["Quarterly Note" /* QuarterlyNote */]: "quarterly",
|
||
|
|
["Yearly Note" /* YearlyNote */]: "yearly"
|
||
|
|
};
|
||
|
|
const adjective = periodMap[type];
|
||
|
|
if (!adjective) return false;
|
||
|
|
return ((_p = (_o = periodicNotes == null ? void 0 : periodicNotes.settings) == null ? void 0 : _o[adjective]) == null ? void 0 : _p.enabled) === true;
|
||
|
|
} else {
|
||
|
|
const nounMap = {
|
||
|
|
["Weekly Note" /* WeeklyNote */]: "week",
|
||
|
|
["Monthly Note" /* MonthlyNote */]: "month",
|
||
|
|
["Quarterly Note" /* QuarterlyNote */]: "quarter",
|
||
|
|
["Yearly Note" /* YearlyNote */]: "year"
|
||
|
|
};
|
||
|
|
const noun = nounMap[type];
|
||
|
|
if (!noun) return false;
|
||
|
|
const calendarSet = (_r = (_q = periodicNotes == null ? void 0 : periodicNotes.calendarSetManager) == null ? void 0 : _q.getActiveSet) == null ? void 0 : _r.call(_q);
|
||
|
|
return ((_s = calendarSet == null ? void 0 : calendarSet[noun]) == null ? void 0 : _s.enabled) === true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Check if URL params indicate a file/workspace should be opened (skip homepage)
|
||
|
|
*/
|
||
|
|
hasUrlParams() {
|
||
|
|
if (typeof window !== "undefined" && window.OBS_ACT) {
|
||
|
|
const params = Object.keys(window.OBS_ACT);
|
||
|
|
const action = window.OBS_ACT.action;
|
||
|
|
return action !== void 0 && ["open", "advanced-uri"].includes(action) && ["file", "filepath", "workspace"].some((e) => params.includes(e));
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// "Open when empty" feature removed - redundant with "New tab replacement: only when empty"
|
||
|
|
// Since Obsidian auto-creates an empty tab when you close the last one, they do the same thing
|
||
|
|
};
|
||
|
|
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsibm9kZV9tb2R1bGVzLy5wbnBtL29ic2lkaWFuLWRhaWx5LW5vdGVzLWludGVyZl84ZDlmYzI0YjBmYzYyMmQ5NGZkMTFmYTVlOWIxODViMy9ub2RlX21vZHVsZXMvb2JzaWRpYW4tZGFpbHktbm90ZXMtaW50ZXJmYWNlL2Rpc3QvbWFpbi5qcyIsICJzcmMvbWFpbi50cyIsICJzcmMvc2V0dGluZ3MudHMiLCAic3JjL3VpL3NldHRpbmdzLXRhYi50cyIsICJzcmMvdWkvZmlsZS1zdWdnZXN0LnRzIiwgInNyYy91aS9jb21tYW5kLXN1Z2dlc3QudHMiLCAic3JjL3VpL2ljb24tcGlja2VyLnRzIiwgInNyYy9zZXJ2aWNlcy9ob21lLXNlcnZpY2UudHMiLCAic3JjL3V0aWxzL2ZpbGUtdXRpbHMudHMiLCAic3JjL3V0aWxzL2hvbWViYXNlLXJlc29sdmVyLnRzIiwgInNyYy9zZXJ2aWNlcy9uZXctdGFiLXNlcnZpY2UudHMiLCAic3JjL3NlcnZpY2VzL3N0aWNreS10YWItc2VydmljZS50cyIsICJzcmMvc2VydmljZXMvbW9iaWxlLWJ1dHRvbi1zZXJ2aWNlLnRzIiwgInNyYy9taWdyYXRpb24udHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIid1c2Ugc3RyaWN0JztcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcblxudmFyIG9ic2lkaWFuID0gcmVxdWlyZSgnb2JzaWRpYW4nKTtcblxuY29uc3QgREVGQVVMVF9EQUlMWV9OT1RFX0ZPUk1BVCA9IFwiWVlZWS1NTS1ERFwiO1xuY29uc3QgREVGQVVMVF9XRUVLTFlfTk9URV9GT1JNQVQgPSBcImdnZ2ctW1ddd3dcIjtcbmNvbnN0IERFRkFVTFRfTU9OVEhMWV9OT1RFX0ZPUk1BVCA9IFwiWVlZWS1NTVwiO1xuY29uc3QgREVGQVVMVF9RVUFSVEVSTFlfTk9URV9GT1JNQVQgPSBcIllZWVktW1FdUVwiO1xuY29uc3QgREVGQVVMVF9ZRUFSTFlfTk9URV9GT1JNQVQgPSBcIllZWVlcIjtcblxuZnVuY3Rpb24gc2hvdWxkVXNlUGVyaW9kaWNOb3Rlc1NldHRpbmdzKHBlcmlvZGljaXR5KSB7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnlcbiAgICBjb25zdCBwZXJpb2RpY05vdGVzID0gd2luZG93LmFwcC5wbHVnaW5zLmdldFBsdWdpbihcInBlcmlvZGljLW5vdGVzXCIpO1xuICAgIHJldHVybiBwZXJpb2RpY05vdGVzICYmIHBlcmlvZGljTm90ZXMuc2V0dGluZ3M/LltwZXJpb2RpY2l0eV0/LmVuYWJsZWQ7XG59XG4vKipcbiAqIFJlYWQgdGhlIHVzZXIgc2V0dGluZ3MgZm9yIHRoZSBgZGFpbHktbm90ZXNgIHBsdWdpblxuICogdG8ga2VlcCBiZWhhdmlvciBvZiBjcmVhdGluZyBhIG5ldyBub3RlIGluLXN5bmMuXG4gKi9cbmZ1bmN0aW9uIGdldERhaWx5Tm90ZVNldHRpbmdzKCkge1xuICAgIHRyeSB7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tZXhwbGljaXQtYW55XG4gICAgICAgIGNvbnN0IHsgaW50ZXJuYWxQbHVnaW5zLCBwbHVnaW5zIH0gPSB3aW5kb3cuYXBwO1xuICAgICAgICBpZiAoc2hvdWxkVXNlUGVyaW9kaWNOb3Rlc1NldHRpbmdzKFwiZGFpbHlcIikpIHtcbiAgICAgICAgICAgIGNvbnN0IHsgZm9ybWF0LCBmb2xkZXIsIHRlbXBsYXRlIH0gPSBwbHVnaW5zLmdldFBsdWdpbihcInBlcmlvZGljLW5vdGVzXCIpPy5zZXR0aW5ncz8uZGFpbHkgfHwge307XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIGZvcm1hdDogZm9ybWF0IHx8IERFRkFVTFRfREFJTFlfTk9URV9GT1JNQVQsXG4gICAgICAgICAgICAgICAgZm9sZGVyOiBmb2xkZXI/LnRyaW0oKSB8fCBcIlwiLFxuICAgICAgICAgICAgICAgIHRlbXBsYXRlOiB0ZW1wbGF0ZT8udHJpbSgpIHx8IFwiXCIsXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHsgZm9sZGVyLCBmb3JtYXQsIHRlbXBsYXRlIH0gPSBpbnRlcm5hbFBsdWdpbnMuZ2V0UGx1Z2luQnlJZChcImRhaWx5LW5vdGVzXCIpPy5pbnN0YW5jZT8ub3B0aW9ucyB8fCB7fTtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGZvcm1hdDogZm9ybWF0IHx8IERFRkFVTFRfREFJTFlfTk9URV9GT1JNQVQsXG4gICAgICAgICAgICBmb2xkZXI6IGZvbGRlcj8udHJpbSgpIHx8IFwiXCIsXG4gICAgICAgICAgICB0ZW1wbGF0ZTogdGVtcGxhdGU/LnRyaW0oKSB8fCBcIlwiLFxuICAgICAgICB9O1xuICAgIH1cbiAgICBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGNvbnNvbGUuaW5mbyhcIk5vIGN1c3RvbSBkYWlseSBub3RlIHNldHRpbmdzIGZvdW5kIVwiLCBlcnIpO1xuICAgIH1cbn1cbi8qKlxuICogUmVhZCB0aGUgdXNlciBzZXR0aW5ncyBmb3IgdGhlIGB3ZWVrbHktbm90ZXNgIHBsdWdpblxuICogdG8ga2VlcCBiZWhhdmlvciBvZiBjcmVhdGluZyBhIG5ldyBub3RlIGluLXN5bmMuXG4gKi9cbmZ1bmN0aW9uIGdldFdlZWtseU5vdGVTZXR0aW5ncygpIHtcbiAgICB0cnkge1xuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueVxuICAgICAgICBjb25zdCBwbHVnaW5NYW5hZ2VyID0gd2luZG93LmFwcC5wbHVnaW5zO1xuICAgICAgICBjb25zdCBjYWxlbmRhclNldHRpbmdzID0gcGx1Z2luTWFuYWdlci5nZXRQbHVnaW4oXCJjYWxlbmRhclwiKT8ub3B0aW9ucztcbiAgICAgICAgY29uc3QgcGVyaW9kaWNOb3Rlc1NldHRpbmdzID0gcGx1Z2luTWFuYWdlci5nZXRQbHVnaW4oXCJwZXJpb2RpYy1ub3Rlc1wiKT8uc2V0dGluZ3M/LndlZWtseTtcbiAgICAgICAgaWYgKHNob3VsZFVzZVBlcmlvZGljTm90ZXNTZXR0aW5ncyhcIndlZWtseVwiKSkge1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICBmb3JtYXQ6IHBlcmlvZGljTm90ZXNTZXR0aW5ncy5mb3JtYXQgfHwgREVGQVVMVF9XRUVLTFlfTk9URV9GT1JNQVQsXG4gICAgICAgICAgICAgICAgZm9sZGVyOiBwZXJpb2RpY05vdGVzU2V0dGluZ3MuZm9sZGVyPy50cmltKCkgfHwgXCJcIixcbiAgICAgICAgICAgICAgICB0Z
|