vault backup: 2026-07-19 16:34:17
This commit is contained in:
Vendored
+2
-1
@@ -3,5 +3,6 @@
|
||||
"iconic",
|
||||
"folder-links",
|
||||
"settings-search",
|
||||
"omnisearch"
|
||||
"omnisearch",
|
||||
"auto-gitkeep"
|
||||
]
|
||||
+300
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
Vendored
+14
-13
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user