From ce11fe22c58637c9ae2a6e5c88482851b3191f8b Mon Sep 17 00:00:00 2001 From: tobias Date: Fri, 31 Jul 2026 16:39:56 +0200 Subject: [PATCH] vault backup: 2026-07-31 16:39:56 --- .obsidian/community-plugins.json | 4 +- .../plugins/color-folders-files/data.json | 19 + .obsidian/plugins/color-folders-files/main.js | 933 +++++++++++++++++ .../plugins/color-folders-files/manifest.json | 11 + .../plugins/color-folders-files/styles.css | 274 +++++ .../plugins/file-explorer-note-count/main.js | 973 ++++++++++++++++++ .../file-explorer-note-count/manifest.json | 10 + .../file-explorer-note-count/styles.css | 34 + .obsidian/workspace.json | 22 +- 9 files changed, 2270 insertions(+), 10 deletions(-) create mode 100644 .obsidian/plugins/color-folders-files/data.json create mode 100644 .obsidian/plugins/color-folders-files/main.js create mode 100644 .obsidian/plugins/color-folders-files/manifest.json create mode 100644 .obsidian/plugins/color-folders-files/styles.css create mode 100644 .obsidian/plugins/file-explorer-note-count/main.js create mode 100644 .obsidian/plugins/file-explorer-note-count/manifest.json create mode 100644 .obsidian/plugins/file-explorer-note-count/styles.css diff --git a/.obsidian/community-plugins.json b/.obsidian/community-plugins.json index 33ac0a0..8703f58 100644 --- a/.obsidian/community-plugins.json +++ b/.obsidian/community-plugins.json @@ -1,5 +1,7 @@ [ "obsidian-git", "local-llm-helper", - "table-editor-obsidian" + "table-editor-obsidian", + "file-explorer-note-count", + "color-folders-files" ] \ No newline at end of file diff --git a/.obsidian/plugins/color-folders-files/data.json b/.obsidian/plugins/color-folders-files/data.json new file mode 100644 index 0000000..02cfe9c --- /dev/null +++ b/.obsidian/plugins/color-folders-files/data.json @@ -0,0 +1,19 @@ +{ + "styles": { + "10. Grundlagen technischer Systeme": {} + }, + "presets": { + "Grundlagen technischer Systeme": { + "backgroundColor": "#2d69e1", + "textColor": "#ffffff", + "isBold": false, + "isItalic": false, + "opacity": 1, + "applyToSubfolders": false, + "applyToFiles": false + } + }, + "presetOrder": [ + "Grundlagen technischer Systeme" + ] +} \ No newline at end of file diff --git a/.obsidian/plugins/color-folders-files/main.js b/.obsidian/plugins/color-folders-files/main.js new file mode 100644 index 0000000..3e74065 --- /dev/null +++ b/.obsidian/plugins/color-folders-files/main.js @@ -0,0 +1,933 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +If you want to view the source, visit the plugin's github repository +*/ + +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); + +// src/main.ts +var main_exports = {}; +__export(main_exports, { + default: () => ColorFolderPlugin +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian3 = require("obsidian"); + +// src/constants.ts +var DEFAULT_STYLE = { + backgroundColor: "#ffffff", + textColor: "#000000", + isBold: false, + isItalic: false, + opacity: 1, + applyToSubfolders: false, + applyToFiles: false +}; +var DEFAULT_SETTINGS = { + styles: {}, + presets: {}, + presetOrder: [] +}; + +// src/settings/ColorSettingsTab.ts +var import_obsidian = require("obsidian"); +var ColorSettingsTab = class extends import_obsidian.PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + this.resetPresetStyle(); + this.syncPresetOrder(); + } + syncPresetOrder() { + const allPresets = Object.keys(this.plugin.settings.presets); + if (!this.plugin.settings.presetOrder) { + this.plugin.settings.presetOrder = []; + } + allPresets.forEach((presetName) => { + if (!this.plugin.settings.presetOrder.includes(presetName)) { + this.plugin.settings.presetOrder.push(presetName); + } + }); + this.plugin.settings.presetOrder = this.plugin.settings.presetOrder.filter( + (name) => allPresets.includes(name) + ); + this.plugin.saveSettings(); + } + resetPresetStyle() { + this.presetStyle = { ...DEFAULT_STYLE }; + } + hide() { + if (this.styleEl) { + this.styleEl.remove(); + } + super.hide(); + } + display() { + const { containerEl } = this; + containerEl.empty(); + if (this.styleEl) { + this.styleEl.remove(); + } + this.styleEl = document.createElement("style"); + document.head.appendChild(this.styleEl); + this.syncPresetOrder(); + new import_obsidian.Setting(containerEl).setHeading().setName("Create new preset"); + const previewEl = containerEl.createDiv("preview-item"); + previewEl.setAttribute("data-path", "new-preset-preview"); + previewEl.createSpan().setText("Preview"); + this.updatePreview(previewEl); + if (Object.keys(this.plugin.settings.presets).length > 0) { + new import_obsidian.Setting(containerEl).setName("Start from preset").setDesc("Select an existing preset as a starting point").addDropdown((dropdown) => { + dropdown.addOption("", "Select a preset..."); + Object.keys(this.plugin.settings.presets).forEach((preset) => { + dropdown.addOption(preset, preset); + }); + return dropdown.onChange((value) => { + if (value) { + this.presetStyle = { ...this.plugin.settings.presets[value] }; + this.updatePreview(previewEl); + if (this.bgColorPicker) + this.bgColorPicker.setValue(this.presetStyle.backgroundColor || "#ffffff"); + if (this.textColorPicker) + this.textColorPicker.setValue(this.presetStyle.textColor || "#000000"); + if (this.boldToggle) + this.boldToggle.setValue(this.presetStyle.isBold || false); + if (this.italicToggle) + this.italicToggle.setValue(this.presetStyle.isItalic || false); + if (this.opacitySlider) + this.opacitySlider.setValue(this.presetStyle.opacity || 1); + dropdown.setValue(""); + } + }); + }); + } + const bgColorSetting = new import_obsidian.Setting(containerEl).setName("Background color"); + const bgColorContainer = bgColorSetting.controlEl.createDiv("color-container"); + let bgHexInput; + let bgColorPicker; + bgColorSetting.addColorPicker((color) => { + bgColorPicker = color; + this.bgColorPicker = color; + color.setValue(this.presetStyle.backgroundColor || "#ffffff").onChange((value) => { + this.presetStyle.backgroundColor = value; + bgHexInput.value = value; + this.updatePreview(previewEl); + }); + }); + bgHexInput = bgColorContainer.createEl("input", { + type: "text", + cls: "color-hex-input", + value: this.presetStyle.backgroundColor || "#ffffff" + }); + bgHexInput.addEventListener("change", () => { + const value = bgHexInput.value; + if (/^#[0-9A-Fa-f]{6}$/.test(value)) { + this.presetStyle.backgroundColor = value; + bgColorPicker.setValue(value); + this.updatePreview(previewEl); + } + }); + const textColorSetting = new import_obsidian.Setting(containerEl).setName("Text color"); + const textColorContainer = textColorSetting.controlEl.createDiv("color-container"); + let textHexInput; + let textColorPicker; + textColorSetting.addColorPicker((color) => { + textColorPicker = color; + this.textColorPicker = color; + color.setValue(this.presetStyle.textColor || "#000000").onChange((value) => { + this.presetStyle.textColor = value; + textHexInput.value = value; + this.updatePreview(previewEl); + }); + }); + textHexInput = textColorContainer.createEl("input", { + type: "text", + cls: "color-hex-input", + value: this.presetStyle.textColor || "#000000" + }); + textHexInput.addEventListener("change", () => { + const value = textHexInput.value; + if (/^#[0-9A-Fa-f]{6}$/.test(value)) { + this.presetStyle.textColor = value; + textColorPicker.setValue(value); + this.updatePreview(previewEl); + } + }); + let boldToggle; + new import_obsidian.Setting(containerEl).setName("Bold").addToggle((toggle) => { + boldToggle = toggle; + this.boldToggle = toggle; + toggle.setValue(this.presetStyle.isBold || false).onChange((value) => { + this.presetStyle.isBold = value; + this.updatePreview(previewEl); + }); + }); + let italicToggle; + new import_obsidian.Setting(containerEl).setName("Italic").addToggle((toggle) => { + italicToggle = toggle; + this.italicToggle = toggle; + toggle.setValue(this.presetStyle.isItalic || false).onChange((value) => { + this.presetStyle.isItalic = value; + this.updatePreview(previewEl); + }); + }); + let opacitySlider; + new import_obsidian.Setting(containerEl).setName("Opacity").addSlider((slider) => { + opacitySlider = slider; + this.opacitySlider = slider; + slider.setLimits(0, 1, 0.1).setValue(this.presetStyle.opacity || 1).onChange((value) => { + this.presetStyle.opacity = value; + this.updatePreview(previewEl); + }); + }); + let textComponent; + const savePresetSetting = new import_obsidian.Setting(containerEl).setName("Save preset").addText((text) => { + textComponent = text; + text.setPlaceholder("Preset name").setValue(""); + }).addButton((button) => button.setButtonText("Reset").onClick(() => { + this.resetPresetStyle(); + this.updatePreview(previewEl); + bgColorPicker.setValue(this.presetStyle.backgroundColor || "#ffffff"); + textColorPicker.setValue(this.presetStyle.textColor || "#000000"); + boldToggle.setValue(this.presetStyle.isBold || false); + italicToggle.setValue(this.presetStyle.isItalic || false); + opacitySlider.setValue(this.presetStyle.opacity || 1); + })).addButton((button) => button.setButtonText("Save").setCta().onClick(async () => { + const presetName = textComponent.getValue(); + if (presetName) { + if (this.plugin.settings.presets[presetName]) { + const shouldOverwrite = await this.plugin.confirmOverwritePreset(presetName); + if (!shouldOverwrite) + return; + } + this.plugin.settings.presets[presetName] = { ...this.presetStyle }; + if (!this.plugin.settings.presetOrder.includes(presetName)) { + this.plugin.settings.presetOrder.push(presetName); + } + await this.plugin.saveSettings(); + textComponent.setValue(""); + this.display(); + new import_obsidian.Notice(`Preset "${presetName}" saved`); + } + })); + new import_obsidian.Setting(containerEl).setHeading().setName("Existing presets"); + const presetsContainer = containerEl.createDiv("presets-container"); + this.plugin.settings.presetOrder.forEach((name) => { + const preset = this.plugin.settings.presets[name]; + if (!preset) + return; + const presetContainer = presetsContainer.createDiv("preset-container"); + presetContainer.setAttribute("draggable", "true"); + presetContainer.dataset.presetName = name; + presetContainer.addEventListener("dragstart", (e) => { + if (e.dataTransfer) { + e.dataTransfer.setData("text/plain", name); + presetContainer.addClass("dragging"); + } + }); + presetContainer.addEventListener("dragend", () => { + presetContainer.removeClass("dragging"); + }); + presetContainer.addEventListener("dragover", (e) => { + e.preventDefault(); + const dragging = presetsContainer.querySelector(".dragging"); + if (!dragging) + return; + const siblings = Array.from(presetsContainer.querySelectorAll(".preset-container:not(.dragging)")); + const nextSibling = siblings.find((sibling) => { + const rect = sibling.getBoundingClientRect(); + const offset = e.clientY - rect.top - rect.height / 2; + return offset < 0; + }); + if (nextSibling) { + presetsContainer.insertBefore(dragging, nextSibling); + } else { + presetsContainer.appendChild(dragging); + } + }); + presetContainer.addEventListener("drop", async (e) => { + e.preventDefault(); + if (e.dataTransfer) { + const containers = Array.from(presetsContainer.querySelectorAll(".preset-container")); + const newOrder = containers.map((container) => container.dataset.presetName).filter((name2) => name2 !== void 0); + this.plugin.settings.presetOrder = newOrder; + await this.plugin.saveSettings(); + } + }); + const previewEl2 = presetContainer.createDiv("preview-item"); + previewEl2.setAttribute("data-path", `preset-${name}`); + previewEl2.createSpan().setText(name); + this.updatePreview(previewEl2, preset); + const dragHandle = presetContainer.createDiv("drag-handle"); + const handleIcon = dragHandle.createSpan(); + handleIcon.setText("\u22EE\u22EE"); + new import_obsidian.Setting(presetContainer).addButton((btn) => btn.setIcon("trash").setTooltip("Delete preset").onClick(async () => { + delete this.plugin.settings.presets[name]; + this.plugin.settings.presetOrder = this.plugin.settings.presetOrder.filter((n) => n !== name); + await this.plugin.saveSettings(); + this.display(); + new import_obsidian.Notice(`Preset "${name}" deleted`); + })); + }); + new import_obsidian.Setting(containerEl).setHeading().setName("Import/export"); + const importExportContainer = containerEl.createDiv("settings-import-export"); + const importButton = importExportContainer.createEl("button", { + text: "Import" + }); + importButton.addEventListener("click", async () => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".json"; + input.onchange = async () => { + var _a; + const file = (_a = input.files) == null ? void 0 : _a[0]; + if (file) { + try { + const text = await file.text(); + const settings = JSON.parse(text); + if (settings && typeof settings === "object" && "styles" in settings && "presets" in settings) { + this.plugin.settings = settings; + this.syncPresetOrder(); + await this.plugin.saveSettings(); + this.display(); + new import_obsidian.Notice("Settings imported successfully"); + } else { + new import_obsidian.Notice("Invalid settings file format"); + } + } catch (e) { + console.error("Error importing settings:", e); + new import_obsidian.Notice("Error importing settings"); + } + } + }; + input.click(); + }); + const exportButton = importExportContainer.createEl("button", { + text: "Export" + }); + exportButton.addEventListener("click", () => { + const settingsJson = JSON.stringify(this.plugin.settings, null, 2); + const blob = new Blob([settingsJson], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `color-folders-files-settings-v${this.plugin.manifest.version}.json`; + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); + }); + } + updatePreview(previewEl, style = this.presetStyle) { + const path = previewEl.getAttribute("data-path"); + if (!path || !style) + return; + if (style.backgroundColor) { + previewEl.style.backgroundColor = style.backgroundColor; + previewEl.style.transition = "background-color 0.1s ease"; + } + if (style.textColor) { + previewEl.style.color = style.textColor; + } + if (style.isBold) { + previewEl.style.fontWeight = "bold"; + } + if (style.isItalic) { + previewEl.style.fontStyle = "italic"; + } + if (typeof style.opacity === "number") { + previewEl.style.opacity = String(style.opacity); + } + if (style.backgroundColor) { + const rules = ` + /* Light mode: lighten on hover */ + body.theme-light .preview-item[data-path="${path}"]:hover { + background-color: color-mix(in srgb, white 20%, ${style.backgroundColor}) !important; + ${typeof style.opacity === "number" ? `opacity: ${Math.min(1, style.opacity + 0.15)} !important;` : ""} + } + + /* Dark mode: darken on hover */ + body.theme-dark .preview-item[data-path="${path}"]:hover { + background-color: color-mix(in srgb, black 20%, ${style.backgroundColor}) !important; + ${typeof style.opacity === "number" ? `opacity: ${Math.min(1, style.opacity + 0.15)} !important;` : ""} + } + `; + this.styleEl.textContent += rules; + } + } +}; + +// src/core/StyleManager.ts +var StyleManager = class { + constructor() { + this.styleEl = document.createElement("style"); + document.head.appendChild(this.styleEl); + } + updateStyles(styles) { + const cssRules = []; + cssRules.push(` + .nav-folder-title, + .nav-file-title, + .preview-item { + transition: background-color 0.1s ease, opacity 0.1s ease !important; + } + `); + for (const [path, style] of Object.entries(styles)) { + const escapedPath = CSS.escape(path); + if (style.backgroundColor) { + cssRules.push(` + .nav-folder-title[data-path="${escapedPath}"] { + background-color: ${style.backgroundColor} !important; + } + + /* Light mode: lighten on hover */ + body.theme-light .nav-folder-title[data-path="${escapedPath}"]:hover { + background-color: color-mix(in srgb, white 20%, ${style.backgroundColor}) !important; + } + + /* Dark mode: darken on hover */ + body.theme-dark .nav-folder-title[data-path="${escapedPath}"]:hover { + background-color: color-mix(in srgb, black 20%, ${style.backgroundColor}) !important; + } + `); + } + if (style.textColor) { + cssRules.push(` + .nav-folder-title[data-path="${escapedPath}"], + .nav-folder-title[data-path="${escapedPath}"]:hover { + color: ${style.textColor} !important; + } + `); + } + if (style.backgroundColor) { + cssRules.push(` + .nav-file-title[data-path="${escapedPath}"] { + background-color: ${style.backgroundColor} !important; + } + + /* Light mode: lighten on hover */ + body.theme-light .nav-file-title[data-path="${escapedPath}"]:hover { + background-color: color-mix(in srgb, white 20%, ${style.backgroundColor}) !important; + } + + /* Dark mode: darken on hover */ + body.theme-dark .nav-file-title[data-path="${escapedPath}"]:hover { + background-color: color-mix(in srgb, black 20%, ${style.backgroundColor}) !important; + } + `); + } + if (style.textColor) { + cssRules.push(` + .nav-file-title[data-path="${escapedPath}"], + .nav-file-title[data-path="${escapedPath}"]:hover { + color: ${style.textColor} !important; + } + `); + } + if (style.isBold) { + cssRules.push(` + .nav-folder-title[data-path="${escapedPath}"], + .nav-file-title[data-path="${escapedPath}"] { + font-weight: bold !important; + } + `); + } + if (style.isItalic) { + cssRules.push(` + .nav-folder-title[data-path="${escapedPath}"], + .nav-file-title[data-path="${escapedPath}"] { + font-style: italic !important; + } + `); + } + if (typeof style.opacity === "number") { + cssRules.push(` + .nav-folder-title[data-path="${escapedPath}"], + .nav-file-title[data-path="${escapedPath}"] { + opacity: ${style.opacity} !important; + } + .nav-folder-title[data-path="${escapedPath}"]:hover, + .nav-file-title[data-path="${escapedPath}"]:hover { + opacity: ${Math.min(1, style.opacity + 0.15)} !important; + } + `); + } + if (style.applyToSubfolders) { + if (style.backgroundColor) { + cssRules.push(` + .nav-folder-title[data-path^="${escapedPath}/"] { + background-color: ${style.backgroundColor} !important; + } + + /* Light mode: lighten on hover */ + body.theme-light .nav-folder-title[data-path^="${escapedPath}/"]:hover { + background-color: color-mix(in srgb, white 20%, ${style.backgroundColor}) !important; + } + + /* Dark mode: darken on hover */ + body.theme-dark .nav-folder-title[data-path^="${escapedPath}/"]:hover { + background-color: color-mix(in srgb, black 20%, ${style.backgroundColor}) !important; + } + `); + } + if (style.textColor) { + cssRules.push(` + .nav-folder-title[data-path^="${escapedPath}/"], + .nav-folder-title[data-path^="${escapedPath}/"]:hover { + color: ${style.textColor} !important; + } + `); + } + if (style.isBold) { + cssRules.push(` + .nav-folder-title[data-path^="${escapedPath}/"] { + font-weight: bold !important; + } + `); + } + if (style.isItalic) { + cssRules.push(` + .nav-folder-title[data-path^="${escapedPath}/"] { + font-style: italic !important; + } + `); + } + if (typeof style.opacity === "number") { + cssRules.push(` + .nav-folder-title[data-path^="${escapedPath}/"] { + opacity: ${style.opacity} !important; + } + .nav-folder-title[data-path^="${escapedPath}/"]:hover { + opacity: ${Math.min(1, style.opacity + 0.15)} !important; + } + `); + } + } + if (style.applyToFiles) { + if (style.backgroundColor) { + cssRules.push(` + .nav-file-title[data-path^="${escapedPath}/"] { + background-color: ${style.backgroundColor} !important; + } + + /* Light mode: lighten on hover */ + body.theme-light .nav-file-title[data-path^="${escapedPath}/"]:hover { + background-color: color-mix(in srgb, white 20%, ${style.backgroundColor}) !important; + } + + /* Dark mode: darken on hover */ + body.theme-dark .nav-file-title[data-path^="${escapedPath}/"]:hover { + background-color: color-mix(in srgb, black 20%, ${style.backgroundColor}) !important; + } + `); + } + if (style.textColor) { + cssRules.push(` + .nav-file-title[data-path^="${escapedPath}/"], + .nav-file-title[data-path^="${escapedPath}/"]:hover { + color: ${style.textColor} !important; + } + `); + } + if (style.isBold) { + cssRules.push(` + .nav-file-title[data-path^="${escapedPath}/"] { + font-weight: bold !important; + } + `); + } + if (style.isItalic) { + cssRules.push(` + .nav-file-title[data-path^="${escapedPath}/"] { + font-style: italic !important; + } + `); + } + if (typeof style.opacity === "number") { + cssRules.push(` + .nav-file-title[data-path^="${escapedPath}/"] { + opacity: ${style.opacity} !important; + } + .nav-file-title[data-path^="${escapedPath}/"]:hover { + opacity: ${Math.min(1, style.opacity + 0.15)} !important; + } + `); + } + } + if (style.backgroundColor) { + cssRules.push(` + .preview-item[data-path="${escapedPath}"] { + background-color: ${style.backgroundColor} !important; + ${style.textColor ? `color: ${style.textColor} !important;` : ""} + ${style.isBold ? "font-weight: bold !important;" : ""} + ${style.isItalic ? "font-style: italic !important;" : ""} + ${typeof style.opacity === "number" ? `opacity: ${style.opacity} !important;` : ""} + } + + /* Light mode: lighten on hover */ + body.theme-light .preview-item[data-path="${escapedPath}"]:hover { + background-color: color-mix(in srgb, white 20%, ${style.backgroundColor}) !important; + ${typeof style.opacity === "number" ? `opacity: ${Math.min(1, style.opacity + 0.15)} !important;` : ""} + } + + /* Dark mode: darken on hover */ + body.theme-dark .preview-item[data-path="${escapedPath}"]:hover { + background-color: color-mix(in srgb, black 20%, ${style.backgroundColor}) !important; + ${typeof style.opacity === "number" ? `opacity: ${Math.min(1, style.opacity + 0.15)} !important;` : ""} + } + `); + } + } + this.styleEl.textContent = cssRules.join("\n"); + } + cleanup() { + if (this.styleEl && this.styleEl.parentNode) { + this.styleEl.parentNode.removeChild(this.styleEl); + } + } +}; + +// src/modals/ColorSettingsModal.ts +var import_obsidian2 = require("obsidian"); +var ColorSettingsModal = class extends import_obsidian2.Modal { + constructor(app, plugin, filePath) { + super(app); + this.plugin = plugin; + this.filePath = filePath; + this.style = { ...plugin.settings.styles[filePath] || {} }; + } + onOpen() { + this.modalEl.addClass("color-folders-files-modal"); + const { contentEl } = this; + contentEl.empty(); + const previewSection = contentEl.createDiv("preview-section"); + this.previewEl = previewSection.createDiv("preview-item"); + this.previewEl.setAttribute("data-path", this.filePath); + const fileName = this.filePath.split("/").pop() || this.filePath; + this.previewEl.createSpan().setText(fileName); + this.updatePreview(); + const bgColorSetting = new import_obsidian2.Setting(contentEl).setName("Background color"); + const bgColorContainer = bgColorSetting.controlEl.createDiv("color-container"); + bgColorSetting.addColorPicker((color) => { + this.bgColorPicker = color; + color.setValue(this.style.backgroundColor || "#ffffff").onChange((value) => { + this.style.backgroundColor = value; + bgHexInput.value = value; + this.updatePreview(); + }); + }); + const bgHexInput = bgColorContainer.createEl("input", { + type: "text", + cls: "color-hex-input", + value: this.style.backgroundColor || "#ffffff" + }); + bgHexInput.addEventListener("change", () => { + const value = bgHexInput.value; + if (/^#[0-9A-Fa-f]{6}$/.test(value)) { + this.style.backgroundColor = value; + this.bgColorPicker.setValue(value); + this.updatePreview(); + } + }); + const textColorSetting = new import_obsidian2.Setting(contentEl).setName("Text color"); + const textColorContainer = textColorSetting.controlEl.createDiv("color-container"); + textColorSetting.addColorPicker((color) => { + this.textColorPicker = color; + color.setValue(this.style.textColor || "#000000").onChange((value) => { + this.style.textColor = value; + textHexInput.value = value; + this.updatePreview(); + }); + }); + const textHexInput = textColorContainer.createEl("input", { + type: "text", + cls: "color-hex-input", + value: this.style.textColor || "#000000" + }); + textHexInput.addEventListener("change", () => { + const value = textHexInput.value; + if (/^#[0-9A-Fa-f]{6}$/.test(value)) { + this.style.textColor = value; + this.textColorPicker.setValue(value); + this.updatePreview(); + } + }); + new import_obsidian2.Setting(contentEl).setName("Bold").addToggle((toggle) => { + this.boldToggle = toggle; + toggle.setValue(this.style.isBold || false).onChange((value) => { + this.style.isBold = value; + this.updatePreview(); + }); + }); + new import_obsidian2.Setting(contentEl).setName("Italic").addToggle((toggle) => { + this.italicToggle = toggle; + toggle.setValue(this.style.isItalic || false).onChange((value) => { + this.style.isItalic = value; + this.updatePreview(); + }); + }); + new import_obsidian2.Setting(contentEl).setName("Opacity").addSlider((slider) => { + this.opacitySlider = slider; + slider.setLimits(0, 1, 0.1).setValue(this.style.opacity || 1).onChange((value) => { + this.style.opacity = value; + this.updatePreview(); + }); + }); + new import_obsidian2.Setting(contentEl).setName("Apply to subfolders").addToggle((toggle) => { + this.subfolderToggle = toggle; + toggle.setValue(this.style.applyToSubfolders || false).onChange((value) => { + this.style.applyToSubfolders = value; + }); + }); + new import_obsidian2.Setting(contentEl).setName("Apply to files").addToggle((toggle) => { + this.filesToggle = toggle; + toggle.setValue(this.style.applyToFiles || false).onChange((value) => { + this.style.applyToFiles = value; + }); + }); + if (Object.keys(this.plugin.settings.presets).length > 0) { + new import_obsidian2.Setting(contentEl).setName("Apply preset").addDropdown((dropdown) => { + Object.keys(this.plugin.settings.presets).forEach((preset) => { + dropdown.addOption(preset, preset); + }); + return dropdown.onChange((value) => { + this.style = { ...this.plugin.settings.presets[value] }; + this.updateControls(); + this.updatePreview(); + }); + }); + } + let presetNameInput; + const presetSetting = new import_obsidian2.Setting(contentEl).setClass("preset-save").setName("Save as preset").addText((text) => { + presetNameInput = text; + text.setPlaceholder("Preset name"); + }).addButton((button) => button.setButtonText("Save").setCta().onClick(async () => { + const presetName = presetNameInput.getValue(); + if (presetName) { + if (this.plugin.settings.presets[presetName]) { + const shouldOverwrite = await this.plugin.confirmOverwritePreset(presetName); + if (!shouldOverwrite) + return; + } + this.plugin.settings.presets[presetName] = { ...this.style }; + await this.plugin.saveSettings(); + presetNameInput.setValue(""); + new import_obsidian2.Notice(`Preset "${presetName}" saved`); + this.onOpen(); + } + })); + const buttonSection = contentEl.createDiv("button-section"); + const removeButton = buttonSection.createEl("button", { + text: "Remove styling" + }); + removeButton.addEventListener("click", async () => { + delete this.plugin.settings.styles[this.filePath]; + await this.plugin.saveSettings(); + new import_obsidian2.Notice("Styling removed"); + this.close(); + }); + const resetButton = buttonSection.createEl("button", { + text: "Reset" + }); + resetButton.addEventListener("click", () => { + this.style = { ...DEFAULT_STYLE }; + this.updateControls(); + this.updatePreview(); + }); + const applyButton = buttonSection.createEl("button", { + text: "Apply", + cls: "mod-cta" + }); + applyButton.addEventListener("click", async () => { + await this.saveChanges(); + new import_obsidian2.Notice("Changes applied"); + this.close(); + }); + const closeButton = buttonSection.createEl("button", { + text: "Close" + }); + closeButton.addEventListener("click", () => { + this.close(); + }); + } + updateControls() { + if (this.bgColorPicker) + this.bgColorPicker.setValue(this.style.backgroundColor || "#ffffff"); + if (this.textColorPicker) + this.textColorPicker.setValue(this.style.textColor || "#000000"); + if (this.boldToggle) + this.boldToggle.setValue(this.style.isBold || false); + if (this.italicToggle) + this.italicToggle.setValue(this.style.isItalic || false); + if (this.opacitySlider) + this.opacitySlider.setValue(this.style.opacity || 1); + if (this.subfolderToggle) + this.subfolderToggle.setValue(this.style.applyToSubfolders || false); + if (this.filesToggle) + this.filesToggle.setValue(this.style.applyToFiles || false); + } + updatePreview() { + if (this.styleEl) { + this.styleEl.remove(); + } + this.styleEl = document.createElement("style"); + const rules = []; + if (this.style.backgroundColor) { + rules.push(` + .preview-item[data-path="${this.filePath}"] { + background-color: ${this.style.backgroundColor} !important; + transition: background-color 0.1s ease !important; + ${this.style.textColor ? `color: ${this.style.textColor} !important;` : ""} + ${this.style.isBold ? "font-weight: bold !important;" : ""} + ${this.style.isItalic ? "font-style: italic !important;" : ""} + ${typeof this.style.opacity === "number" ? `opacity: ${this.style.opacity} !important;` : ""} + } + + /* Light mode: lighten on hover */ + body.theme-light .preview-item[data-path="${this.filePath}"]:hover { + background-color: color-mix(in srgb, white 20%, ${this.style.backgroundColor}) !important; + ${typeof this.style.opacity === "number" ? `opacity: ${Math.min(1, this.style.opacity + 0.15)} !important;` : ""} + } + + /* Dark mode: darken on hover */ + body.theme-dark .preview-item[data-path="${this.filePath}"]:hover { + background-color: color-mix(in srgb, black 20%, ${this.style.backgroundColor}) !important; + ${typeof this.style.opacity === "number" ? `opacity: ${Math.min(1, this.style.opacity + 0.15)} !important;` : ""} + } + `); + } + this.styleEl.textContent = rules.join("\n"); + document.head.appendChild(this.styleEl); + } + async saveChanges() { + this.plugin.settings.styles[this.filePath] = this.style; + await this.plugin.saveSettings(); + } + onClose() { + if (this.styleEl) { + this.styleEl.remove(); + } + this.modalEl.removeClass("color-folders-files-modal"); + const { contentEl } = this; + contentEl.empty(); + } +}; + +// src/core/EventManager.ts +var EventManager = class { + constructor(app, plugin) { + this.app = app; + this.plugin = plugin; + this.activeModals = /* @__PURE__ */ new Set(); + } + registerEvents() { + this.plugin.registerEvent( + this.app.workspace.on("file-menu", (menu, file) => { + this.handleFileMenu(menu, file); + }) + ); + } + handleFileMenu(menu, file) { + menu.addItem((item) => { + item.setTitle("Customize appearance").setIcon("palette").onClick(() => { + this.activeModals.forEach((modal2) => modal2.close()); + this.activeModals.clear(); + const modal = new ColorSettingsModal(this.app, this.plugin, file.path); + this.activeModals.add(modal); + const originalClose = modal.close.bind(modal); + modal.close = () => { + this.activeModals.delete(modal); + originalClose(); + }; + modal.open(); + }); + }); + } + cleanup() { + this.activeModals.forEach((modal) => { + try { + modal.close(); + } catch (e) { + console.error("Error closing modal:", e); + } + }); + this.activeModals.clear(); + document.querySelectorAll(".color-folders-files-modal").forEach((el) => { + try { + el.remove(); + } catch (e) { + console.error("Error removing modal element:", e); + } + }); + } +}; + +// src/main.ts +var ColorFolderPlugin = class extends import_obsidian3.Plugin { + async onload() { + await this.loadSettings(); + this.styleManager = new StyleManager(); + this.eventManager = new EventManager(this.app, this); + this.eventManager.registerEvents(); + this.addSettingTab(new ColorSettingsTab(this.app, this)); + this.updateStyles(); + } + onunload() { + this.styleManager.cleanup(); + this.eventManager.cleanup(); + } + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + async saveSettings() { + await this.saveData(this.settings); + this.updateStyles(); + } + updateStyles() { + this.styleManager.updateStyles(this.settings.styles); + } + async confirmOverwritePreset(name) { + return new Promise((resolve) => { + const modal = new import_obsidian3.Modal(this.app); + modal.titleEl.setText("Overwrite preset"); + modal.contentEl.createEl("p", { + text: `A preset named "${name}" already exists. Do you want to overwrite it?` + }); + modal.contentEl.createDiv("modal-button-container", (buttonContainer) => { + buttonContainer.createEl("button", { text: "Cancel" }).addEventListener("click", () => { + modal.close(); + resolve(false); + }); + const confirmButton = buttonContainer.createEl("button", { + cls: "mod-cta", + text: "Overwrite" + }); + confirmButton.addEventListener("click", () => { + modal.close(); + resolve(true); + }); + }); + modal.open(); + }); + } +}; + +/* nosourcemap */ \ No newline at end of file diff --git a/.obsidian/plugins/color-folders-files/manifest.json b/.obsidian/plugins/color-folders-files/manifest.json new file mode 100644 index 0000000..6ebf006 --- /dev/null +++ b/.obsidian/plugins/color-folders-files/manifest.json @@ -0,0 +1,11 @@ +{ + "id": "color-folders-files", + "name": "Color Folders and Files", + "version": "1.4.1", + "minAppVersion": "1.4.1", + "description": "Customize the appearance of folders and files in the file explorer.", + "author": "Mithadon", + "authorUrl": "https://github.com/Mithadon", + "fundingUrl": "https://ko-fi.com/mithadon", + "isDesktopOnly": false +} diff --git a/.obsidian/plugins/color-folders-files/styles.css b/.obsidian/plugins/color-folders-files/styles.css new file mode 100644 index 0000000..28b59df --- /dev/null +++ b/.obsidian/plugins/color-folders-files/styles.css @@ -0,0 +1,274 @@ +/* Modal styles */ +.modal.color-folders-files-modal { + width: 350px !important; + max-width: 350px !important; + max-height: 80vh !important; +} + +/* Remove ALL Obsidian's default modal shadows */ +.modal.color-folders-files-modal, +.modal.color-folders-files-modal .modal-container, +.modal.color-folders-files-modal .modal-content, +.modal.color-folders-files-modal .modal-close-button, +.modal.color-folders-files-modal .modal-title, +.modal.color-folders-files-modal .vertical-tab-header, +.modal.color-folders-files-modal .vertical-tab-content-container { + box-shadow: none !important; +} + +.modal.color-folders-files-modal .preview-section { + margin: 20px 0; + padding: 15px; + border-radius: var(--radius-m); + background-color: var(--background-secondary); + border: 1px solid var(--background-modifier-border); +} + +.modal.color-folders-files-modal .preview-item { + margin: 10px 0; + padding: 8px 12px; + border-radius: var(--radius-s); + font-size: var(--font-ui-small); + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + transition: all 0.2s ease; +} + +/* Preview item style states */ +.preview-item.is-bold { + font-weight: bold; +} + +.preview-item.is-italic { + font-style: italic; +} + +/* Preview item custom properties */ +.preview-item { + background-color: var(--preview-bg-color, var(--background-primary)); + color: var(--preview-text-color, var(--text-normal)); + opacity: var(--preview-opacity, 1); +} + +/* Setting styles */ +.modal.color-folders-files-modal .setting-item { + border-top: none; + padding: var(--size-4-2) 0; +} + +.modal.color-folders-files-modal .setting-item-control { + gap: var(--size-4-2); +} + +/* Color picker and hex input styles */ +.color-container { + display: flex; + align-items: center; + gap: 8px; +} + +.color-hex-input { + width: 80px; + height: 24px; + padding: 0 4px; + border-radius: var(--radius-s); + border: 1px solid var(--background-modifier-border); + background-color: var(--background-primary); + color: var(--text-normal); + font-size: var(--font-ui-smaller); + font-family: var(--font-monospace); +} + +.color-hex-input:focus { + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px var(--background-modifier-border-focus); +} + +/* Button styles */ +.modal.color-folders-files-modal button.mod-cta { + background-color: var(--interactive-accent); + color: var(--text-on-accent); +} + +.modal.color-folders-files-modal button.mod-cta:hover { + background-color: var(--interactive-accent-hover); +} + +/* Slider styles */ +.modal.color-folders-files-modal .slider { + height: 4px; + background-color: var(--interactive-normal); + border-radius: var(--radius-s); +} + +.modal.color-folders-files-modal .slider::-webkit-slider-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background-color: var(--interactive-accent); + cursor: pointer; +} + +/* Dropdown styles */ +.modal.color-folders-files-modal select { + padding: var(--size-4-1) var(--size-4-2); + border-radius: var(--radius-s); + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + color: var(--text-normal); + font-size: var(--font-ui-small); + padding-right: 25px !important; + background-position: right 8px center; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: url('data:image/svg+xml;charset=US-ASCII,'); + background-repeat: no-repeat; + background-position: right 8px center; +} + +.modal.color-folders-files-modal select:focus { + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px var(--background-modifier-border-focus); +} + +/* Notice styles */ +.modal.color-folders-files-modal .notice { + background-color: var(--background-primary); + color: var(--text-normal); + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-m); + padding: var(--size-4-2) var(--size-4-3); + margin: var(--size-4-2) 0; +} + +/* Button section styles */ +.modal.color-folders-files-modal .button-section { + margin-top: 20px; + display: flex; + justify-content: center; + gap: 8px; +} + +.modal.color-folders-files-modal .button-section button { + padding: 6px 12px; + border-radius: var(--radius-s); + font-size: var(--font-ui-small); + background-color: var(--interactive-normal); + border: none; + color: var(--text-normal); + cursor: pointer; +} + +.modal.color-folders-files-modal .button-section button:hover { + background-color: var(--interactive-hover); +} + +/* Preset save section */ +.modal.color-folders-files-modal .setting-item.preset-save { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.modal.color-folders-files-modal .setting-item.preset-save .setting-item-info { + width: 100%; + text-align: center; +} + +.modal.color-folders-files-modal .setting-item.preset-save .setting-item-control { + width: 100%; + display: flex; + flex-direction: column; + gap: 8px; +} + +/* Settings tab preset styles */ +.presets-container { + display: flex; + flex-direction: column; + gap: 8px; +} + +.preset-container { + display: flex; + align-items: center; + gap: 8px; + padding: 4px; + cursor: grab; + border-radius: var(--radius-s); + transition: background-color 0.15s ease; +} + +.preset-container:hover { + background-color: var(--background-secondary); +} + +.preset-container.dragging { + opacity: 0.5; + background-color: var(--background-secondary); +} + +.drag-handle { + color: var(--text-muted); + font-family: var(--font-monospace); + user-select: none; + cursor: grab; + padding: 0 4px; +} + +/* Only apply fixed width to settings tab preview items */ +.vertical-tab-content .preview-item { + display: inline-block; + width: 300px; + padding: 4px 12px; + border-radius: var(--radius-s); + font-size: var(--font-ui-small); + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.preset-container .setting-item { + margin: 0; + padding: 0; + flex-grow: 0; + border: none; +} + +.preset-container .setting-item-control { + margin: 0; +} + +.preset-container button { + background: none; + border: none; + padding: 4px; + border-radius: var(--radius-s); +} + +.preset-container button:hover { + background-color: var(--background-modifier-hover); +} + +/* Import/Export buttons */ +.settings-import-export { + display: flex; + gap: 8px; + margin-top: 24px; +} + +.settings-import-export button { + flex: 1; + padding: var(--size-4-1) var(--size-4-3); + border-radius: var(--radius-s); + background-color: var(--interactive-normal); + color: var(--text-normal); + font-weight: var(--font-medium); +} + +.settings-import-export button:hover { + background-color: var(--interactive-hover); +} diff --git a/.obsidian/plugins/file-explorer-note-count/main.js b/.obsidian/plugins/file-explorer-note-count/main.js new file mode 100644 index 0000000..e17d756 --- /dev/null +++ b/.obsidian/plugins/file-explorer-note-count/main.js @@ -0,0 +1,973 @@ +'use strict'; + +var obsidian = require('obsidian'); + +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}; + +// 'path' module extracted from Node.js v8.11.1 (only the posix part) + +function assertPath(path) { + if (typeof path !== 'string') { + throw new TypeError('Path must be a string. Received ' + JSON.stringify(path)); + } +} + +// Resolves . and .. elements in a path with directory names +function normalizeStringPosix(path, allowAboveRoot) { + var res = ''; + var lastSegmentLength = 0; + var lastSlash = -1; + var dots = 0; + var code; + for (var i = 0; i <= path.length; ++i) { + if (i < path.length) + code = path.charCodeAt(i); + else if (code === 47 /*/*/) + break; + else + code = 47 /*/*/; + if (code === 47 /*/*/) { + if (lastSlash === i - 1 || dots === 1) ; else if (lastSlash !== i - 1 && dots === 2) { + if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 /*.*/ || res.charCodeAt(res.length - 2) !== 46 /*.*/) { + if (res.length > 2) { + var lastSlashIndex = res.lastIndexOf('/'); + if (lastSlashIndex !== res.length - 1) { + if (lastSlashIndex === -1) { + res = ''; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf('/'); + } + lastSlash = i; + dots = 0; + continue; + } + } else if (res.length === 2 || res.length === 1) { + res = ''; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + if (res.length > 0) + res += '/..'; + else + res = '..'; + lastSegmentLength = 2; + } + } else { + if (res.length > 0) + res += '/' + path.slice(lastSlash + 1, i); + else + res = path.slice(lastSlash + 1, i); + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === 46 /*.*/ && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; +} + +function _format(sep, pathObject) { + var dir = pathObject.dir || pathObject.root; + var base = pathObject.base || (pathObject.name || '') + (pathObject.ext || ''); + if (!dir) { + return base; + } + if (dir === pathObject.root) { + return dir + base; + } + return dir + sep + base; +} + +var posix = { + // path.resolve([from ...], to) + resolve: function resolve() { + var resolvedPath = ''; + var resolvedAbsolute = false; + var cwd; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path; + if (i >= 0) + path = arguments[i]; + else { + if (cwd === undefined) + cwd = process.cwd(); + path = cwd; + } + + assertPath(path); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charCodeAt(0) === 47 /*/*/; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute); + + if (resolvedAbsolute) { + if (resolvedPath.length > 0) + return '/' + resolvedPath; + else + return '/'; + } else if (resolvedPath.length > 0) { + return resolvedPath; + } else { + return '.'; + } + }, + + normalize: function normalize(path) { + assertPath(path); + + if (path.length === 0) return '.'; + + var isAbsolute = path.charCodeAt(0) === 47 /*/*/; + var trailingSeparator = path.charCodeAt(path.length - 1) === 47 /*/*/; + + // Normalize the path + path = normalizeStringPosix(path, !isAbsolute); + + if (path.length === 0 && !isAbsolute) path = '.'; + if (path.length > 0 && trailingSeparator) path += '/'; + + if (isAbsolute) return '/' + path; + return path; + }, + + isAbsolute: function isAbsolute(path) { + assertPath(path); + return path.length > 0 && path.charCodeAt(0) === 47 /*/*/; + }, + + join: function join() { + if (arguments.length === 0) + return '.'; + var joined; + for (var i = 0; i < arguments.length; ++i) { + var arg = arguments[i]; + assertPath(arg); + if (arg.length > 0) { + if (joined === undefined) + joined = arg; + else + joined += '/' + arg; + } + } + if (joined === undefined) + return '.'; + return posix.normalize(joined); + }, + + relative: function relative(from, to) { + assertPath(from); + assertPath(to); + + if (from === to) return ''; + + from = posix.resolve(from); + to = posix.resolve(to); + + if (from === to) return ''; + + // Trim any leading backslashes + var fromStart = 1; + for (; fromStart < from.length; ++fromStart) { + if (from.charCodeAt(fromStart) !== 47 /*/*/) + break; + } + var fromEnd = from.length; + var fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + var toStart = 1; + for (; toStart < to.length; ++toStart) { + if (to.charCodeAt(toStart) !== 47 /*/*/) + break; + } + var toEnd = to.length; + var toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + var length = fromLen < toLen ? fromLen : toLen; + var lastCommonSep = -1; + var i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === 47 /*/*/) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } else if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === 47 /*/*/) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo'; to='/' + lastCommonSep = 0; + } + } + break; + } + var fromCode = from.charCodeAt(fromStart + i); + var toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) + break; + else if (fromCode === 47 /*/*/) + lastCommonSep = i; + } + + var out = ''; + // Generate the relative path based on the path difference between `to` + // and `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === 47 /*/*/) { + if (out.length === 0) + out += '..'; + else + out += '/..'; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) + return out + to.slice(toStart + lastCommonSep); + else { + toStart += lastCommonSep; + if (to.charCodeAt(toStart) === 47 /*/*/) + ++toStart; + return to.slice(toStart); + } + }, + + _makeLong: function _makeLong(path) { + return path; + }, + + dirname: function dirname(path) { + assertPath(path); + if (path.length === 0) return '.'; + var code = path.charCodeAt(0); + var hasRoot = code === 47 /*/*/; + var end = -1; + var matchedSlash = true; + for (var i = path.length - 1; i >= 1; --i) { + code = path.charCodeAt(i); + if (code === 47 /*/*/) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) return hasRoot ? '/' : '.'; + if (hasRoot && end === 1) return '//'; + return path.slice(0, end); + }, + + basename: function basename(path, ext) { + if (ext !== undefined && typeof ext !== 'string') throw new TypeError('"ext" argument must be a string'); + assertPath(path); + + var start = 0; + var end = -1; + var matchedSlash = true; + var i; + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) return ''; + var extIdx = ext.length - 1; + var firstNonSlashEnd = -1; + for (i = path.length - 1; i >= 0; --i) { + var code = path.charCodeAt(i); + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) end = firstNonSlashEnd;else if (end === -1) end = path.length; + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) return ''; + return path.slice(start, end); + } + }, + + extname: function extname(path) { + assertPath(path); + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + for (var i = path.length - 1; i >= 0; --i) { + var code = path.charCodeAt(i); + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46 /*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { + return ''; + } + return path.slice(startDot, end); + }, + + format: function format(pathObject) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject); + } + return _format('/', pathObject); + }, + + parse: function parse(path) { + assertPath(path); + + var ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) return ret; + var code = path.charCodeAt(0); + var isAbsolute = code === 47 /*/*/; + var start; + if (isAbsolute) { + ret.root = '/'; + start = 1; + } else { + start = 0; + } + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + var i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + code = path.charCodeAt(i); + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46 /*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i;else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) ret.base = ret.name = path.slice(1, end);else ret.base = ret.name = path.slice(startPart, end); + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot); + ret.base = path.slice(1, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } + + if (startPart > 0) ret.dir = path.slice(0, startPart - 1);else if (isAbsolute) ret.dir = '/'; + + return ret; + }, + + sep: '/', + delimiter: ':', + win32: null, + posix: null +}; + +posix.posix = posix; + +var pathBrowserify = posix; + +const withSubfolderClass = 'oz-with-subfolder'; +const showAllNumbersClass = 'oz-show-all-num'; +const isFolder = (item) => item.file instanceof obsidian.TFolder; +const iterateItems = (items, callback) => { + for (const key in items) { + if (!Object.prototype.hasOwnProperty.call(items, key)) + continue; + callback(items[key]); + } +}; +const getParentPath = (src) => { + if (src === '/') + return null; + const path = pathBrowserify.dirname(src); + if (path === '.') + return '/'; + else + return path; +}; +const equals = (arr1, arr2) => { + // if the other array is a falsy value, return + if (!Array.isArray(arr1) || !Array.isArray(arr2)) + return false; + // compare lengths - can save a lot of time + if (arr1.length != arr2.length) + return false; + return arr1.every((v, i) => v === arr2[i]); +}; +const isParent = (parent, child) => { + if (child === parent) + return false; + if (parent === '/') + parent = ''; + if (child === '/') + child = ''; + const parentTokens = parent.split('/').filter((i) => i.length); + return parentTokens.every((t, i) => child.split('/')[i] === t); +}; +// Helper to play with the File Explorer (if exists) +const doWithFileExplorer = (plugin, callback) => { + let leaves; + let count = 0; + const tryGetView = () => { + leaves = plugin.app.workspace.getLeavesOfType('file-explorer'); + if (leaves.length === 0) { + if (count++ > 5) + console.error('failed to get file-explorer'); + else { + console.log('file-explorer not found, retrying...'); + setTimeout(tryGetView, 500); + } + } + else { + if (leaves.length > 1) + console.warn('more then one file-explorer'); + callback(leaves[0].view); + } + }; + tryGetView(); +}; + +function around(obj, factories) { + const removers = Object.keys(factories).map(key => around1(obj, key, factories[key])); + return removers.length === 1 ? removers[0] : function () { removers.forEach(r => r()); }; +} +function around1(obj, method, createWrapper) { + const original = obj[method], hadOwn = obj.hasOwnProperty(method); + let current = createWrapper(original); + // Let our wrapper inherit static props from the wrapping method, + // and the wrapping method, props from the original method + if (original) + Object.setPrototypeOf(current, original); + Object.setPrototypeOf(wrapper, current); + obj[method] = wrapper; + // Return a callback to allow safe removal + return remove; + function wrapper(...args) { + // If we have been deactivated and are no longer wrapped, remove ourselves + if (current === original && obj[method] === wrapper) + remove(); + return current.apply(this, args); + } + function remove() { + // If no other patches, just do a direct removal + if (obj[method] === wrapper) { + if (hadOwn) + obj[method] = original; + else + delete obj[method]; + } + if (current === original) + return; + // Else pass future calls through, and remove wrapper from the prototype chain + current = original; + Object.setPrototypeOf(wrapper, original || Function); + } +} + +const countFolderChildren = (folder, filter) => { + let count = 0; + for (const af of folder.children) { + if (filter(af)) + count++; + if (af instanceof obsidian.TFolder) + count += countFolderChildren(af, filter); + } + return count; +}; +/** filter out all path that is the parent of existing path */ +const filterParent = (pathList) => { + const list = Array.from(pathList); + list.sort(); + for (let i = 0; i < list.length; i++) { + if (i < list.length - 1 && (list[i] === list[i + 1] || isParent(list[i], list[i + 1]))) { + list.shift(); + i--; + } + } + return new Set(list); +}; +/** get all parents and add to set if not exist */ +const getAllParents = (path, set) => { + let parent = getParentPath(path); + while (parent && !set.has(parent)) { + set.add(parent); + parent = getParentPath(parent); + } +}; +/** + * Update folder count of target's parent + */ +const updateCount = (targetList, plugin) => { + const set = filterParent(targetList); + for (const path of targetList) { + getAllParents(path, set); + } + // set count of path + const { fileExplorer, fileFilter } = plugin; + if (!fileExplorer) { + console.error('fileExplorer missing'); + return; + } + for (const path of set) { + // check if path available + if (!fileExplorer.fileItems[path]) + continue; + setCount(fileExplorer.fileItems[path], fileFilter); + } + // Update root separately + if (plugin.rootFolderEl && plugin.settings.addRootFolder) { + setupRootCount(plugin); + } + // empty waitingList + targetList.length = 0; +}; +const setupRootCount = (plugin) => { + if (plugin.rootFolderEl) { + let rootFolderElChildren = plugin.rootFolderEl.children; + if (rootFolderElChildren && rootFolderElChildren.length > 0) { + let totalCount = countFolderChildren(plugin.app.vault.getAbstractFileByPath('/'), plugin.fileFilter); + rootFolderElChildren[0].setAttr('data-count', totalCount.toString()); + } + } +}; +const setupCount = (plugin, revert = false) => { + if (!plugin.fileExplorer) + throw new Error('fileExplorer not found'); + // For each setup, first setup the root folder + plugin.setupRootFolder(); + setupRootCount(plugin); + // Iterate other items and include new counts + iterateItems(plugin.fileExplorer.fileItems, (item) => { + if (!isFolder(item)) + return; + if (revert) + removeCount(item); + else + setCount(item, plugin.fileFilter); + }); +}; +const setCount = (item, filter) => { + // if (item.file.isRoot()) return; + const count = countFolderChildren(item.file, filter); + item.selfEl.dataset['count'] = count.toString(); + item.selfEl.toggleClass(withSubfolderClass, Array.isArray(item.file.children) && item.file.children.some((af) => af instanceof obsidian.TFolder)); +}; +const removeCount = (item) => { + if (item.selfEl.dataset['count']) + delete item.selfEl.dataset['count']; + item.selfEl.removeClass(withSubfolderClass); +}; + +class VaultHandler { + get app() { + return this.plugin.app; + } + get vault() { + return this.plugin.app.vault; + } + constructor(plugin) { + this.waitingList = []; + this.update = obsidian.debounce(() => updateCount(this.waitingList, this.plugin), 500, true); + this.handler = (...args) => { + var _a; + for (const arg of args) { + const path = arg instanceof obsidian.TAbstractFile ? arg.path : arg; + this.waitingList.push((_a = getParentPath(path)) !== null && _a !== void 0 ? _a : '/'); + } + this.update(); + }; + this.registerVaultEvent = () => { + this.plugin.registerEvent(this.vault.on('create', this.handler)); + this.plugin.registerEvent(this.vault.on('rename', this.handler)); + this.plugin.registerEvent(this.vault.on('delete', this.handler)); + }; + this.plugin = plugin; + } +} + +const DEFAULT_SETTINGS = { + showAllNumbers: false, + filterList: ['md'], + blacklist: false, + addRootFolder: false, +}; +class FENoteCountSettingTab extends obsidian.PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + get showOnlyNoteValue() { + const { settings } = this.plugin; + return settings.blacklist === DEFAULT_SETTINGS.blacklist && equals(settings.filterList, DEFAULT_SETTINGS.filterList); + } + set showOnlyNoteValue(value) { + const { blacklist, filterList } = DEFAULT_SETTINGS; + this.plugin.settings.blacklist = blacklist; + if (value) { + // do deep copy + this.plugin.settings.filterList = Array.from(filterList); + } + else { + this.plugin.settings.filterList.length = 0; + } + } + display() { + let { containerEl } = this; + containerEl.empty(); + containerEl.createEl('h2', { + text: 'File Explorer Note Count Settings', + }); + new obsidian.Setting(containerEl) + .setName('Show All Numbers') + .setDesc('Turn on this option if you want to see the number of notes even after you expand the collapsed folders') + .addToggle((toggle) => toggle.setValue(this.plugin.settings.showAllNumbers).onChange((value) => { + document.body.toggleClass('oz-show-all-num', value); + this.plugin.settings.showAllNumbers = value; + this.plugin.saveSettings(); + })); + new obsidian.Setting(containerEl) + .setName('Add Root Folder') + .setDesc('By default, there is no root folder provided by Obsidian. It is moved to drop-down menu to switch between vaults. ' + + 'Enable this option if you want to see root folder and its count in the file explorer') + .addToggle((toggle) => toggle.setValue(this.plugin.settings.addRootFolder).onChange((value) => { + this.plugin.settings.addRootFolder = value; + this.plugin.saveSettings(); + this.plugin.reloadCount(); + })); + this.filterOpt(); + } + filterOpt() { + new obsidian.Setting(this.containerEl) + .setName('Show Only Markdown Notes') + .setDesc('Turn off this option to choose file that should be counted') + .addToggle((toggle) => toggle.setValue(this.showOnlyNoteValue).onChange((value) => { + this.showOnlyNoteValue = value; + this.plugin.reloadCount(); + this.plugin.saveSettings(); + this.display(); + })); + if (!this.showOnlyNoteValue) { + new obsidian.Setting(this.containerEl) + .setName('Filter List') + .setDesc(createFragment((descEl) => { + descEl.appendText('Extension list to include/exclude file during counting'); + descEl.appendChild(document.createElement('br')); + descEl.appendText('Separated by comma'); + })) + .addTextArea((text) => { + const onChange = (value) => __awaiter(this, void 0, void 0, function* () { + const list = value.split(',').map((v) => v.trim()); + this.plugin.settings.filterList = list; + this.plugin.reloadCount(); + yield this.plugin.saveSettings(); + }); + text.setPlaceholder('Leave it empty to count all types of files'); + text.setValue(this.plugin.settings.filterList.join(', ')).onChange(obsidian.debounce(onChange, 500, true)); + text.inputEl.rows = 2; + text.inputEl.cols = 25; + }); + new obsidian.Setting(this.containerEl) + .setName('Enable Blacklist') + .setDesc('Turn on this option to use Filter List to exclude files') + .addToggle((toggle) => toggle.setValue(this.plugin.settings.blacklist).onChange((value) => { + this.plugin.settings.blacklist = value; + this.plugin.reloadCount(); + this.plugin.saveSettings(); + })); + } + } +} + +class FileExplorerNoteCount extends obsidian.Plugin { + constructor() { + super(...arguments); + this.settings = DEFAULT_SETTINGS; + this.vaultHandler = new VaultHandler(this); + this.rootFolderEl = null; + this.explorerNavHeaderSelector = '.workspace-leaf-content[data-type="file-explorer"] .nav-header'; + this.rootFolderClassName = 'oz-explorer-root-folder'; + this.initialize = (revert = false) => { + let plugin = this; + // First Check if the root folder exists + let explorerHeaderEl = document.querySelector(`${this.explorerNavHeaderSelector} .${this.rootFolderClassName}`); + if (explorerHeaderEl) + this.rootFolderEl = explorerHeaderEl; + const getViewHandler = (revert) => (view) => { + this.fileExplorer = view; + setupCount(this, revert); + this.setupRootFolder(revert); + if (!revert) { + this.registerEvent(this.app.workspace.on('css-change', this.setupRootFolder)); + this.vaultHandler.registerVaultEvent(); + if (this.settings.showAllNumbers) + document.body.addClass('oz-show-all-num'); + } + else { + for (const el of document.getElementsByClassName(withSubfolderClass)) { + el.removeClass(withSubfolderClass); + } + document.body.removeClass(showAllNumbersClass); + } + if (!revert) { + // when file explorer is closed (workspace changed) + // try to update fehanlder with new file explorer instance + this.register(around(view, { + onClose: (next) => function () { + setTimeout(() => doWithFileExplorer(plugin, getViewHandler(false)), 1e3); + return next.apply(this); + }, + })); + } + }; + doWithFileExplorer(plugin, getViewHandler(revert)); + }; + this.setupRootFolder = (revert = false) => { + var _a, _b, _c; + if (!this.fileExplorer) { + console.error('file-explorer not found'); + return; + } + if (this.rootFolderEl && !this.settings.addRootFolder) { + this.rootFolderEl.remove(); + this.rootFolderEl = null; + } + // Check if root is provided by Obsidian (it shouldn't be in the new releases) + const root = (_c = (_b = (_a = this.fileExplorer) === null || _a === void 0 ? void 0 : _a.fileItems) === null || _b === void 0 ? void 0 : _b['/']) !== null && _c !== void 0 ? _c : null; + if (!root) { + // Get the Nav Header + let explorerHeaderEl = document.querySelector(this.explorerNavHeaderSelector); + if (!explorerHeaderEl) + return; + if (!this.rootFolderEl && this.settings.addRootFolder) { + this.rootFolderEl = explorerHeaderEl.createEl('div', { + cls: ['tree-item', 'nav-folder', this.rootFolderClassName], + }); + let rootTitle = this.rootFolderEl.createEl('div', { + cls: ['oz-explorer-root-nav-folder-title'], + attr: { 'data-path': '/' }, + }); + let titleContent = rootTitle.createEl('div', { + cls: ['tree-item-inner', 'nav-folder-title-content'], + }); + titleContent.textContent = this.app.vault.getName(); + } + } + }; + } + onload() { + return __awaiter(this, void 0, void 0, function* () { + console.log('loading FileExplorerNoteCount'); + this.addSettingTab(new FENoteCountSettingTab(this.app, this)); + yield this.loadSettings(); + this.app.workspace.onLayoutReady(this.initialize); + }); + } + onunload() { + console.log('unloading FileExplorerNoteCount'); + this.initialize(true); + } + loadSettings() { + return __awaiter(this, void 0, void 0, function* () { + this.settings = Object.assign(Object.assign({}, this.settings), (yield this.loadData())); + }); + } + saveSettings() { + return __awaiter(this, void 0, void 0, function* () { + yield this.saveData(this.settings); + }); + } + reloadCount() { + setupCount(this); + } + get fileFilter() { + let list = this.settings.filterList; + return (af) => { + if (af instanceof obsidian.TFile) { + const { extension: target } = af; + // if list is empty, filter nothing + if (list.length === 0) + return true; + else if (this.settings.blacklist) + return !list.includes(target); + else + return list.includes(target); + } + else + return false; + }; + } +} + +module.exports = FileExplorerNoteCount; + +/* nosourcemap */ \ No newline at end of file diff --git a/.obsidian/plugins/file-explorer-note-count/manifest.json b/.obsidian/plugins/file-explorer-note-count/manifest.json new file mode 100644 index 0000000..faf897f --- /dev/null +++ b/.obsidian/plugins/file-explorer-note-count/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "file-explorer-note-count", + "name": "File Explorer Note Count", + "version": "1.2.4", + "minAppVersion": "1.2.0", + "description": "The plugin helps you to see the number of notes under each folder within the file explorer.", + "author": "Ozan Tellioglu", + "authorUrl": "https://www.ozan.pl", + "isDesktopOnly": false +} diff --git a/.obsidian/plugins/file-explorer-note-count/styles.css b/.obsidian/plugins/file-explorer-note-count/styles.css new file mode 100644 index 0000000..07a579f --- /dev/null +++ b/.obsidian/plugins/file-explorer-note-count/styles.css @@ -0,0 +1,34 @@ +.nav-folder-title[data-count]::after { + content: attr(data-count); + display: inline-block; + position: relative; + font-size: calc(100% * 0.8); + margin-right: 4px; + /* border-radius: 3px; */ + padding: 2px 0; + /* background-color: var(--background-secondary-alt); */ + transition: opacity 100ms ease-in-out; +} + +.oz-explorer-root-nav-folder-title { + display: flex; +} + +.oz-explorer-root-nav-folder-title[data-count]::after { + content: attr(data-count); + margin-right: 4px; + font-size: calc(100% * 0.8); + display: inline-block; +} + +body:not(.oz-show-all-num) .nav-folder:not(.is-collapsed) > .nav-folder-title.oz-with-subfolder[data-count]:not([data-path='/'])::after { + opacity: 0; +} + +.nav-folder-title-content { + flex-grow: 1; +} + +.oz-explorer-root-folder { + margin-top: 15px; +} diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index abfb8a4..a52f8a9 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -11,10 +11,14 @@ "id": "4eef998ab0601fdb", "type": "leaf", "state": { - "type": "empty", - "state": {}, + "type": "markdown", + "state": { + "file": "100.Orga/Studienfortschritt.md", + "mode": "source", + "source": false + }, "icon": "lucide-file", - "title": "New tab" + "title": "Studienfortschritt" } } ] @@ -105,6 +109,7 @@ "state": { "type": "backlink", "state": { + "file": "100.Orga/Studienfortschritt.md", "collapseAll": false, "extraContext": false, "sortOrder": "alphabetical", @@ -172,7 +177,7 @@ "state": { "type": "outline", "state": { - "file": "10. Grundlagen technischer Systeme/Untitled.md", + "file": "100.Orga/Studienfortschritt.md", "followCursor": false, "showSearch": false, "searchQuery": "" @@ -221,13 +226,13 @@ "table-editor-obsidian:Advanced Tables Toolbar": false } }, - "active": "42ae838cdd906d75", + "active": "4eef998ab0601fdb", "lastOpenFiles": [ - "10. Grundlagen technischer Systeme/Angewandte Physik für technische Systeme", + "10. Grundlagen technischer Systeme/Mathematische Grundlagen und Algebra/Überblick mathematische Grundlagen und Algebra.md", "10. Grundlagen technischer Systeme/Untitled.md", + "10. Grundlagen technischer Systeme/Angewandte Physik für technische Systeme", "100.Orga/adsad.md", "100.Orga/Studienfortschritt.md", - "10. Grundlagen technischer Systeme/Mathematische Grundlagen und Algebra/Überblick mathematische Grundlagen und Algebra.md", "10. Grundlagen technischer Systeme/Logik und Analysis", "100.Orga/Untitled", "50. Spezialisierung", @@ -236,7 +241,6 @@ "20. Informatische Lösungen gestalten", "Untitled.base", "00.Assets", - "100.Orga", - "01-grundlagen-technischer-systeme/Mathematische Grundlagen und Algebra/Untitled" + "100.Orga" ] } \ No newline at end of file