vault backup: 2026-07-19 16:34:17

This commit is contained in:
2026-07-19 16:34:17 +02:00
parent fda2206f43
commit d560da5af2
33 changed files with 3289 additions and 16 deletions
+2 -1
View File
@@ -3,5 +3,6 @@
"iconic",
"folder-links",
"settings-search",
"omnisearch"
"omnisearch",
"auto-gitkeep"
]
+300
View File
@@ -0,0 +1,300 @@
/*
THIS IS A GENERATED/COMPILED FILE AND IS NOT MEANT TO BE EDITED.
*/
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// main.ts
var main_exports = {};
__export(main_exports, {
default: () => AutoGitKeepPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian = require("obsidian");
var GITKEEP_FILENAME = ".gitkeep";
var SVG_TRASH = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" xmlns="http://www.w3.org/2000/svg"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>`;
var SVG_SCAN = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" xmlns="http://www.w3.org/2000/svg"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><rect x="7" y="7" width="10" height="10" rx="1"/></svg>`;
function getDefaultSettings(app) {
return {
autoEnabled: true,
excludedPaths: `${app.vault.configDir}
.trash`
};
}
function parseExcluded(raw) {
return new Set(
raw.split("\n").map((l) => (0, import_obsidian.normalizePath)(l.trim())).filter(Boolean)
);
}
function appendSvg(target, svg) {
const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
const node = doc.documentElement;
if (node && node.nodeName.toLowerCase() === "svg") {
target.appendChild(target.ownerDocument.importNode(node, true));
}
}
function isFolderExcluded(folderPath, excluded) {
for (const ex of excluded) {
if (folderPath === ex || folderPath.startsWith(ex + "/"))
return true;
}
return false;
}
function getAllFolders(app) {
const folders = [];
const recurse = (folder) => {
folders.push(folder);
for (const child of folder.children) {
if (child instanceof import_obsidian.TFolder)
recurse(child);
}
};
recurse(app.vault.getRoot());
return folders;
}
async function ensureGitKeep(app, folder, excluded) {
if (isFolderExcluded(folder.path, excluded))
return false;
const filePath = (0, import_obsidian.normalizePath)(
folder.path === "/" ? GITKEEP_FILENAME : `${folder.path}/${GITKEEP_FILENAME}`
);
if (await app.vault.adapter.exists(filePath))
return false;
await app.vault.create(filePath, "");
return true;
}
async function removeGitKeep(app, folder) {
const filePath = (0, import_obsidian.normalizePath)(
folder.path === "/" ? GITKEEP_FILENAME : `${folder.path}/${GITKEEP_FILENAME}`
);
if (!await app.vault.adapter.exists(filePath))
return false;
const file = app.vault.getAbstractFileByPath(filePath);
if (file instanceof import_obsidian.TFile) {
await app.fileManager.trashFile(file);
return true;
}
return false;
}
async function scanAndAddAll(app, excluded) {
const folders = getAllFolders(app);
let count = 0;
for (const folder of folders) {
if (await ensureGitKeep(app, folder, excluded))
count++;
}
return count;
}
async function removeAll(app) {
const folders = getAllFolders(app);
let count = 0;
for (const folder of folders) {
if (await removeGitKeep(app, folder))
count++;
}
return count;
}
var AutoGitKeepPlugin = class extends import_obsidian.Plugin {
constructor() {
super(...arguments);
this.statusBarItem = null;
}
async onload() {
await this.loadSettings();
this.statusBarItem = this.addStatusBarItem();
this.updateStatusBar();
this.addSettingTab(new AutoGitKeepSettingTab(this.app, this));
this.app.workspace.onLayoutReady(async () => {
if (this.settings.autoEnabled) {
const excluded = parseExcluded(this.settings.excludedPaths);
const count = await scanAndAddAll(this.app, excluded);
if (count > 0) {
new import_obsidian.Notice(`Auto GitKeep: added ${count} .gitkeep file${count !== 1 ? "s" : ""}.`);
}
}
});
this.registerEvent(
this.app.vault.on("create", async (file) => {
if (!this.settings.autoEnabled)
return;
if (!(file instanceof import_obsidian.TFolder))
return;
const excluded = parseExcluded(this.settings.excludedPaths);
await ensureGitKeep(this.app, file, excluded);
})
);
this.registerEvent(
this.app.vault.on("rename", async (file) => {
if (!this.settings.autoEnabled)
return;
if (!(file instanceof import_obsidian.TFolder))
return;
const excluded = parseExcluded(this.settings.excludedPaths);
await ensureGitKeep(this.app, file, excluded);
})
);
}
onunload() {
}
// ── Status bar ────────────────────────────
updateStatusBar() {
if (!this.statusBarItem)
return;
}
// ── Settings persistence ──────────────────
async loadSettings() {
const loaded = await this.loadData();
this.settings = Object.assign({}, getDefaultSettings(this.app), loaded != null ? loaded : {});
}
async saveSettings() {
await this.saveData(this.settings);
this.updateStatusBar();
}
};
var AutoGitKeepSettingTab = class extends import_obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
// ── Rich description helper ───────────────
desc(parts) {
const frag = activeDocument.createDocumentFragment();
for (const p of parts) {
if (typeof p === "string") {
frag.appendText(p);
} else if (p.strong) {
const el = activeDocument.createElement("span");
el.className = "agk-desc-strong";
el.textContent = p.strong;
frag.appendChild(el);
} else if (p.code) {
const el = activeDocument.createElement("code");
el.className = "agk-desc-code";
el.textContent = p.code;
frag.appendChild(el);
}
}
return frag;
}
// ── Main render ───────────────────────────
display() {
const { containerEl } = this;
containerEl.empty();
new import_obsidian.Setting(containerEl).setName("Auto GitKeep").setDesc(this.desc([
"When enabled, ",
{ strong: "automatically adds" },
{ code: ".gitkeep" },
" to every new folder and scans the vault on startup."
])).addToggle(
(toggle) => toggle.setValue(this.plugin.settings.autoEnabled).onChange(async (value) => {
this.plugin.settings.autoEnabled = value;
await this.plugin.saveSettings();
this.display();
})
);
new import_obsidian.Setting(containerEl).setName("Excluded paths").setDesc(this.desc([
"Vault-relative folder paths to skip \u2014 one per line. ",
{ strong: "Subdirectories are excluded automatically." },
" Example: ",
{ code: this.app.vault.configDir },
", ",
{ code: ".trash" },
"."
])).addTextArea((ta) => {
ta.setPlaceholder(`${this.app.vault.configDir}
.trash`).setValue(this.plugin.settings.excludedPaths).onChange(async (value) => {
this.plugin.settings.excludedPaths = value;
await this.plugin.saveSettings();
});
ta.inputEl.rows = 5;
ta.inputEl.addClass("agk-excluded-textarea");
return ta;
});
new import_obsidian.Setting(containerEl).setName("Actions").setHeading();
containerEl.createEl("p", {
text: "Run these operations on demand regardless of the auto toggle above.",
cls: "setting-item-description"
});
const actionsRow = containerEl.createDiv({ cls: "agk-actions" });
const addBtn = actionsRow.createEl("button", { cls: "agk-btn mod-cta" });
appendSvg(addBtn, SVG_SCAN);
addBtn.createSpan({ text: "Add .gitkeep to all folders" });
addBtn.onclick = async () => {
addBtn.setAttribute("disabled", "true");
addBtn.querySelector("span").textContent = "Working\u2026";
const excluded2 = parseExcluded(this.plugin.settings.excludedPaths);
const count = await scanAndAddAll(this.app, excluded2);
new import_obsidian.Notice(
count > 0 ? `Auto GitKeep: added ${count} .gitkeep file${count !== 1 ? "s" : ""}.` : "Auto GitKeep: all folders already have .gitkeep."
);
addBtn.removeAttribute("disabled");
addBtn.querySelector("span").textContent = "Add .gitkeep to all folders";
this.display();
};
const removeBtn = actionsRow.createEl("button", { cls: "agk-btn mod-danger" });
appendSvg(removeBtn, SVG_TRASH);
removeBtn.createSpan({ text: "Remove all .gitkeep" });
removeBtn.onclick = async () => {
removeBtn.setAttribute("disabled", "true");
removeBtn.querySelector("span").textContent = "Removing\u2026";
const count = await removeAll(this.app);
new import_obsidian.Notice(
count > 0 ? `Auto GitKeep: removed ${count} .gitkeep file${count !== 1 ? "s" : ""}.` : "Auto GitKeep: no .gitkeep files found."
);
removeBtn.removeAttribute("disabled");
removeBtn.querySelector("span").textContent = "Remove all .gitkeep";
this.display();
};
new import_obsidian.Setting(containerEl).setName("Status").setHeading();
const statusDiv = containerEl.createDiv({ cls: "agk-status-block" });
const countGitKeep = () => {
let count = 0;
const recurse = (folder) => {
const has = folder.children.some(
(c) => !(c instanceof import_obsidian.TFolder) && c.name === GITKEEP_FILENAME
);
if (has)
count++;
for (const child of folder.children) {
if (child instanceof import_obsidian.TFolder)
recurse(child);
}
};
recurse(this.app.vault.getRoot());
return count;
};
const totalFolders = getAllFolders(this.app).length;
const totalGitKeep = countGitKeep();
const excluded = parseExcluded(this.plugin.settings.excludedPaths);
const excludedCount = getAllFolders(this.app).filter(
(f) => isFolderExcluded(f.path, excluded)
).length;
const statusLine = (label, value) => {
const p = statusDiv.createEl("p");
p.createEl("strong", { text: `${label}:` });
p.appendText(` ${value}`);
};
statusLine("Auto mode", this.plugin.settings.autoEnabled ? "Enabled" : "Disabled");
statusLine("Total folders", totalFolders);
statusLine("Excluded folders", excludedCount);
statusLine(".gitkeep files present", totalGitKeep);
}
};
/* nosourcemap */
+10
View File
@@ -0,0 +1,10 @@
{
"id": "auto-gitkeep",
"name": "Auto GitKeep",
"version": "1.1.2",
"minAppVersion": "1.7.2",
"description": "Automatically places a .gitkeep file in every folder of your vault so empty directories are tracked by Git. Scans existing folders on load and watches for new ones.",
"author": "SATOSprod",
"authorUrl": "",
"isDesktopOnly": false
}
+115
View File
@@ -0,0 +1,115 @@
/* ─────────────────────────────────────────
Auto GitKeep Plugin — styles.css
Minimal flat design, no shadows, no hover animations
───────────────────────────────────────── */
/* ── Section headings ── */
.agk-section-heading {
font-size: var(--font-small);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
margin-top: 20px;
margin-bottom: 4px;
padding-bottom: 4px;
border-bottom: 1px solid var(--background-modifier-border);
}
/* ── Action buttons row ── */
.agk-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.agk-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: var(--radius-m, 4px);
font-size: var(--font-small);
font-weight: 500;
cursor: pointer;
border: 1px solid var(--background-modifier-border);
background: var(--background-secondary);
color: var(--text-normal);
user-select: none;
}
.agk-btn svg {
width: 14px;
height: 14px;
flex-shrink: 0;
}
.agk-btn.mod-danger {
border-color: var(--text-error);
color: var(--text-error);
background: transparent;
}
.agk-btn.mod-cta {
border-color: var(--interactive-accent);
color: var(--interactive-accent);
background: transparent;
}
/* ── Status block ── */
.agk-status-block {
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-m, 4px);
padding: 10px 14px;
margin-top: 8px;
}
.agk-status-block p {
margin: 3px 0;
color: var(--text-muted);
font-size: var(--font-small);
}
.agk-status-block p strong {
color: var(--text-normal);
font-weight: 600;
}
/* ── Description emphasis helpers ── */
.agk-desc-strong {
font-weight: 600;
color: var(--text-normal);
}
.agk-desc-code {
font-family: var(--font-monospace);
font-size: 0.9em;
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
border-radius: 3px;
padding: 0 4px;
color: var(--text-normal);
}
/* ── Status bar pill ── */
.agk-statusbar {
display: inline-flex;
align-items: center;
gap: 5px;
cursor: default;
font-size: var(--font-small);
}
.agk-statusbar svg {
width: 13px;
height: 13px;
flex-shrink: 0;
}
.agk-excluded-textarea {
width: 100%;
font-family: var(--font-monospace);
font-size: 12px;
}
+14 -13
View File
@@ -4,21 +4,17 @@
"type": "split",
"children": [
{
"id": "599535c394bd0ccc",
"id": "be235ae05405f804",
"type": "tabs",
"children": [
{
"id": "84655ec8deead6f8",
"id": "579a503273b0c5ec",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "20 Work/Ideas/Giving everyone a chance to learn management.md",
"mode": "source",
"source": false
},
"type": "empty",
"state": {},
"icon": "lucide-file",
"title": "Giving everyone a chance to learn management"
"title": "New tab"
}
}
]
@@ -196,10 +192,17 @@
"omnisearch:Omnisearch": false
}
},
"active": "84655ec8deead6f8",
"active": "579a503273b0c5ec",
"lastOpenFiles": [
"20 Work/Ideas/Coaching on management.md",
"10 Knowledge/CTO Academy/Data/Semantic Layer.md",
"10 Knowledge/CTO Academy/Data/Snowflake Schema.md",
"10 Knowledge/CTO Academy/Data/Star Schema.md",
"10 Knowledge/CTO Academy/Data/Dimension Table.md",
"10 Knowledge/CTO Academy/Data/Fact Table.md",
"10 Knowledge/CTO Academy/Data/Medallion Architecture.md",
"10 Knowledge/CTO Academy/Data/ETL vs ELT.md",
"20 Work/Ideas/Giving everyone a chance to learn management.md",
"20 Work/Ideas/Coaching on management.md",
"10 Knowledge/Templates/Journal Template.md",
"10 Knowledge/Templates/Academy Template.md",
"10 Knowledge/Templates/Employe - Team Template.md",
@@ -207,10 +210,8 @@
"10 Knowledge/Templates/Idea Template.md",
"10 Knowledge/Templates/Meeting Template.md",
"10 Knowledge/Templates/Decision Template.md",
"10 Knowledge/CTO Academy/Data/ETL vs ELT.md",
"10 Knowledge/Templates",
"10 Knowledge/CTO Academy/Data/Data Lake vs Data Warehouse vs Lakehouse.md",
"10 Knowledge/CTO Academy/Data/Medallion Architecture.md",
"Welcome.md",
"10 Knowledge/CTO Academy/Data",
"10 Knowledge/CTO Academy.md",