kazi-aidah_sidecards/main.js
2026-05-17 02:03:05 +06:00

7537 lines
295 KiB
JavaScript

/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
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/utils/dom.ts
var dom_exports = {};
__export(dom_exports, {
applyCardColorToElement: () => applyCardColorToElement,
hexToRgba: () => hexToRgba,
resolveAutoColor: () => resolveAutoColor,
resolveColorVarToHex: () => resolveColorVarToHex
});
function hexToRgba(hex, alpha = 1) {
if (!hex)
return "";
const h = hex.replace("#", "").trim();
let r, g, b;
if (h.length === 3) {
r = parseInt(h[0] + h[0], 16);
g = parseInt(h[1] + h[1], 16);
b = parseInt(h[2] + h[2], 16);
} else if (h.length === 6) {
r = parseInt(h.substring(0, 2), 16);
g = parseInt(h.substring(2, 4), 16);
b = parseInt(h.substring(4, 6), 16);
} else {
return hex;
}
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
function resolveColorVarToHex(colorVar, settings) {
if (!colorVar)
return null;
if (colorVar.startsWith("#"))
return colorVar;
const m = colorVar.match(/--card-color-(\d+)/);
if (m) {
try {
const root = window && window.getComputedStyle ? window.getComputedStyle(document.documentElement) : null;
if (root) {
const val = root.getPropertyValue(`--card-color-${m[1]}`);
if (val) {
const v = String(val).trim();
if (v)
return v;
}
}
} catch (e) {
}
const key = `color${m[1]}`;
return settings[key] || null;
}
return null;
}
function applyCardColorToElement(cardEl, colorVar, settings) {
var _a, _b, _c, _d;
const style = Number((_a = settings.cardStyle) != null ? _a : 2);
const opacity = Number((_b = settings.cardBgOpacity) != null ? _b : 0.08);
const borderThickness = Number((_c = settings.borderThickness) != null ? _c : 2);
const borderShadowOpacity = Number((_d = settings.cardBorderShadowOpacity) != null ? _d : 1);
cardEl.style.removeProperty("border-left");
cardEl.style.removeProperty("border");
cardEl.style.removeProperty("background-color");
cardEl.style.removeProperty("box-shadow");
cardEl.style.removeProperty("max-height");
cardEl.style.removeProperty("overflow");
const hex = resolveColorVarToHex(colorVar, settings) || colorVar;
const rgba = hexToRgba(hex, opacity);
const borderColor = borderShadowOpacity >= 1 ? colorVar : hexToRgba(hex, borderShadowOpacity);
if (style === 1) {
cardEl.setCssProps({
"border": `${borderThickness}px solid ${borderColor}`,
"background-color": rgba
});
} else if (style === 3) {
cardEl.setCssProps({
"border-left": `${borderThickness}px solid ${borderColor}`,
"background-color": rgba,
"border-top": `1px solid var(--background-modifier-border)`,
"border-right": `1px solid var(--background-modifier-border)`,
"border-bottom": `1px solid var(--background-modifier-border)`
});
} else {
cardEl.setCssProps({
"border": `${borderThickness}px solid ${borderColor}`,
"background-color": rgba,
"box-shadow": `2px 2px 0 0 ${borderColor}`
});
}
}
function resolveAutoColor(content, tags, settings) {
const rules = settings.autoColorRules;
if (!rules || rules.length === 0)
return null;
const lowerContent = content.toLowerCase();
const lowerTags = tags.map((t) => t.toLowerCase().replace(/^#/, ""));
for (const rule of rules) {
if (!rule.match)
continue;
const match = rule.match.toLowerCase().replace(/^#/, "");
if (rule.type === "tag") {
if (lowerTags.some((t) => t === match || t.includes(match))) {
return `var(--card-color-${rule.colorIndex})`;
}
} else {
if (lowerContent.includes(match)) {
return `var(--card-color-${rule.colorIndex})`;
}
}
}
return null;
}
var init_dom = __esm({
"src/utils/dom.ts"() {
"use strict";
}
});
// src/models/Card.ts
var Card_exports = {};
__export(Card_exports, {
Card: () => Card,
Status: () => Status
});
var Status, Card;
var init_Card = __esm({
"src/models/Card.ts"() {
"use strict";
Status = /* @__PURE__ */ ((Status2) => {
Status2["TODO"] = "todo";
Status2["IN_PROGRESS"] = "in-progress";
Status2["DONE"] = "done";
return Status2;
})(Status || {});
Card = class {
constructor(data) {
this.id = data.id || Math.random().toString(36).slice(2, 9);
this.content = data.content || "";
this.color = data.color || "var(--card-color-1)";
this.tags = data.tags || [];
this.category = data.category || null;
this.created = data.created || Date.now();
this.modified = data.modified || this.created;
this.archived = data.archived || false;
this.pinned = data.pinned || false;
this.notePath = data.notePath || null;
this.expiresAt = data.expiresAt || null;
this.status = data.status || null;
}
clone() {
return new Card({
...this,
id: Math.random().toString(36).slice(2, 9),
created: Date.now()
});
}
toJSON() {
return {
id: this.id,
content: this.content,
color: this.color,
tags: this.tags,
category: this.category,
created: this.created,
modified: this.modified,
archived: this.archived,
pinned: this.pinned,
notePath: this.notePath,
expiresAt: this.expiresAt,
status: this.status
};
}
};
}
});
// src/core/Plugin.ts
var Plugin_exports = {};
__export(Plugin_exports, {
default: () => SideCardsPlugin
});
module.exports = __toCommonJS(Plugin_exports);
var import_obsidian10 = require("obsidian");
// src/views/CardSidebarView.ts
var import_obsidian4 = require("obsidian");
// src/views/components/Card.ts
init_dom();
var import_obsidian3 = require("obsidian");
// src/views/modals/DateTimeModal.ts
var import_obsidian = require("obsidian");
var DateTimeModal = class extends import_obsidian.Modal {
constructor(app, card, store) {
super(app);
this.card = card;
this.store = store;
this.mode = "relative";
this.slots = [];
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("sc-datetime-modal");
this.titleEl.setText("Set expiry");
const modeRow = contentEl.createDiv("sc-dt-mode-row");
const relLabel = modeRow.createEl("label", { cls: "sc-dt-radio-label" });
const relRadio = relLabel.createEl("input", { type: "radio" });
relRadio.name = "sc-dt-mode";
relRadio.value = "relative";
relRadio.checked = true;
relLabel.createSpan({ text: "Relative duration" });
const exactLabel = modeRow.createEl("label", { cls: "sc-dt-radio-label" });
const exactRadio = exactLabel.createEl("input", { type: "radio" });
exactRadio.name = "sc-dt-mode";
exactRadio.value = "exact";
exactLabel.createSpan({ text: "Exact date & time" });
const relPanel = contentEl.createDiv("sc-dt-panel sc-dt-panel-relative");
this.slotsContainer = relPanel.createDiv("sc-dt-slots");
this.addSlot();
const addSlotBtn = relPanel.createEl("button", { text: "Add time unit", cls: "sc-dt-add-slot-btn" });
addSlotBtn.addEventListener("click", () => this.addSlot());
const presetsRow = relPanel.createDiv("sc-dt-presets");
const presets = [
{ label: "5 min", minutes: 5 },
{ label: "30 min", minutes: 30 },
{ label: "1 hour", minutes: 60 }
];
presets.forEach((p) => {
const btn = presetsRow.createEl("button", { text: p.label, cls: "sc-dt-preset-btn" });
btn.addEventListener("click", () => {
this.slots = [];
this.slotsContainer.empty();
if (p.minutes < 60) {
this.addSlot(p.minutes, "minutes");
} else {
this.addSlot(p.minutes / 60, "hours");
}
});
});
const exactPanel = contentEl.createDiv("sc-dt-panel sc-dt-panel-exact");
exactPanel.addClass("sc-hidden");
const exactInput = exactPanel.createEl("input", { type: "datetime-local", cls: "sc-dt-exact-input" });
if (this.card.expiresAt) {
const d = new Date(this.card.expiresAt);
exactInput.value = this.toInputValue(d);
}
const quickRow = exactPanel.createDiv("sc-dt-quick-row");
const todayMidnightBtn = quickRow.createEl("button", { text: "Today midnight", cls: "sc-dt-quick-btn" });
todayMidnightBtn.addEventListener("click", () => {
const d = new Date();
d.setHours(23, 59, 0, 0);
exactInput.value = this.toInputValue(d);
});
relRadio.addEventListener("change", () => {
if (relRadio.checked) {
this.mode = "relative";
relPanel.removeClass("sc-hidden");
exactPanel.addClass("sc-hidden");
}
});
exactRadio.addEventListener("change", () => {
if (exactRadio.checked) {
this.mode = "exact";
relPanel.addClass("sc-hidden");
exactPanel.removeClass("sc-hidden");
}
});
const actionsRow = contentEl.createDiv("sc-dt-actions-row");
const cancelBtn = actionsRow.createEl("button", { text: "Cancel", cls: "sc-dt-cancel-btn" });
cancelBtn.addEventListener("click", () => this.close());
const saveBtn = actionsRow.createEl("button", { text: "Save", cls: "sc-dt-save-btn mod-cta" });
saveBtn.addEventListener("click", () => {
void (async () => {
let expiresAt = null;
if (this.mode === "relative") {
let totalMs = 0;
this.slots.forEach((s) => {
const val = Number(s.numInput.value) || 0;
const unit = s.el.querySelector("select").value;
if (unit === "seconds")
totalMs += val * 1e3;
else if (unit === "minutes")
totalMs += val * 60 * 1e3;
else if (unit === "hours")
totalMs += val * 60 * 60 * 1e3;
else if (unit === "days")
totalMs += val * 24 * 60 * 60 * 1e3;
else if (unit === "weeks")
totalMs += val * 7 * 24 * 60 * 60 * 1e3;
});
if (totalMs > 0)
expiresAt = Date.now() + totalMs;
} else {
const val = exactInput.value;
if (val)
expiresAt = new Date(val).getTime();
}
await this.store.update(this.card.id, { expiresAt });
this.close();
})();
});
}
addSlot(val = 1, unit = "minutes") {
const slotEl = this.slotsContainer.createDiv("sc-dt-slot");
const numInput = slotEl.createEl("input", { type: "number", cls: "sc-dt-slot-num" });
numInput.value = String(val);
const unitSelect = slotEl.createEl("select", { cls: "sc-dt-slot-unit" });
["seconds", "minutes", "hours", "days", "weeks"].forEach((u) => {
const opt = unitSelect.createEl("option", { text: u, value: u });
if (u === unit)
opt.selected = true;
});
const removeBtn = slotEl.createEl("button", { text: "\xD7", cls: "sc-dt-slot-remove" });
const slotObj = { value: val, unit, el: slotEl, numInput };
this.slots.push(slotObj);
removeBtn.addEventListener("click", () => {
this.slots = this.slots.filter((s) => s !== slotObj);
slotEl.remove();
});
}
toInputValue(d) {
const pad = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
};
// src/utils/editor-utils.ts
function handleKeyWrap(event, editorEl, _editor, enabled = true) {
if (!enabled)
return false;
if (event.ctrlKey || event.metaKey || event.altKey)
return false;
const key = event.key;
const wrapMap = {
"[": ["[", "]"],
"(": ["(", ")"],
"{": ["{", "}"],
"`": ["`", "`"],
"%": ["%", "%"],
"=": ["=", "="],
'"': ['"', '"']
};
const pair = wrapMap[key];
if (!pair)
return false;
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return false;
const range = sel.getRangeAt(0);
if (!editorEl.contains(range.commonAncestorContainer))
return false;
event.preventDefault();
event.stopPropagation();
const selectedText = range.toString();
const [open, close] = pair;
let newText = `${open}${selectedText}${close}`;
if (key === "%") {
if (selectedText.startsWith("%") && selectedText.endsWith("%") && !selectedText.startsWith("%%")) {
const inner = selectedText.slice(1, -1).trim();
newText = `%% ${inner} %%`;
}
} else if (key === "=") {
if (selectedText.startsWith("=") && selectedText.endsWith("=") && !selectedText.startsWith("==")) {
const inner = selectedText.slice(1, -1);
newText = `==${inner}==`;
}
} else if (key === "[") {
if (selectedText.startsWith("[") && selectedText.endsWith("]") && !selectedText.startsWith("[[")) {
const inner = selectedText.slice(1, -1);
newText = `[[${inner}]]`;
}
}
range.deleteContents();
const node = document.createTextNode(newText);
range.insertNode(node);
const newRange = document.createRange();
if (selectedText.length === 0) {
newRange.setStart(node, open.length);
newRange.collapse(true);
} else {
newRange.selectNode(node);
}
sel.removeAllRanges();
sel.addRange(newRange);
return true;
}
function isWordChar(char) {
return /[A-Za-z0-9_]/.test(char);
}
function getWordRangeAtCaret(selection) {
if (!selection.rangeCount)
return null;
const baseRange = selection.getRangeAt(0);
if (!baseRange.collapsed)
return baseRange;
const node = baseRange.startContainer;
if (!(node instanceof Text))
return null;
const text = node.data;
if (!text)
return null;
const offset = baseRange.startOffset;
const leftChar = offset > 0 ? text[offset - 1] : "";
const rightChar = offset < text.length ? text[offset] : "";
if (!isWordChar(leftChar) && !isWordChar(rightChar))
return null;
let start = offset;
let end = offset;
while (start > 0 && isWordChar(text[start - 1]))
start--;
while (end < text.length && isWordChar(text[end]))
end++;
const wordRange = document.createRange();
wordRange.setStart(node, start);
wordRange.setEnd(node, end);
return wordRange;
}
// src/views/components/InlineAutocomplete.ts
var import_obsidian2 = require("obsidian");
var InlineAutocomplete = class {
constructor(editorEl, store, app) {
this.editorEl = editorEl;
this.store = store;
this.app = app;
this.selectedIndex = -1;
this.items = [];
this.triggerStart = -1;
this.triggerChar = null;
this.isOpen = false;
const parent = editorEl.parentElement;
parent.addClass("sc-ac-parent");
this.dropdown = parent.createDiv("sc-inline-autocomplete");
editorEl.addEventListener("input", () => this.onInput());
editorEl.addEventListener("keydown", (e) => this.onKeyDown(e), true);
editorEl.addEventListener("blur", () => window.setTimeout(() => this.hide(), 150));
}
getCaretInfo() {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return { text: "", offset: 0 };
const range = sel.getRangeAt(0);
const preRange = document.createRange();
preRange.selectNodeContents(this.editorEl);
preRange.setEnd(range.startContainer, range.startOffset);
const text = preRange.toString();
return { text, offset: text.length };
}
onInput() {
const { text } = this.getCaretInfo();
if (this.app) {
const doubleBracketIdx = text.lastIndexOf("[[");
if (doubleBracketIdx !== -1) {
const afterBracket = text.substring(doubleBracketIdx + 2);
if (!afterBracket.includes("]]")) {
const query2 = afterBracket.toLowerCase();
const suggestions2 = this.getFileSuggestions(query2);
if (suggestions2.length > 0) {
this.triggerStart = doubleBracketIdx;
this.triggerChar = "[[";
this.items = suggestions2;
this.renderDropdown();
this.positionDropdown();
return;
}
}
}
}
let triggerIdx = -1;
let triggerChar = null;
for (let i = text.length - 1; i >= 0; i--) {
const ch = text[i];
if (ch === " " || ch === "\n")
break;
if (ch === "@" || ch === "#") {
triggerIdx = i;
triggerChar = ch;
break;
}
}
if (triggerIdx === -1 || triggerChar === null) {
this.hide();
return;
}
const query = text.substring(triggerIdx + 1).toLowerCase();
this.triggerStart = triggerIdx;
this.triggerChar = triggerChar;
const suggestions = triggerChar === "@" ? this.getCategorySuggestions(query) : this.getTagSuggestions(query);
if (suggestions.length === 0) {
this.hide();
return;
}
this.items = suggestions;
this.renderDropdown();
this.positionDropdown();
}
getFileSuggestions(query) {
if (!this.app)
return [];
const files = this.app.vault.getFiles().filter((f) => f.name && !f.name.startsWith(".") && f.name.toLowerCase().includes(query)).slice(0, 10);
return files.map((f) => {
const iconInfo = this.resolveIconicIcon(f);
return {
label: f.name,
value: f.name,
prefix: "[[",
icon: (iconInfo == null ? void 0 : iconInfo.icon) || "file-text",
iconColor: iconInfo == null ? void 0 : iconInfo.color
};
});
}
/** Synchronously resolve iconic plugin icon for a file (no async needed for sync APIs). */
resolveIconicIcon(file) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
if (!this.app)
return null;
const iconicPlugin = (_b = (_a = this.app.plugins) == null ? void 0 : _a.getPlugin) == null ? void 0 : _b.call(_a, "iconic");
if (!iconicPlugin)
return null;
const path = file.path;
try {
const ruled = (_d = (_c = iconicPlugin.ruleManager) == null ? void 0 : _c.checkRuling) == null ? void 0 : _d.call(_c, "file", path);
if (ruled) {
const iconValue = (_e = ruled.icon) != null ? _e : ruled.iconDefault;
if (iconValue)
return { icon: String(iconValue), color: (_f = ruled.color) != null ? _f : void 0 };
}
} catch (e) {
}
try {
const item = (_g = iconicPlugin.getFileItem) == null ? void 0 : _g.call(iconicPlugin, path);
if (item) {
const iconValue = (_h = item.icon) != null ? _h : item.iconDefault;
if (iconValue)
return { icon: String(iconValue), color: (_i = item.color) != null ? _i : void 0 };
}
} catch (e) {
}
const entry = (_p = (_n = (_k = (_j = iconicPlugin.settings) == null ? void 0 : _j.fileIcons) == null ? void 0 : _k[path]) != null ? _n : (_m = (_l = iconicPlugin.data) == null ? void 0 : _l.fileIcons) == null ? void 0 : _m[path]) != null ? _p : (_o = iconicPlugin.fileIcons) == null ? void 0 : _o[path];
if (!entry)
return null;
if (typeof entry === "string")
return { icon: entry };
if (typeof entry === "object") {
const iconValue = (_r = (_q = entry.icon) != null ? _q : entry.name) != null ? _r : entry.value;
if (iconValue)
return { icon: String(iconValue), color: typeof entry.color === "string" ? entry.color : void 0 };
}
return null;
}
getCategorySuggestions(query) {
const settings = this.store.settings;
const cats = [];
const builtinIcons = settings.builtinCategoryIcons || {};
if (!settings.hideTodayFilter)
cats.push({ label: "Today", value: "today", prefix: "@", icon: builtinIcons["today"] || "calendar-check" });
if (!settings.hideTomorrowFilter)
cats.push({ label: "Tomorrow", value: "tomorrow", prefix: "@", icon: builtinIcons["tomorrow"] || "calendar-plus" });
if (settings.enableCustomCategories) {
(settings.customCategories || []).forEach((c) => {
cats.push({ label: c.label, value: c.id || c.label, prefix: "@", icon: c.icon });
});
}
return query ? cats.filter((c) => c.label.toLowerCase().startsWith(query) || c.value.toLowerCase().startsWith(query)) : cats.slice(0, 8);
}
getTagSuggestions(query) {
const tags = /* @__PURE__ */ new Set();
this.store.getAll().forEach((c) => (c.tags || []).forEach((t) => tags.add(t.toLowerCase())));
const all = Array.from(tags).sort();
const filtered = query ? all.filter((t) => t.startsWith(query)) : all;
return filtered.slice(0, 8).map((t) => ({ label: t, value: t, prefix: "#" }));
}
renderDropdown() {
this.dropdown.empty();
this.selectedIndex = -1;
this.isOpen = true;
this.items.forEach((item, idx) => {
var _a;
const row = this.dropdown.createDiv("sc-inline-ac-item");
if (item.prefix === "[[" || item.prefix === "@" && item.icon) {
const iconEl = row.createSpan("sc-inline-ac-icon");
const iconName = item.icon || (item.prefix === "[[" ? "file-text" : "at-sign");
try {
(0, import_obsidian2.setIcon)(iconEl, iconName);
} catch (e) {
iconEl.textContent = item.prefix;
}
if (item.iconColor) {
const colorMap = {
red: "var(--color-red)",
orange: "var(--color-orange)",
yellow: "var(--color-yellow)",
green: "var(--color-green)",
cyan: "var(--color-cyan)",
blue: "var(--color-blue)",
purple: "var(--color-purple)",
pink: "var(--color-pink)",
magenta: "var(--color-pink)",
gray: "var(--color-base-70)",
grey: "var(--color-base-70)"
};
const normalized = item.iconColor.trim().toLowerCase();
iconEl.style.color = (_a = colorMap[normalized]) != null ? _a : item.iconColor;
}
} else if (item.prefix === "@") {
row.createSpan({ cls: "sc-inline-ac-badge", text: "@" });
} else {
row.createSpan({ cls: "sc-inline-ac-badge", text: "#" });
}
row.createSpan({ text: item.label });
row.addEventListener("mousedown", (e) => {
e.preventDefault();
this.selectItem(idx);
});
row.addEventListener("mouseenter", () => {
this.selectedIndex = idx;
this.highlightSelected();
});
});
this.dropdown.addClass("is-visible");
}
positionDropdown() {
const editorRect = this.editorEl.getBoundingClientRect();
const parentRect = this.editorEl.parentElement.getBoundingClientRect();
let caretLeft = 0;
const sel = window.getSelection();
if (sel && sel.rangeCount) {
const range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
const rect = range.getBoundingClientRect();
if (rect.width > 0 || rect.height > 0)
caretLeft = rect.left - parentRect.left;
}
const bottomOffset = parentRect.bottom - editorRect.top + 4;
const leftOffset = Math.max(0, caretLeft);
this.dropdown.setCssProps({
"--sc-ac-bottom": `${bottomOffset}px`,
"--sc-ac-left": `${leftOffset}px`
});
this.dropdown.removeClass("ac-below");
this.dropdown.addClass("ac-above");
requestAnimationFrame(() => {
const dropRect = this.dropdown.getBoundingClientRect();
if (dropRect.top < 0) {
const topOffset = editorRect.top - parentRect.top + editorRect.height + 4;
this.dropdown.setCssProps({ "--sc-ac-top": `${topOffset}px`, "--sc-ac-left": `${leftOffset}px` });
this.dropdown.removeClass("ac-above");
this.dropdown.addClass("ac-below");
}
});
}
highlightSelected() {
const rows = this.dropdown.querySelectorAll(".sc-inline-ac-item");
rows.forEach((r, i) => r.toggleClass("is-selected", i === this.selectedIndex));
if (this.selectedIndex >= 0 && rows[this.selectedIndex]) {
rows[this.selectedIndex].scrollIntoView({ block: "nearest" });
}
}
selectItem(idx) {
const item = this.items[idx];
if (!item)
return;
const fullText = this.editorEl.textContent || "";
const caretOffset = this.getCaretInfo().offset;
if (item.prefix === "[[") {
const before = fullText.substring(0, this.triggerStart);
let after = fullText.substring(caretOffset);
if (after.startsWith("]]"))
after = after.substring(2);
const replacement = "[[" + item.value + "]]";
this.editorEl.textContent = before + replacement + after;
this.setCaretAt(before.length + replacement.length);
} else {
const before = fullText.substring(0, this.triggerStart);
const after = fullText.substring(caretOffset);
const replacement = item.prefix + item.value + " ";
this.editorEl.textContent = before + replacement + after;
this.setCaretAt(before.length + replacement.length);
}
this.editorEl.dispatchEvent(new Event("input"));
this.hide();
}
setCaretAt(offset) {
const sel = window.getSelection();
if (!sel)
return;
let remaining = offset;
const walker = document.createTreeWalker(this.editorEl, NodeFilter.SHOW_TEXT);
let node = null;
while (walker.nextNode()) {
const n = walker.currentNode;
if (remaining <= n.length) {
node = n;
break;
}
remaining -= n.length;
}
const range = document.createRange();
if (node) {
range.setStart(node, remaining);
} else {
range.selectNodeContents(this.editorEl);
range.collapse(false);
}
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
onKeyDown(e) {
if (!this.isOpen)
return;
if (e.key === "ArrowDown") {
e.preventDefault();
e.stopPropagation();
this.selectedIndex = (this.selectedIndex + 1) % this.items.length;
this.highlightSelected();
} else if (e.key === "ArrowUp") {
e.preventDefault();
e.stopPropagation();
this.selectedIndex = (this.selectedIndex - 1 + this.items.length) % this.items.length;
this.highlightSelected();
} else if (e.key === "Enter") {
e.preventDefault();
e.stopImmediatePropagation();
if (this.selectedIndex >= 0) {
this.selectItem(this.selectedIndex);
} else {
this.hide();
}
} else if (e.key === "Escape") {
e.preventDefault();
e.stopPropagation();
this.hide();
}
}
hide() {
this.dropdown.removeClass("is-visible", "ac-above", "ac-below");
this.isOpen = false;
this.items = [];
this.selectedIndex = -1;
this.triggerChar = null;
}
destroy() {
this.dropdown.remove();
}
};
// src/views/components/Card.ts
var _CardComponent = class extends import_obsidian3.Component {
constructor(container, card, store, app, plugin, settingsOverride) {
super();
this.container = container;
this.store = store;
this.app = app;
this.plugin = plugin;
this.settingsOverride = settingsOverride;
this.unsubscribe = [];
this.isEditing = false;
this.ignoreNextClick = false;
this.renderCount = 0;
this.expiryTickInterval = null;
_CardComponent.instanceCount += 1;
this.card = card;
this.el = container.createDiv("sc-card");
this.scope = new import_obsidian3.Scope(this.app.scope);
this.setupMockEditor();
this.ensureGlobalMouseDownHandler();
this.load();
void this.render();
this.setupListeners();
}
ensureGlobalMouseDownHandler() {
if (_CardComponent.globalMouseDownBound)
return;
document.addEventListener("mousedown", _CardComponent.handleGlobalMouseDown, true);
_CardComponent.globalMouseDownBound = true;
}
setupMockEditor() {
this.editor = {
getSelection: () => {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return "";
const selectedText = sel.toString();
if (selectedText.length > 0)
return selectedText;
const wordRange = getWordRangeAtCaret(sel);
return wordRange ? wordRange.toString() : "";
},
replaceSelection: (text, keepSelection = false) => {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const currentRange = sel.getRangeAt(0);
const isCollapsed = currentRange.collapsed;
const range = isCollapsed ? getWordRangeAtCaret(sel) || currentRange : currentRange;
range.deleteContents();
const node = document.createTextNode(text);
range.insertNode(node);
if (keepSelection || !isCollapsed) {
const newRange = document.createRange();
newRange.selectNode(node);
sel.removeAllRanges();
sel.addRange(newRange);
} else {
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
},
toggleBold: () => this.toggleMarkdownWrapper("**"),
toggleItalic: () => this.toggleMarkdownWrapper("*"),
toggleHighlight: () => this.toggleMarkdownWrapper("=="),
toggleComment: () => this.toggleMarkdownWrapper("%%", "%%", true)
};
this.owner = {
editor: this.editor,
editMode: true
};
}
toggleMarkdownWrapper(wrapper, closeWrapper, includeInnerPadding = false) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const currentRange = sel.getRangeAt(0);
const range = currentRange.collapsed ? getWordRangeAtCaret(sel) || currentRange : currentRange;
const selectedText = range.toString();
const endWrapper = closeWrapper != null ? closeWrapper : wrapper;
if (selectedText.length === 0) {
const text = wrapper + endWrapper;
const node = document.createTextNode(text);
range.insertNode(node);
const cursorOffset = wrapper.length;
range.setStart(node, cursorOffset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return;
}
const alreadyWrapped = selectedText.startsWith(wrapper) && selectedText.endsWith(endWrapper);
const newText = alreadyWrapped ? selectedText.slice(wrapper.length, selectedText.length - endWrapper.length) : includeInnerPadding ? `${wrapper} ${selectedText} ${endWrapper}` : `${wrapper}${selectedText}${endWrapper}`;
sel.removeAllRanges();
sel.addRange(range);
this.editor.replaceSelection(newText);
}
getEffectiveHotkeys(commandId) {
var _a, _b, _c, _d, _e, _f, _g;
const appAny = this.app;
const fromManager = (_b = (_a = appAny.hotkeyManager) == null ? void 0 : _a.getHotkeys) == null ? void 0 : _b.call(_a, commandId);
if (Array.isArray(fromManager) && fromManager.length > 0)
return fromManager;
const custom = (_d = (_c = appAny.hotkeyManager) == null ? void 0 : _c.customKeys) == null ? void 0 : _d[commandId];
if (Array.isArray(custom) && custom.length > 0)
return custom;
const defaults = (_g = (_f = (_e = appAny.commands) == null ? void 0 : _e.commands) == null ? void 0 : _f[commandId]) == null ? void 0 : _g.hotkeys;
if (Array.isArray(defaults) && defaults.length > 0)
return defaults;
return [];
}
getFormattingCommandIds(kind) {
var _a;
const defaults = {
bold: ["editor:toggle-bold", "custom-wrap-bold"],
italic: ["editor:toggle-italic", "editor:toggle-emphasis", "custom-wrap-italic"],
highlight: ["editor:toggle-highlight", "custom-wrap-highlight"],
comment: ["editor:toggle-comment", "custom-wrap-comment"]
};
const appAny = this.app;
const commands = ((_a = appAny.commands) == null ? void 0 : _a.commands) || {};
const matcher = {
bold: /bold/i,
italic: /italic|emphasis/i,
highlight: /highlight/i,
comment: /comment/i
};
const discovered = Object.values(commands).filter((cmd) => typeof (cmd == null ? void 0 : cmd.id) === "string" && cmd.id.startsWith("editor:")).filter((cmd) => matcher[kind].test(String((cmd == null ? void 0 : cmd.name) || ""))).map((cmd) => String(cmd.id));
return Array.from(/* @__PURE__ */ new Set([...defaults[kind], ...discovered]));
}
eventMatchesHotkey(event, hotkey) {
const key = String((hotkey == null ? void 0 : hotkey.key) || "").toLowerCase();
if (!key)
return false;
const eventKey = String(event.key || "").toLowerCase();
if (eventKey !== key)
return false;
const modifierSet = new Set((hotkey.modifiers || []).map((m) => String(m).toLowerCase()));
const hasMod = modifierSet.has("mod");
const expectsCtrl = modifierSet.has("ctrl") || hasMod && !import_obsidian3.Platform.isMacOS;
const expectsMeta = modifierSet.has("meta") || hasMod && import_obsidian3.Platform.isMacOS;
const expectsAlt = modifierSet.has("alt");
const expectsShift = modifierSet.has("shift");
if (expectsCtrl !== event.ctrlKey)
return false;
if (expectsMeta !== event.metaKey)
return false;
if (expectsAlt !== event.altKey)
return false;
if (expectsShift !== event.shiftKey)
return false;
return true;
}
applyFormattingHotkey(event, root) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return false;
const range = sel.getRangeAt(0);
if (!root.contains(range.commonAncestorContainer))
return false;
const targets = [
{ kind: "bold", run: () => this.toggleMarkdownWrapper("**") },
{ kind: "italic", run: () => this.toggleMarkdownWrapper("*") },
{ kind: "highlight", run: () => this.toggleMarkdownWrapper("==") },
{ kind: "comment", run: () => this.toggleMarkdownWrapper("%%", "%%", true) }
];
for (const target of targets) {
const commandIds = this.getFormattingCommandIds(target.kind);
const hotkeys = commandIds.flatMap((id) => this.getEffectiveHotkeys(id));
if (!hotkeys.length)
continue;
if (!hotkeys.some((h) => this.eventMatchesHotkey(event, h)))
continue;
event.preventDefault();
event.stopPropagation();
target.run();
return true;
}
return false;
}
async render() {
var _a;
const currentRender = ++this.renderCount;
this.stopExpiryTick();
if (this.isEditing) {
const existingContent = this.el.querySelector(".sc-content");
if (existingContent) {
existingContent.setAttribute("contenteditable", "true");
existingContent.textContent = this.card.content;
existingContent.addClass("is-editing");
window.setTimeout(() => {
existingContent.focus();
const range = document.createRange();
const sel = window.getSelection();
range.selectNodeContents(existingContent);
range.collapse(false);
sel == null ? void 0 : sel.removeAllRanges();
sel == null ? void 0 : sel.addRange(range);
}, 0);
existingContent.addEventListener("focusin", () => {
this.app.keymap.pushScope(this.scope);
this.app.workspace.activeEditor = this.owner;
});
existingContent.addEventListener("blur", () => {
this.app.keymap.popScope(this.scope);
if (this.app.workspace.activeEditor === this.owner) {
this.app.workspace.activeEditor = null;
}
if (this.isEditing) {
void (async () => {
const newContent = existingContent.textContent || "";
if (newContent !== this.card.content) {
await this.store.update(this.card.id, { content: newContent });
}
this.isEditing = false;
if (_CardComponent.activeEditor === this) {
_CardComponent.activeEditor = null;
}
void this.render();
})();
}
});
existingContent.addEventListener("keydown", (e) => {
var _a2;
const keyboardEvent = e;
if (handleKeyWrap(keyboardEvent, existingContent, this.editor, ((_a2 = this.plugin.settings) == null ? void 0 : _a2.autoPairBrackets) !== false)) {
e.preventDefault();
e.stopPropagation();
return;
}
if (this.applyFormattingHotkey(keyboardEvent, existingContent))
return;
const settings = this.store.settings;
const normalizeKey = (v) => String(v || "").toLowerCase().replace(/[\s+_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
const saveKey = normalizeKey(settings.saveKey || "enter");
let pressed = "";
if (keyboardEvent.ctrlKey)
pressed += "ctrl-";
if (keyboardEvent.shiftKey)
pressed += "shift-";
if (keyboardEvent.altKey)
pressed += "alt-";
if (keyboardEvent.key && keyboardEvent.key.toLowerCase() === "enter")
pressed += "enter";
if (pressed === saveKey) {
e.preventDefault();
existingContent.blur();
}
});
return;
}
}
this.el.empty();
this.el.dataset.id = this.card.id;
this.el.draggable = true;
const statusDef = this.card.status ? (this.store.settings.cardStatuses || []).find((s) => s.name === this.card.status.name) : null;
const useStatusColor = this.store.settings.inheritStatusColor && statusDef;
const effectiveColor = useStatusColor && statusDef ? statusDef.colorIndex ? `var(--card-color-${statusDef.colorIndex})` : statusDef.color : this.card.color;
applyCardColorToElement(this.el, effectiveColor, {
cardStyle: this.store.settings.cardStyle,
cardBgOpacity: this.store.settings.cardBgOpacity,
borderThickness: this.store.settings.borderThickness,
cardBorderShadowOpacity: this.store.settings.cardBorderShadowOpacity
});
if ((_a = this.card.status) == null ? void 0 : _a.name) {
this.el.dataset.status = this.card.status.name;
} else {
delete this.el.dataset.status;
}
if (this.store.settings.cardStyle === 2) {
this.el.addClass("sc-style-2-masonry");
} else {
this.el.removeClass("sc-style-2-masonry");
}
if (this.store.settings.enableCopyCardContent) {
const copyBtn = this.el.createDiv("sc-copy-btn");
try {
(0, import_obsidian3.setIcon)(copyBtn, "copy");
} catch (e) {
copyBtn.textContent = "\u{1F4CB}";
}
copyBtn.title = "Copy card content";
copyBtn.addEventListener("click", (e) => {
e.stopPropagation();
this.copyCardContent();
});
}
const pillBar = this.el.createDiv("sc-pill-bar");
this.renderPills(pillBar);
const content = this.el.createDiv("sc-content");
await this.renderContent(content);
if (currentRender !== this.renderCount) {
content.remove();
return;
}
const footer = this.el.createDiv("sc-footer");
this.renderFooter(footer);
this.startExpiryTick();
}
copyCardContent() {
let contentToCopy = this.card.content;
const contentEl = this.el.querySelector(".sc-content");
if (contentEl && !this.store.settings.disableCardRendering) {
contentToCopy = contentEl.textContent || this.card.content;
}
navigator.clipboard.writeText(contentToCopy).then(() => {
new import_obsidian3.Notice("Card content copied!");
}, () => {
new import_obsidian3.Notice("Failed to copy card content");
});
}
renderPills(container) {
if (this.card.expiresAt && this.store.settings.showExpiryTimeLeft) {
const pill = container.createDiv("sc-expiry-pill");
pill.textContent = this.formatExpiryTimeLeft(this.card.expiresAt);
if (this.card.status)
pill.addClass("sc-status-pill--with-margin");
}
if (this.card.status) {
const pill = container.createDiv("sc-status-pill");
pill.textContent = this.card.status.name;
const statusDef = (this.store.settings.cardStatuses || []).find((s) => s.name === this.card.status.name);
if (statusDef == null ? void 0 : statusDef.colorIndex) {
pill.style.backgroundColor = `var(--card-color-${statusDef.colorIndex})`;
pill.style.color = statusDef.textColor || "#000";
} else {
pill.style.backgroundColor = this.card.status.color || "transparent";
pill.style.color = this.card.status.textColor || "#000";
}
}
}
async renderContent(container) {
if (this.isEditing || this.store.settings.disableCardRendering) {
container.setAttr("contenteditable", "true");
container.textContent = this.card.content;
container.addClass("is-editing");
const ac = new InlineAutocomplete(container, this.store, this.app);
window.setTimeout(() => {
container.focus();
const range = document.createRange();
const sel = window.getSelection();
range.selectNodeContents(container);
range.collapse(false);
sel == null ? void 0 : sel.removeAllRanges();
sel == null ? void 0 : sel.addRange(range);
}, 0);
container.addEventListener("focusin", () => {
this.app.keymap.pushScope(this.scope);
this.app.workspace.activeEditor = this.owner;
});
container.addEventListener("blur", () => {
this.app.keymap.popScope(this.scope);
if (this.app.workspace.activeEditor === this.owner) {
this.app.workspace.activeEditor = null;
}
if (this.isEditing) {
void (async () => {
ac.destroy();
const newContent = container.textContent || "";
if (newContent !== this.card.content) {
await this.store.update(this.card.id, { content: newContent });
}
this.isEditing = false;
if (_CardComponent.activeEditor === this) {
_CardComponent.activeEditor = null;
}
void this.render();
})();
}
});
container.addEventListener("keydown", (e) => {
var _a;
if (handleKeyWrap(e, container, this.editor, ((_a = this.plugin.settings) == null ? void 0 : _a.autoPairBrackets) !== false)) {
e.preventDefault();
e.stopPropagation();
return;
}
if (this.applyFormattingHotkey(e, container))
return;
const settings = this.store.settings;
const normalizeKey = (v) => String(v || "").toLowerCase().replace(/[\s+_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
const saveKey = normalizeKey(settings.saveKey || "enter");
const nextLineKey = normalizeKey(settings.nextLineKey || "shift-enter");
let pressed = "";
if (e.ctrlKey)
pressed += "ctrl-";
if (e.shiftKey)
pressed += "shift-";
if (e.altKey)
pressed += "alt-";
if (e.key && e.key.toLowerCase() === "enter")
pressed += "enter";
if (pressed === saveKey) {
e.preventDefault();
container.blur();
} else if (pressed === nextLineKey) {
}
});
} else {
container.setAttribute("contenteditable", "false");
container.removeClass("is-editing");
const temp = document.createElement("div");
try {
await import_obsidian3.MarkdownRenderer.render(this.app, this.card.content, temp, this.card.notePath || "", this);
} catch (e) {
temp.textContent = this.card.content;
}
temp.querySelectorAll("mark").forEach((el) => el.addClass("cm-highlight"));
this.resolveImageEmbeds(temp);
while (temp.firstChild)
container.appendChild(temp.firstChild);
this.attachInternalLinkHandlers(container);
}
}
resolveImageEmbeds(container) {
const IMAGE_EXTS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif"]);
container.querySelectorAll("span.internal-embed").forEach((span) => {
var _a, _b;
try {
const src = span.getAttribute("src") || "";
const ext = (_b = (_a = src.split(".").pop()) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
if (!IMAGE_EXTS.has(ext))
return;
const file = this.app.metadataCache.getFirstLinkpathDest(src, this.card.notePath || "");
if (!file)
return;
const resourcePath = this.app.vault.getResourcePath(file);
const img = document.createElement("img");
img.src = resourcePath;
img.alt = src;
img.addClass("sc-embed-image");
const altAttr = span.getAttribute("alt") || "";
const sizeMatch = altAttr.match(/(\d+)(?:x(\d+))?/);
if (sizeMatch) {
img.style.width = `${sizeMatch[1]}px`;
if (sizeMatch[2])
img.style.height = `${sizeMatch[2]}px`;
}
span.replaceWith(img);
} catch (e) {
}
});
}
attachInternalLinkHandlers(container) {
const links = container.querySelectorAll("a.internal-link, a[data-href]");
links.forEach((link) => {
var _a;
if (!(link instanceof HTMLAnchorElement))
return;
const href = ((_a = link.dataset) == null ? void 0 : _a.href) || "";
if (!href)
return;
link.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const openInNewLeaf = e.metaKey || e.ctrlKey;
void this.openOrCreateLink(href, openInNewLeaf);
});
});
}
async openOrCreateLink(rawLinkText, openInNewLeaf) {
var _a;
const sourcePath = this.card.notePath || ((_a = this.app.workspace.getActiveFile()) == null ? void 0 : _a.path) || "";
const linkText = String(rawLinkText || "").trim();
if (!linkText)
return;
const { filePart, fullLinkText } = this.parseLinkText(linkText);
const dest = this.app.metadataCache.getFirstLinkpathDest(filePart, sourcePath) || (filePart.endsWith(".md") ? this.app.metadataCache.getFirstLinkpathDest(filePart.slice(0, -3), sourcePath) : null);
if (dest) {
await this.app.workspace.openLinkText(fullLinkText, sourcePath, openInNewLeaf);
return;
}
const created = await this.createFileForLinkTarget(filePart, sourcePath);
if (!created)
return;
await this.app.workspace.openLinkText(fullLinkText, sourcePath, openInNewLeaf);
}
parseLinkText(linkText) {
var _a, _b, _c;
const fullLinkText = String(linkText || "").trim();
const noAlias = ((_a = fullLinkText.split("|")[0]) == null ? void 0 : _a.trim()) || "";
const withoutBang = noAlias.startsWith("!") ? noAlias.slice(1).trim() : noAlias;
const fileOnly = ((_c = (_b = withoutBang.split("#")[0]) == null ? void 0 : _b.split("^")[0]) == null ? void 0 : _c.trim()) || "";
return { filePart: fileOnly, fullLinkText };
}
async createFileForLinkTarget(filePartRaw, sourcePath) {
var _a, _b, _c, _d;
const filePart = this.sanitizePath(String(filePartRaw || "").trim());
if (!filePart)
return null;
const pluginFolderRaw = String(((_b = (_a = this.plugin) == null ? void 0 : _a.settings) == null ? void 0 : _b.storageFolder) || "").trim();
const pluginFolder = pluginFolderRaw && pluginFolderRaw !== "/" ? pluginFolderRaw : "";
const sourceFolder = sourcePath.includes("/") ? sourcePath.slice(0, sourcePath.lastIndexOf("/")) : "";
const defaultFolder = sourceFolder || ((_d = (_c = this.app.workspace.getActiveFile()) == null ? void 0 : _c.parent) == null ? void 0 : _d.path) || pluginFolder;
const hasFolder = filePart.includes("/");
const basePath = (hasFolder ? filePart.replace(/^\/+/, "") : [defaultFolder, filePart].filter(Boolean).join("/")).replace(/^\/+/, "");
const normalizedBase = basePath.replace(/\/+/g, "/");
const { targetPath } = this.ensureExtension(normalizedBase);
const folderPath = targetPath.includes("/") ? targetPath.slice(0, targetPath.lastIndexOf("/")) : "";
if (folderPath) {
await this.ensureFolderExists(folderPath);
}
const existing = this.app.vault.getAbstractFileByPath(targetPath);
if (existing instanceof import_obsidian3.TFile)
return existing;
try {
return await this.app.vault.create(targetPath, "");
} catch (e) {
new import_obsidian3.Notice(`Failed to create file: ${targetPath}`);
return null;
}
}
ensureExtension(path) {
const lastSegment = path.split("/").pop() || "";
const hasExtension = /\.[A-Za-z0-9]+$/.test(lastSegment);
if (hasExtension)
return { targetPath: path };
return { targetPath: `${path}.md` };
}
sanitizePath(path) {
return path.replace(/[\\:*?"<>|]/g, "-").replace(/\s+/g, " ").trim().replace(/\/+/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
}
async ensureFolderExists(folderPath) {
const normalized = this.sanitizePath(folderPath);
if (!normalized)
return;
const parts = normalized.split("/").filter(Boolean);
let current = "";
for (const part of parts) {
current = current ? `${current}/${part}` : part;
const existing = this.app.vault.getAbstractFileByPath(current);
if (existing instanceof import_obsidian3.TFolder)
continue;
if (existing instanceof import_obsidian3.TFile)
return;
try {
await this.app.vault.createFolder(current);
} catch (e) {
}
}
}
renderFooter(container) {
var _a, _b, _c, _d, _e, _f;
const settings = this.store.settings;
const groupTags = (_b = (_a = this.settingsOverride) == null ? void 0 : _a.groupTags) != null ? _b : settings.groupTags;
const showTimestamps = (_d = (_c = this.settingsOverride) == null ? void 0 : _c.showTimestamps) != null ? _d : settings.showTimestamps;
const showTags = (_f = (_e = this.settingsOverride) == null ? void 0 : _e.showTags) != null ? _f : true;
const hasTags = showTags && this.card.tags && this.card.tags.length > 0;
if (groupTags) {
if (showTimestamps && settings.timestampBelowTags) {
const ts = container.createDiv("sc-timestamp sc-timestamp--block");
ts.textContent = this.formatTimestamp(this.card.created);
}
if (hasTags) {
const tagsEl = container.createDiv("sc-tags");
this.card.tags.forEach((tag) => {
const tagEl = tagsEl.createSpan("sc-tag");
const cleanTag = tag.trim().replace(/^[-#\s]+/, "").trim();
tagEl.textContent = settings.omitTagHash ? cleanTag : `#${cleanTag}`;
tagEl.addEventListener("click", (e) => {
e.stopPropagation();
this.store.eventBus.emit("filter:tag", cleanTag);
});
});
}
if (showTimestamps && !settings.timestampBelowTags) {
const ts = container.createDiv(`sc-timestamp ${hasTags ? "sc-timestamp--inline-spaced" : "sc-timestamp--inline"}`);
ts.textContent = this.formatTimestamp(this.card.created);
}
} else {
if (showTimestamps) {
container.createDiv("sc-timestamp").textContent = this.formatTimestamp(this.card.created);
}
}
}
setupListeners() {
this.el.addEventListener("dragstart", (e) => {
if (!e.dataTransfer)
return;
e.dataTransfer.effectAllowed = "copyMove";
const payload = JSON.stringify({ id: this.card.id, content: this.card.content });
e.dataTransfer.setData("text/x-card-sidebar", payload);
try {
e.dataTransfer.setData("text/plain", this.card.content);
} catch (e2) {
}
});
this.el.addEventListener("click", (e) => {
if (this.ignoreNextClick) {
this.ignoreNextClick = false;
return;
}
if (_CardComponent.activeEditor && _CardComponent.activeEditor !== this) {
_CardComponent.activeEditor.blurAndSave();
}
if (this.isEditing)
return;
const target = e.target;
if (target.closest(".sc-copy-btn") || target.closest(".sc-expiry-pill") || target.closest(".sc-status-pill") || target.closest(".sc-tag") || target.tagName === "A" || // Don't trigger if clicking a link
target.closest("button")) {
return;
}
this.isEditing = true;
_CardComponent.activeEditor = this;
void this.render();
});
this.el.addEventListener("contextmenu", (e) => {
e.preventDefault();
const menu = new import_obsidian3.Menu();
const s = this.store.settings;
menu.addItem((item) => {
var _a;
item.setTitle("Colors");
const container = document.createElement("div");
container.className = "sc-color-dots";
if (s.twoRowSwatches)
container.classList.add("two-row");
const colors = [
"var(--card-color-1)",
"var(--card-color-2)",
"var(--card-color-3)",
"var(--card-color-4)",
"var(--card-color-5)",
"var(--card-color-6)",
"var(--card-color-7)",
"var(--card-color-8)",
"var(--card-color-9)",
"var(--card-color-10)"
];
colors.forEach((color, idx) => {
const swatch = document.createElement("div");
swatch.className = "sc-color-dot sc-color-dot-swatch";
swatch.classList.toggle("is-selected", this.card.color === color);
swatch.title = s.colorNames[idx] || `Color ${idx + 1}`;
const computed = getComputedStyle(document.documentElement).getPropertyValue(color.replace("var(", "").replace(")", ""));
swatch.style.backgroundColor = computed.trim() || color;
swatch.addEventListener("click", () => {
void this.store.setColor(this.card.id, color);
});
container.appendChild(swatch);
});
(_a = item.titleEl) == null ? void 0 : _a.appendChild(container);
});
const todayVisible = !s.hideTodayFilter;
const tomorrowVisible = !s.hideTomorrowFilter;
const customCategories = s.enableCustomCategories ? (s.customCategories || []).filter((c) => c.showInMenu !== false) : [];
if (todayVisible || tomorrowVisible || customCategories.length > 0) {
if (todayVisible) {
menu.addItem((item) => {
var _a, _b;
item.setTitle("Add to Today").setIcon((_b = (_a = s.builtinCategoryIcons) == null ? void 0 : _a["today"]) != null ? _b : "calendar-check").onClick(async () => {
await this.store.setCategory(this.card.id, "today");
});
});
}
if (tomorrowVisible) {
menu.addItem((item) => {
var _a, _b;
item.setTitle("Add to Tomorrow").setIcon((_b = (_a = s.builtinCategoryIcons) == null ? void 0 : _a["tomorrow"]) != null ? _b : "calendar-plus").onClick(async () => {
await this.store.setCategory(this.card.id, "tomorrow");
});
});
}
customCategories.forEach((cat) => {
menu.addItem((item) => {
item.setTitle(`Add to ${cat.label}`).setIcon(cat.icon || "plus-square").onClick(async () => {
await this.store.setCategory(this.card.id, cat.label || cat.id);
});
});
});
if (this.card.category) {
menu.addItem((item) => {
item.setTitle(`Remove from ${this.card.category}`).setIcon("x").onClick(async () => {
await this.store.setCategory(this.card.id, null);
});
});
}
}
menu.addSeparator();
menu.addItem((item) => {
item.setTitle(this.card.pinned ? "Unpin" : "Pin Card").setIcon("pin").onClick(async () => {
await this.store.togglePin(this.card.id, !this.card.pinned);
});
});
if (s.enableCardStatus && Array.isArray(s.cardStatuses) && s.cardStatuses.length > 0) {
menu.addItem((item) => {
item.setTitle("Set Status").setIcon("flag").onClick(() => {
var _a;
const menu2 = new import_obsidian3.Menu();
(_a = s.cardStatuses) == null ? void 0 : _a.forEach((st) => {
menu2.addItem((i) => {
i.setTitle(st.name || "").onClick(async () => {
await this.store.setStatus(this.card.id, {
name: st.name || "",
color: st.color || "",
textColor: st.textColor || "#000",
colorIndex: st.colorIndex
});
});
});
});
menu2.addItem((i) => {
i.setTitle("Clear Status").onClick(async () => {
await this.store.setStatus(this.card.id, null);
});
});
menu2.showAtMouseEvent(e);
});
});
}
menu.addItem((item) => {
item.setTitle("Set Expiry").setIcon("alarm-clock").onClick(() => {
new DateTimeModal(this.app, this.card, this.store).open();
});
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle("Duplicate").setIcon("copy").onClick(async () => {
await this.store.duplicateCard(this.card.id);
});
});
menu.addItem((item) => {
item.setTitle(this.card.notePath ? "View Note" : "Create Note").setIcon(this.card.notePath ? "link" : "file-plus").onClick(async () => {
if (this.card.notePath) {
const file = this.app.vault.getAbstractFileByPath(this.card.notePath);
if (file instanceof import_obsidian3.TFile) {
await this.app.workspace.getLeaf(true).openFile(file);
} else {
new import_obsidian3.Notice("Note not found");
}
} else {
const path = await this.store.createNoteFromCard(this.card.id);
if (!path)
return;
const file = this.app.vault.getAbstractFileByPath(path);
if (file instanceof import_obsidian3.TFile) {
await this.app.workspace.getLeaf(true).openFile(file);
}
}
});
});
if (!s.hideArchivedFilterButton || this.card.archived) {
menu.addItem((item) => {
var _a, _b;
item.setTitle(this.card.archived ? "Unarchive" : "Archive").setIcon((_b = (_a = s.builtinCategoryIcons) == null ? void 0 : _a["archived"]) != null ? _b : "archive").onClick(async () => {
await this.store.toggleArchive(this.card.id, !this.card.archived);
});
});
}
menu.addItem((item) => {
item.setTitle("Delete").setIcon("trash").onClick(async () => {
await this.store.delete(this.card.id);
});
});
menu.showAtMouseEvent(e);
});
const unbind = this.store.eventBus.on("card:updated", (updated) => {
if (updated.id === this.card.id) {
this.card = updated;
if (!this.isEditing) {
void this.render();
}
}
});
this.unsubscribe.push(unbind);
}
blurAndSave() {
const contentEl = this.el.querySelector(".sc-content");
if (contentEl) {
contentEl.blur();
}
}
formatTimestamp(ts) {
const created = this.getCreatedTime();
const fmt = this.store.settings.datetimeFormat;
const momentFn = window.moment;
if (fmt && momentFn) {
return momentFn(created).format(fmt);
}
return new Date(created).toLocaleDateString() + " " + new Date(created).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}
getCreatedTime() {
if (this.card.notePath) {
const file = this.app.vault.getAbstractFileByPath(this.card.notePath);
if (file instanceof import_obsidian3.TFile)
return file.stat.ctime;
}
return this.card.created;
}
formatExpiry(ts) {
const diff = ts - Date.now();
const days = Math.ceil(diff / (1e3 * 60 * 60 * 24));
if (days < 0)
return "Expired";
if (days === 0)
return "Expires today";
if (days === 1)
return "Expires tomorrow";
return `Expires in ${days} days`;
}
formatExpiryTimeLeft(ts) {
const diff = ts - Date.now();
if (diff <= 0)
return "Expired";
const fmt = this.store.settings.expiryTimeFormat || "human";
const totalSecs = Math.floor(diff / 1e3);
const years = Math.floor(totalSecs / (365 * 24 * 3600));
const months = Math.floor(totalSecs % (365 * 24 * 3600) / (30 * 24 * 3600));
const weeks = Math.floor(totalSecs % (30 * 24 * 3600) / (7 * 24 * 3600));
const days = Math.floor(totalSecs % (7 * 24 * 3600) / (24 * 3600));
const hours = Math.floor(totalSecs % (24 * 3600) / 3600);
const mins = Math.floor(totalSecs % 3600 / 60);
const secs = totalSecs % 60;
if (fmt === "short") {
const parts2 = [];
if (years)
parts2.push(`${years}y`);
if (months)
parts2.push(`${months}mo`);
if (weeks)
parts2.push(`${weeks}w`);
if (days)
parts2.push(`${days}d`);
if (hours)
parts2.push(`${hours}h`);
if (mins)
parts2.push(`${mins}m`);
if (secs && parts2.length < 2)
parts2.push(`${secs}s`);
return "Expires in " + (parts2.slice(0, 3).join(" ") || "< 1s");
}
const parts = [];
if (years)
parts.push(`${years} year${years !== 1 ? "s" : ""}`);
if (months)
parts.push(`${months} month${months !== 1 ? "s" : ""}`);
if (weeks)
parts.push(`${weeks} week${weeks !== 1 ? "s" : ""}`);
if (days)
parts.push(`${days} day${days !== 1 ? "s" : ""}`);
if (hours)
parts.push(`${hours} hour${hours !== 1 ? "s" : ""}`);
if (mins)
parts.push(`${mins} min${mins !== 1 ? "s" : ""}`);
if (secs && parts.length < 2)
parts.push(`${secs} sec${secs !== 1 ? "s" : ""}`);
return "Expires in " + (parts.slice(0, 3).join(" ") || "< 1 sec");
}
destroy() {
if (_CardComponent.activeEditor === this) {
_CardComponent.activeEditor = null;
}
_CardComponent.instanceCount = Math.max(0, _CardComponent.instanceCount - 1);
if (_CardComponent.instanceCount === 0 && _CardComponent.globalMouseDownBound) {
document.removeEventListener("mousedown", _CardComponent.handleGlobalMouseDown, true);
_CardComponent.globalMouseDownBound = false;
}
this.stopExpiryTick();
this.unsubscribe.forEach((fn) => fn());
this.unload();
this.el.remove();
}
startExpiryTick() {
this.stopExpiryTick();
if (!this.card.expiresAt || !this.store.settings.showExpiryTimeLeft)
return;
this.expiryTickInterval = window.setInterval(() => {
const pill = this.el.querySelector(".sc-expiry-pill");
if (!(pill instanceof HTMLElement) || !this.card.expiresAt)
return;
pill.textContent = this.formatExpiryTimeLeft(this.card.expiresAt);
}, 1e3);
}
stopExpiryTick() {
if (this.expiryTickInterval !== null) {
window.clearInterval(this.expiryTickInterval);
this.expiryTickInterval = null;
}
}
};
var CardComponent = _CardComponent;
CardComponent.activeEditor = null;
CardComponent.instanceCount = 0;
CardComponent.globalMouseDownBound = false;
CardComponent.handleGlobalMouseDown = (event) => {
const active = _CardComponent.activeEditor;
if (!active || !active.isEditing)
return;
const target = event.target;
const editableEl = active.el.querySelector('.sc-content[contenteditable="true"]');
if (target && editableEl && editableEl.contains(target))
return;
active.ignoreNextClick = true;
active.blurAndSave();
};
// src/utils/animations.ts
async function flipAnimateAsync(container, asyncDomChange, opts = {}, settings) {
var _a, _b, _c;
if (!settings.animatedCards) {
await asyncDomChange();
return;
}
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
await asyncDomChange();
return;
}
const duration = (_a = opts.duration) != null ? _a : 260;
const stagger = (_b = opts.stagger) != null ? _b : 20;
const entranceOffset = (_c = opts.offset) != null ? _c : 28;
const oldEls = Array.from(container.querySelectorAll(".sc-card"));
const oldMap = /* @__PURE__ */ new Map();
oldEls.forEach((el) => {
const id = el.dataset.id;
if (id)
oldMap.set(id, el.getBoundingClientRect());
});
await asyncDomChange();
const newEls = Array.from(container.querySelectorAll(".sc-card"));
const newMap = /* @__PURE__ */ new Map();
const elById = /* @__PURE__ */ new Map();
newEls.forEach((el) => {
const id = el.dataset.id;
if (id) {
newMap.set(id, el.getBoundingClientRect());
elById.set(id, el);
}
});
const ids = Array.from(elById.keys());
ids.forEach((id) => {
const oldRect = oldMap.get(id);
const newRect = newMap.get(id);
const el = elById.get(id);
if (oldRect && newRect && el) {
const dx = oldRect.left - newRect.left;
const dy = oldRect.top - newRect.top;
if (dx !== 0 || dy !== 0) {
el.setCssProps({
"transition": "none",
"transform": `translateY(${dy}px)`,
"will-change": "transform"
});
}
} else if (el) {
el.setCssProps({
"transition": "none",
"transform": `translateY(${entranceOffset}px)`,
"will-change": "transform"
});
if (!settings.disableCardFadeIn) {
el.setCssProps({ "opacity": "0" });
}
}
});
void container.offsetHeight;
ids.forEach((id, i) => {
const el = elById.get(id);
if (!el)
return;
const delay = i * stagger;
window.setTimeout(() => {
el.setCssProps({
"transition": `transform ${duration}ms ease-out, opacity ${duration}ms ease-out`,
"transform": ""
});
if (!settings.disableCardFadeIn) {
el.setCssProps({ "opacity": "1" });
}
}, delay);
});
window.setTimeout(() => {
ids.forEach((id) => {
const el = elById.get(id);
if (el) {
el.setCssProps({ "transition": "" });
}
});
}, duration + ids.length * stagger + 50);
}
function animateCardsEntrance(container, opts = {}, settings) {
var _a, _b, _c;
if (!settings.animatedCards)
return;
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches)
return;
const els = Array.from(container.querySelectorAll(".sc-card")).filter((el) => el.style.display !== "none");
if (els.length === 0)
return;
const duration = (_a = opts.duration) != null ? _a : 360;
const stagger = (_b = opts.stagger) != null ? _b : 34;
const offsetPx = (_c = opts.offset) != null ? _c : 28;
els.forEach((el) => {
el.setCssProps({
"transition": "none",
"transform": `translateY(${offsetPx}px)`,
"opacity": settings.disableCardFadeIn ? "1" : "0",
"will-change": "transform, opacity"
});
});
void container.offsetHeight;
els.forEach((el, i) => {
const delay = i * stagger;
window.setTimeout(() => {
el.setCssProps({
"transition": `transform ${duration}ms cubic-bezier(0.2, 0, 0, 1), opacity ${duration}ms cubic-bezier(0.2, 0, 0, 1)`,
"transform": "translateY(0)",
"opacity": "1"
});
}, delay);
});
window.setTimeout(() => {
els.forEach((el) => {
el.setCssProps({
"transition": "",
"transform": "",
"opacity": "",
"will-change": ""
});
});
}, duration + els.length * stagger + 50);
}
// src/views/CardSidebarView.ts
init_Card();
init_dom();
// src/utils/drag-drop.ts
function attachDragToReorder(container, plugin, getSortMode, onReorder, onPlaceholderMoved) {
let ghost = null;
let draggedEl = null;
let offsetX = 0;
let offsetY = 0;
let active = false;
let snapRects = /* @__PURE__ */ new Map();
let originalOrder = [];
let currentTargetIndex = -1;
let draggedIndex = -1;
let lastFlipRects = /* @__PURE__ */ new Map();
function getCards() {
return Array.from(container.querySelectorAll(":scope > .sc-card"));
}
function getCardEl(el) {
if (!(el instanceof HTMLElement))
return null;
if (el.closest('button, a, [contenteditable="true"], .sc-tag, .sc-copy-btn'))
return null;
return el.closest(".sc-card");
}
function computeTargetIndex(clientX, clientY) {
const others = originalOrder.filter((_, i) => i !== draggedIndex);
if (others.length === 0)
return 0;
let closest = null;
let closestDist = Infinity;
for (const card of others) {
const r2 = snapRects.get(card);
const cx = r2.left + r2.width / 2;
const cy = r2.top + r2.height / 2;
const d = Math.hypot(clientX - cx, clientY - cy);
if (d < closestDist) {
closestDist = d;
closest = card;
}
}
if (!closest)
return draggedIndex;
const closestOrigIdx = originalOrder.indexOf(closest);
const r = snapRects.get(closest);
const isGrid = others.some((c) => {
if (c === closest)
return false;
return Math.abs(snapRects.get(c).top - r.top) < 10;
});
const insertBefore = isGrid ? clientX < r.left + r.width / 2 : clientY < r.top + r.height / 2;
if (insertBefore) {
return closestOrigIdx <= draggedIndex ? closestOrigIdx : closestOrigIdx - 1;
} else {
return closestOrigIdx >= draggedIndex ? closestOrigIdx : closestOrigIdx + 1;
}
}
function readFlipRects(targetIdx) {
const newOrder = originalOrder.slice();
newOrder.splice(draggedIndex, 1);
newOrder.splice(targetIdx, 0, draggedEl);
originalOrder.forEach((c) => {
c.setCssProps({ "transition": "none", "transform": "" });
});
newOrder.forEach((card) => container.appendChild(card));
const rects = /* @__PURE__ */ new Map();
originalOrder.forEach((c) => rects.set(c, c.getBoundingClientRect()));
originalOrder.forEach((card) => container.appendChild(card));
return rects;
}
function applyFlip(flipRects) {
for (const card of originalOrder) {
if (card === draggedEl)
continue;
card.setCssProps({ "transition": "none", "transform": card.style.transform || "" });
}
void container.offsetHeight;
for (const card of originalOrder) {
if (card === draggedEl)
continue;
const from = snapRects.get(card);
const to = flipRects.get(card);
const dx = to.left - from.left;
const dy = to.top - from.top;
card.setCssProps({
"transition": "transform 150ms cubic-bezier(0.25,0.46,0.45,0.94)",
"transform": dx === 0 && dy === 0 ? "" : `translate(${dx}px,${dy}px)`
});
}
}
function clearTransforms() {
getCards().forEach((c) => {
c.setCssProps({ "transition": "", "transform": "" });
});
}
let dragStartX = 0;
let dragStartY = 0;
let dragPending = false;
const DRAG_THRESHOLD = 5;
const TOUCH_HOLD_MS = 300;
let touchHoldTimer = null;
let touchHoldReady = false;
const onNativeDragStart = () => {
if (!dragPending && !active)
return;
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
if (touchHoldTimer) {
clearTimeout(touchHoldTimer);
touchHoldTimer = null;
}
touchHoldReady = false;
ghost == null ? void 0 : ghost.remove();
ghost = null;
if (draggedEl) {
draggedEl.setCssProps({ "opacity": "", "pointer-events": "" });
}
clearTransforms();
draggedEl = null;
active = false;
dragPending = false;
snapRects = /* @__PURE__ */ new Map();
lastFlipRects = /* @__PURE__ */ new Map();
originalOrder = [];
currentTargetIndex = -1;
draggedIndex = -1;
};
const onPointerDown = (e) => {
if (getSortMode() !== "manual")
return;
if (e.button !== 0 && e.pointerType === "mouse")
return;
const card = getCardEl(e.target);
if (!card)
return;
draggedEl = card;
originalOrder = getCards();
draggedIndex = originalOrder.indexOf(card);
currentTargetIndex = draggedIndex;
dragStartX = e.clientX;
dragStartY = e.clientY;
dragPending = true;
touchHoldReady = e.pointerType !== "touch";
if (e.pointerType === "touch") {
touchHoldTimer = setTimeout(() => {
touchHoldReady = true;
}, TOUCH_HOLD_MS);
}
snapRects = /* @__PURE__ */ new Map();
originalOrder.forEach((c) => snapRects.set(c, c.getBoundingClientRect()));
const rect = snapRects.get(card);
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
document.addEventListener("pointermove", onPointerMove);
document.addEventListener("pointerup", onPointerUp);
};
function activateDrag(e) {
if (!draggedEl)
return;
dragPending = false;
active = true;
const rect = snapRects.get(draggedEl);
draggedEl.setCssProps({ "opacity": "0", "pointer-events": "none" });
ghost = draggedEl.cloneNode(true);
ghost.classList.add("sc-drag-ghost");
const cs = getComputedStyle(draggedEl);
ghost.setCssProps({
"background": cs.background,
"background-color": cs.backgroundColor,
"border-color": cs.borderColor,
"border-width": cs.borderWidth,
"border-style": cs.borderStyle,
"border-radius": cs.borderRadius,
"color": cs.color,
"font-size": cs.fontSize,
"line-height": cs.lineHeight,
"padding": cs.padding,
"position": "fixed",
"z-index": "9999",
"pointer-events": "none",
"width": rect.width + "px",
"height": rect.height + "px",
"left": e.clientX - offsetX + "px",
"top": e.clientY - offsetY + "px",
"margin": "0",
"box-shadow": "0 8px 24px rgba(0,0,0,0.25)",
"opacity": "1",
"cursor": "grabbing",
"transform": "",
"transition": ""
});
document.body.appendChild(ghost);
}
const onPointerMove = (e) => {
if (dragPending) {
const dx = e.clientX - dragStartX;
const dy = e.clientY - dragStartY;
if (Math.hypot(dx, dy) >= DRAG_THRESHOLD) {
if (!touchHoldReady) {
if (touchHoldTimer) {
clearTimeout(touchHoldTimer);
touchHoldTimer = null;
}
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
dragPending = false;
draggedEl = null;
snapRects = /* @__PURE__ */ new Map();
originalOrder = [];
return;
}
if (e.pointerType === "touch")
e.preventDefault();
activateDrag(e);
} else {
return;
}
}
if (!active || !ghost || !draggedEl)
return;
if (e.pointerType === "touch")
e.preventDefault();
ghost.setCssProps({
"left": e.clientX - offsetX + "px",
"top": e.clientY - offsetY + "px"
});
const newTarget = computeTargetIndex(e.clientX, e.clientY);
if (newTarget !== currentTargetIndex) {
currentTargetIndex = newTarget;
lastFlipRects = readFlipRects(currentTargetIndex);
applyFlip(lastFlipRects);
}
};
const onPointerUp = () => {
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
if (touchHoldTimer) {
clearTimeout(touchHoldTimer);
touchHoldTimer = null;
}
touchHoldReady = false;
if (dragPending) {
dragPending = false;
draggedEl = null;
snapRects = /* @__PURE__ */ new Map();
originalOrder = [];
return;
}
if (!active || !draggedEl)
return;
active = false;
ghost == null ? void 0 : ghost.remove();
ghost = null;
draggedEl.setCssProps({ "opacity": "", "pointer-events": "" });
clearTransforms();
const newOrder = originalOrder.slice();
newOrder.splice(draggedIndex, 1);
newOrder.splice(currentTargetIndex, 0, draggedEl);
newOrder.forEach((card) => container.appendChild(card));
const ids = newOrder.map((c) => {
var _a;
return (_a = c.dataset.id) != null ? _a : "";
}).filter(Boolean);
plugin.settings.manualOrder = ids;
void onReorder(ids);
onPlaceholderMoved == null ? void 0 : onPlaceholderMoved();
draggedEl = null;
snapRects = /* @__PURE__ */ new Map();
lastFlipRects = /* @__PURE__ */ new Map();
originalOrder = [];
currentTargetIndex = -1;
draggedIndex = -1;
};
container.addEventListener("pointerdown", onPointerDown);
container.addEventListener("dragstart", onNativeDragStart);
return () => {
container.removeEventListener("pointerdown", onPointerDown);
container.removeEventListener("dragstart", onNativeDragStart);
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
if (touchHoldTimer) {
clearTimeout(touchHoldTimer);
touchHoldTimer = null;
}
touchHoldReady = false;
ghost == null ? void 0 : ghost.remove();
if (draggedEl) {
draggedEl.setCssProps({ "opacity": "", "pointer-events": "" });
}
clearTransforms();
ghost = null;
draggedEl = null;
active = false;
dragPending = false;
snapRects = /* @__PURE__ */ new Map();
lastFlipRects = /* @__PURE__ */ new Map();
originalOrder = [];
currentTargetIndex = -1;
draggedIndex = -1;
};
}
function attachPinnedListDragToReorder(container, getPaths, onReorder) {
let ghost = null;
let draggedEl = null;
let draggedPath = "";
let offsetY = 0;
let active = false;
let indicator = null;
let dropIndex = -1;
function getItems() {
return Array.from(container.querySelectorAll(":scope > .sc-home-file-item"));
}
function getItemEl(el) {
if (!(el instanceof HTMLElement))
return null;
return el.closest(".sc-home-file-item");
}
function getOrCreateIndicator() {
if (!indicator) {
indicator = document.createElement("div");
indicator.className = "sc-pinned-drop-indicator";
indicator.setCssStyles({ position: "absolute", left: "0", right: "0", height: "2px", background: "var(--interactive-accent)", borderRadius: "2px", pointerEvents: "none", zIndex: "100", display: "none" });
}
return indicator;
}
function computeDropIndex(clientY) {
const items = getItems().filter((i) => i !== draggedEl);
if (items.length === 0)
return 0;
for (let i = 0; i < items.length; i++) {
const r = items[i].getBoundingClientRect();
if (clientY < r.top + r.height / 2)
return i;
}
return items.length;
}
function positionIndicator(idx) {
const ind = getOrCreateIndicator();
const items = getItems().filter((i) => i !== draggedEl);
const containerRect = container.getBoundingClientRect();
let top;
if (items.length === 0) {
top = 0;
} else if (idx >= items.length) {
const last = items[items.length - 1].getBoundingClientRect();
top = last.bottom - containerRect.top;
} else {
const target = items[idx].getBoundingClientRect();
top = target.top - containerRect.top;
}
ind.setCssProps({ "display": "block", "top": top - 1 + "px" });
if (!ind.parentElement) {
container.setCssProps({ "position": "relative" });
container.appendChild(ind);
}
}
const onPointerDown = (e) => {
var _a;
if (e.button !== 0 && e.pointerType === "mouse")
return;
const item = getItemEl(e.target);
if (!item)
return;
draggedEl = item;
draggedPath = (_a = item.dataset.path) != null ? _a : "";
if (!draggedPath)
return;
const rect = item.getBoundingClientRect();
offsetY = e.clientY - rect.top;
ghost = item.cloneNode(true);
ghost.classList.add("sc-drag-ghost");
const cs = getComputedStyle(item);
ghost.setCssStyles({
position: "fixed",
zIndex: "9999",
pointerEvents: "none",
width: rect.width + "px",
height: rect.height + "px",
left: rect.left + "px",
top: rect.top + "px",
background: cs.background,
backgroundColor: cs.backgroundColor,
borderRadius: cs.borderRadius,
padding: cs.padding,
fontSize: cs.fontSize,
color: cs.color,
boxShadow: "0 4px 16px rgba(0,0,0,0.2)",
opacity: "0.9",
cursor: "grabbing"
});
document.body.appendChild(ghost);
item.setCssProps({ "opacity": "0.3" });
active = true;
dropIndex = computeDropIndex(e.clientY);
positionIndicator(dropIndex);
document.addEventListener("pointermove", onPointerMove);
document.addEventListener("pointerup", onPointerUp);
e.preventDefault();
};
const onPointerMove = (e) => {
if (!active || !ghost)
return;
if (e.pointerType === "touch")
e.preventDefault();
ghost.setCssProps({ "top": e.clientY - offsetY + "px" });
dropIndex = computeDropIndex(e.clientY);
positionIndicator(dropIndex);
};
const onPointerUp = () => {
if (!active)
return;
active = false;
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
ghost == null ? void 0 : ghost.remove();
ghost = null;
if (indicator)
indicator.setCssProps({ "display": "none" });
if (draggedEl)
draggedEl.setCssProps({ "opacity": "" });
const paths = getPaths().slice();
const fromIdx = paths.indexOf(draggedPath);
if (fromIdx !== -1 && dropIndex !== -1) {
paths.splice(fromIdx, 1);
const insertAt = fromIdx < dropIndex ? dropIndex : dropIndex;
paths.splice(insertAt, 0, draggedPath);
void onReorder(paths);
}
draggedEl = null;
draggedPath = "";
dropIndex = -1;
};
container.addEventListener("pointerdown", onPointerDown);
return () => {
container.removeEventListener("pointerdown", onPointerDown);
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
ghost == null ? void 0 : ghost.remove();
indicator == null ? void 0 : indicator.remove();
if (draggedEl)
draggedEl.setCssProps({ "opacity": "" });
ghost = null;
draggedEl = null;
active = false;
indicator = null;
};
}
// src/views/CardSidebarView.ts
var CardSidebarView = class extends import_obsidian4.ItemView {
constructor(leaf, plugin, store, eventBus, filterService, sortService) {
super(leaf);
this.plugin = plugin;
this.store = store;
this.eventBus = eventBus;
this.filterService = filterService;
this.sortService = sortService;
this.cardComponents = /* @__PURE__ */ new Map();
this.activeFilters = { query: "", tags: [] };
this.currentSortMode = "manual";
this.sortAscending = true;
this.dragDropCleanup = null;
this._loadInProgress = false;
this._loadingEl = null;
this._loadingTimeout = null;
this._masonryObserver = null;
this._masonryMutationObserver = null;
this._masonryTimeout = null;
this.renderTimeout = null;
this.editorScope = new import_obsidian4.Scope(this.app.scope);
this.setupMockEditor();
}
setupMockEditor() {
this.editor = {
getSelection: () => {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return "";
const selectedText = sel.toString();
if (selectedText.length > 0)
return selectedText;
const wordRange = this.getWordRangeAtCaret(sel);
return wordRange ? wordRange.toString() : "";
},
replaceSelection: (text) => {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const currentRange = sel.getRangeAt(0);
const range = currentRange.collapsed ? this.getWordRangeAtCaret(sel) || currentRange : currentRange;
range.deleteContents();
const node = document.createTextNode(text);
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
},
toggleBold: () => this.toggleMarkdownWrapper("**"),
toggleItalic: () => this.toggleMarkdownWrapper("*"),
toggleHighlight: () => this.toggleMarkdownWrapper("=="),
toggleComment: () => this.toggleMarkdownWrapper("%%", "%%", true)
};
this.owner = {
editor: this.editor,
editMode: true
};
}
isWordChar(char) {
return /[A-Za-z0-9_]/.test(char);
}
getWordRangeAtCaret(selection) {
if (!selection.rangeCount)
return null;
const baseRange = selection.getRangeAt(0);
if (!baseRange.collapsed)
return baseRange;
const node = baseRange.startContainer;
if (!(node == null ? void 0 : node.data))
return null;
const text = node.data;
if (!text)
return null;
const offset = baseRange.startOffset;
const leftChar = offset > 0 ? text[offset - 1] : "";
const rightChar = offset < text.length ? text[offset] : "";
if (!this.isWordChar(leftChar) && !this.isWordChar(rightChar))
return null;
let start = offset;
let end = offset;
while (start > 0 && this.isWordChar(text[start - 1]))
start--;
while (end < text.length && this.isWordChar(text[end]))
end++;
const wordRange = document.createRange();
wordRange.setStart(node, start);
wordRange.setEnd(node, end);
return wordRange;
}
toggleMarkdownWrapper(wrapper, closeWrapper, includeInnerPadding = false) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const currentRange = sel.getRangeAt(0);
const range = currentRange.collapsed ? this.getWordRangeAtCaret(sel) || currentRange : currentRange;
const selectedText = range.toString();
const endWrapper = closeWrapper != null ? closeWrapper : wrapper;
if (selectedText.length === 0) {
const text = wrapper + endWrapper;
const node = document.createTextNode(text);
range.insertNode(node);
const cursorOffset = wrapper.length;
range.setStart(node, cursorOffset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return;
}
const alreadyWrapped = selectedText.startsWith(wrapper) && selectedText.endsWith(endWrapper);
const newText = alreadyWrapped ? selectedText.slice(wrapper.length, selectedText.length - endWrapper.length) : includeInnerPadding ? `${wrapper} ${selectedText} ${endWrapper}` : `${wrapper}${selectedText}${endWrapper}`;
sel.removeAllRanges();
sel.addRange(range);
this.editor.replaceSelection(newText);
}
applySelectionWrapShortcut(event, root) {
if (this.plugin.settings.autoPairBrackets === false)
return false;
if (event.ctrlKey || event.metaKey || event.altKey)
return false;
const wrapMap = {
"[": ["[", "]"],
"(": ["(", ")"],
"{": ["{", "}"],
"`": ["`", "`"],
"%": ["%", "%"],
"=": ["=", "="],
'"': ['"', '"']
};
const pair = wrapMap[event.key];
if (!pair)
return false;
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return false;
const range = sel.getRangeAt(0);
if (!root.contains(range.commonAncestorContainer))
return false;
const selectedText = range.toString();
const [open, close] = pair;
let newText = `${open}${selectedText}${close}`;
if (event.key === "%") {
if (selectedText.startsWith("%") && selectedText.endsWith("%") && !selectedText.startsWith("%%")) {
const inner = selectedText.slice(1, -1).trim();
newText = `%% ${inner} %%`;
}
} else if (event.key === "=") {
if (selectedText.startsWith("=") && selectedText.endsWith("=") && !selectedText.startsWith("==")) {
const inner = selectedText.slice(1, -1);
newText = `==${inner}==`;
}
} else if (event.key === "[") {
if (selectedText.startsWith("[") && selectedText.endsWith("]") && !selectedText.startsWith("[[")) {
const inner = selectedText.slice(1, -1);
newText = `[[${inner}]]`;
}
}
event.preventDefault();
range.deleteContents();
const node = document.createTextNode(newText);
range.insertNode(node);
const newRange = document.createRange();
if (selectedText.length === 0) {
newRange.setStart(node, open.length);
newRange.collapse(true);
} else {
newRange.selectNode(node);
}
sel.removeAllRanges();
sel.addRange(newRange);
return true;
}
getEffectiveHotkeys(commandId) {
var _a, _b, _c, _d, _e, _f, _g;
const appAny = this.app;
const fromManager = (_b = (_a = appAny.hotkeyManager) == null ? void 0 : _a.getHotkeys) == null ? void 0 : _b.call(_a, commandId);
if (Array.isArray(fromManager) && fromManager.length > 0)
return fromManager;
const custom = (_d = (_c = appAny.hotkeyManager) == null ? void 0 : _c.customKeys) == null ? void 0 : _d[commandId];
if (Array.isArray(custom) && custom.length > 0)
return custom;
const defaults = (_g = (_f = (_e = appAny.commands) == null ? void 0 : _e.commands) == null ? void 0 : _f[commandId]) == null ? void 0 : _g.hotkeys;
if (Array.isArray(defaults) && defaults.length > 0)
return defaults;
return [];
}
getFormattingCommandIds(kind) {
var _a;
const defaults = {
bold: ["editor:toggle-bold"],
italic: ["editor:toggle-italic", "editor:toggle-emphasis"],
highlight: ["editor:toggle-highlight"],
comment: ["editor:toggle-comment"]
};
const appAny = this.app;
const commands = ((_a = appAny.commands) == null ? void 0 : _a.commands) || {};
const matcher = {
bold: /bold/i,
italic: /italic|emphasis/i,
highlight: /highlight/i,
comment: /comment/i
};
const discovered = Object.values(commands).filter((cmd) => typeof (cmd == null ? void 0 : cmd.id) === "string" && cmd.id.startsWith("editor:")).filter((cmd) => matcher[kind].test(String((cmd == null ? void 0 : cmd.name) || ""))).map((cmd) => String(cmd.id));
return Array.from(/* @__PURE__ */ new Set([...defaults[kind], ...discovered]));
}
eventMatchesHotkey(event, hotkey) {
const key = String((hotkey == null ? void 0 : hotkey.key) || "").toLowerCase();
if (!key)
return false;
const eventKey = String(event.key || "").toLowerCase();
if (eventKey !== key)
return false;
const modifierSet = new Set((hotkey.modifiers || []).map((m) => String(m).toLowerCase()));
const hasMod = modifierSet.has("mod");
const expectsCtrl = hasMod || modifierSet.has("ctrl");
const expectsMeta = hasMod || modifierSet.has("meta");
const expectsAlt = modifierSet.has("alt");
const expectsShift = modifierSet.has("shift");
if (expectsCtrl !== event.ctrlKey)
return false;
if (expectsMeta !== event.metaKey)
return false;
if (expectsAlt !== event.altKey)
return false;
if (expectsShift !== event.shiftKey)
return false;
return true;
}
applyFormattingHotkey(event, root) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return false;
const range = sel.getRangeAt(0);
if (!root.contains(range.commonAncestorContainer))
return false;
const targets = [
{ kind: "bold", run: () => this.toggleMarkdownWrapper("**") },
{ kind: "italic", run: () => this.toggleMarkdownWrapper("*") },
{ kind: "highlight", run: () => this.toggleMarkdownWrapper("==") },
{ kind: "comment", run: () => this.toggleMarkdownWrapper("%%", "%%", true) }
];
for (const target of targets) {
const commandIds = this.getFormattingCommandIds(target.kind);
const hotkeys = commandIds.flatMap((id) => this.getEffectiveHotkeys(id));
if (!hotkeys.length)
continue;
if (!hotkeys.some((h) => this.eventMatchesHotkey(event, h)))
continue;
event.preventDefault();
event.stopPropagation();
target.run();
return true;
}
return false;
}
getViewType() {
return "card-sidebar";
}
getDisplayText() {
return "Card sidebar";
}
getIcon() {
return "rectangle-horizontal";
}
async onOpen() {
const container = this.containerEl;
container.empty();
container.addClass("sc-sidebar-container");
this.currentSortMode = this.plugin.settings.sortMode || "manual";
this.sortAscending = typeof this.plugin.settings.sortAscending === "boolean" ? !!this.plugin.settings.sortAscending : true;
const mainContainer = container.createDiv("sc-sidebar-main");
this.createHeader(mainContainer);
this.createSearchBar(mainContainer);
this.cardsContainer = mainContainer.createDiv("sc-sidebar-cards-container");
this.applyScrollbarSetting();
this.createInputBox(mainContainer);
this.setupListeners();
this.setupPositionDetection();
this.setupLayoutObservers();
this.registerVaultEvents();
const openOn = this.plugin.settings.openCategoryOnLoad || "all";
const btn = mainContainer.querySelector(`.sc-category-btn[data-filter-value="${openOn}"]`);
if (btn instanceof HTMLElement)
btn.click();
this.showLoadingOverlay();
try {
await this.renderCards();
} finally {
this.hideLoadingOverlay(300);
}
}
showLoadingOverlay(maxMs = 2e3) {
const parent = this.cardsContainer || this.containerEl;
if (!parent || this._loadingEl)
return;
this._loadingEl = parent.createDiv("sc-sidebar-loading");
const box = this._loadingEl.createDiv("sc-sidebar-loading-inner");
const spinner = box.createDiv("sc-sidebar-spinner");
spinner.animate([{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }], { duration: 800, iterations: Infinity });
box.createDiv({ text: "Loading cards...", cls: "sc-sidebar-loading-text" });
this._loadingTimeout = window.setTimeout(() => this.hideLoadingOverlay(), maxMs);
}
hideLoadingOverlay(_fadeMs = 0) {
if (this._loadingTimeout) {
window.clearTimeout(this._loadingTimeout);
this._loadingTimeout = null;
}
if (this._loadingEl) {
this._loadingEl.remove();
this._loadingEl = null;
}
}
setupPositionDetection() {
const detect = () => {
let position = "right";
let current = this.containerEl;
let depth = 0;
while (current && depth < 10) {
const cls = current.className || "";
if (cls.includes("side-dock-left")) {
position = "left";
break;
}
if (cls.includes("side-dock-right")) {
position = "right";
break;
}
current = current.parentElement;
depth++;
}
if (this.plugin.settings.sidebarPosition !== position) {
this.plugin.settings.sidebarPosition = position;
void this.plugin.saveSettings();
}
};
detect();
const observer = new MutationObserver(detect);
if (this.containerEl.parentElement) {
observer.observe(this.containerEl.parentElement, { attributes: true, attributeFilter: ["class"] });
}
}
setupLayoutObservers() {
if (typeof ResizeObserver !== "undefined") {
const ro = new ResizeObserver(() => {
});
ro.observe(this.cardsContainer);
}
}
registerVaultEvents() {
this.plugin.registerEvent(this.app.vault.on("modify", async (file) => {
var _a;
if (!(file instanceof import_obsidian4.TFile))
return;
if ((_a = this.store._syncingPaths) == null ? void 0 : _a.has(file.path))
return;
const pending = this.store._pendingTagWrites.get(file.path);
if (pending) {
await this.store.handlePendingTagReapply(file, pending);
} else {
await this.store.updateCardFromNotePath(file.path);
}
}));
}
createHeader(container) {
if (this.plugin.settings.disableFilterButtons)
return;
const header = container.createDiv("sc-sidebar-header");
const filterGroup = header.createDiv("sc-category-group");
const settings = this.plugin.settings;
const cats = Array.isArray(settings.customCategories) ? settings.customCategories : [];
const defaultOrder = ["filter-all"].concat(!settings.hideTodayFilter ? ["filter-today"] : []).concat(!settings.hideTomorrowFilter ? ["filter-tomorrow"] : []).concat(!settings.hideArchivedFilterButton ? ["filter-archived"] : []).concat(cats.map((c) => String(c.id || "")));
const combinedOrder = Array.isArray(settings.allItemsOrder) && settings.allItemsOrder.length > 0 ? settings.allItemsOrder : defaultOrder;
const chips = [];
combinedOrder.forEach((itemId) => {
if (!itemId)
return;
if (itemId === "filter-all") {
chips.push({ type: "all", label: "All", value: "all" });
return;
}
if (itemId === "filter-today") {
if (!settings.hideTodayFilter)
chips.push({ type: "category", label: "Today", value: "today" });
return;
}
if (itemId === "filter-tomorrow") {
if (!settings.hideTomorrowFilter)
chips.push({ type: "category", label: "Tomorrow", value: "tomorrow" });
return;
}
if (itemId === "filter-archived") {
if (!settings.hideArchivedFilterButton)
chips.push({ type: "archived", label: "Archived", value: "archived" });
return;
}
const cat = cats.find((c) => String(c.id) === String(itemId));
if (cat && cat.showInMenu !== false) {
chips.push({ type: "category", label: cat.label || "", value: cat.id || cat.label || "" });
}
});
if (!chips.find((c) => c.value === "all")) {
chips.unshift({ type: "all", label: "All", value: "all" });
}
chips.forEach((chip) => {
var _a;
const btn = filterGroup.createEl("button", { text: chip.label });
btn.addClass("sc-category-btn");
btn.dataset.filterType = chip.type;
btn.dataset.filterValue = chip.value;
const colorKey = chip.value === "all" ? "all" : chip.value === "today" ? "today" : chip.value === "tomorrow" ? "tomorrow" : chip.value === "archived" ? "archived" : chip.value;
const customColors = (_a = settings.filterColors) == null ? void 0 : _a[colorKey];
if (customColors == null ? void 0 : customColors.bgColor)
btn.setCssProps({ "background-color": customColors.bgColor });
if (customColors == null ? void 0 : customColors.textColor) {
btn.setCssProps({
"color": customColors.textColor,
"--sc-btn-text-color": customColors.textColor
});
}
btn.addEventListener("click", () => {
void (async () => {
filterGroup.querySelectorAll(".sc-category-btn").forEach((b) => {
var _a2;
b.removeClass("active");
const bVal = b.dataset.filterValue || "";
const bColors = (_a2 = settings.filterColors) == null ? void 0 : _a2[bVal];
if (bColors == null ? void 0 : bColors.bgColor)
b.setCssProps({ "background-color": bColors.bgColor });
else
b.style.removeProperty("background-color");
if (bColors == null ? void 0 : bColors.textColor) {
b.setCssProps({
"color": bColors.textColor,
"--sc-btn-text-color": bColors.textColor
});
} else {
b.style.removeProperty("color");
b.style.removeProperty("--sc-btn-text-color");
}
});
btn.addClass("active");
if (chip.type === "archived") {
this.activeFilters.archivedOnly = true;
this.activeFilters.category = void 0;
} else if (chip.type === "all") {
this.activeFilters.archivedOnly = false;
this.activeFilters.category = void 0;
} else if (chip.type === "category") {
this.activeFilters.archivedOnly = false;
this.activeFilters.category = chip.value;
}
await this.renderCards();
})();
});
});
}
applyScrollbarSetting() {
var _a, _b;
if (!this.cardsContainer)
return;
if (this.plugin.settings.hideScrollbar) {
this.cardsContainer.addClass("hide-scrollbar");
(_a = this.containerEl.querySelector(".view-content")) == null ? void 0 : _a.addClass("hide-scrollbar");
} else {
this.cardsContainer.removeClass("hide-scrollbar");
(_b = this.containerEl.querySelector(".view-content")) == null ? void 0 : _b.removeClass("hide-scrollbar");
}
}
createSearchBar(container) {
const wrapper = container.createDiv("sc-search-wrap");
wrapper.toggleClass("sc-search-wrap--hidden", !this.plugin.settings.searchBarVisible);
const row = wrapper.createDiv("sc-search-row");
const iconSpan = row.createSpan({ cls: "sc-search-input-icon" });
try {
(0, import_obsidian4.setIcon)(iconSpan, "search");
} catch (e) {
}
const input = row.createEl("input", { type: "search", placeholder: "Search cards\u2026", cls: "sc-search-input" });
const clearBtn = row.createEl("button", { text: "\u2715", cls: "sc-search-clear-btn" });
clearBtn.toggleClass("sc-search-wrap--hidden", true);
clearBtn.title = "Clear search";
clearBtn.addEventListener("click", () => {
input.value = "";
this.activeFilters.query = "";
this.renderCardsDebounced();
clearBtn.toggleClass("sc-search-wrap--hidden", true);
input.focus();
});
input.addEventListener("input", () => {
this.activeFilters.query = input.value || "";
clearBtn.toggleClass("sc-search-wrap--hidden", !this.activeFilters.query);
this.renderCardsDebounced();
});
}
createInputBox(container) {
const inputContainer = container.createDiv("sc-sidebar-input-container");
const editorEl = inputContainer.createDiv({ cls: "sc-sidebar-input" });
editorEl.setAttribute("contenteditable", "true");
editorEl.dataset.placeholder = "Type here... (@category, #tag)";
const updatePlaceholder = () => {
var _a;
if (!((_a = editorEl.textContent) == null ? void 0 : _a.trim())) {
editorEl.addClass("is-empty");
} else {
editorEl.removeClass("is-empty");
}
};
updatePlaceholder();
editorEl.addEventListener("input", updatePlaceholder);
editorEl.addEventListener("focusin", () => {
this.app.keymap.pushScope(this.editorScope);
this.app.workspace.activeEditor = this.owner;
});
editorEl.addEventListener("blur", () => {
this.app.keymap.popScope(this.editorScope);
if (this.app.workspace.activeEditor === this.owner) {
this.app.workspace.activeEditor = null;
}
});
new InlineAutocomplete(editorEl, this.store, this.app);
editorEl.addEventListener("keydown", (e) => {
if (this.applyFormattingHotkey(e, editorEl))
return;
this.applySelectionWrapShortcut(e, editorEl);
const normalizeKey = (v) => String(v || "").toLowerCase().replace(/[\s+_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
const saveKey = normalizeKey(this.plugin.settings.saveKey || "enter");
let pressed = "";
if (e.ctrlKey)
pressed += "ctrl-";
if (e.shiftKey)
pressed += "shift-";
if (e.altKey)
pressed += "alt-";
if (e.key && e.key.toLowerCase() === "enter")
pressed += "enter";
if (pressed === saveKey || e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.altKey) {
e.preventDefault();
addButton.click();
}
});
const buttonContainer = inputContainer.createDiv("sc-sidebar-button-container");
const searchBtn = buttonContainer.createEl("button");
searchBtn.addClass("sc-icon-btn");
try {
(0, import_obsidian4.setIcon)(searchBtn, "search");
} catch (e) {
searchBtn.textContent = "Search";
}
searchBtn.title = "Toggle search";
searchBtn.addEventListener("click", () => {
const wrap = this.containerEl.querySelector(".sc-search-wrap");
if (wrap) {
const isHidden = wrap.hasClass("sc-search-wrap--hidden");
wrap.toggleClass("sc-search-wrap--hidden", !isHidden);
this.plugin.settings.searchBarVisible = isHidden;
void this.plugin.saveSettings();
}
});
const reloadBtn = buttonContainer.createEl("button");
reloadBtn.addClass("sc-icon-btn");
reloadBtn.title = "Reload cards";
try {
(0, import_obsidian4.setIcon)(reloadBtn, "refresh-cw");
} catch (e) {
reloadBtn.textContent = "Reload";
}
reloadBtn.addEventListener("click", () => {
void (async () => {
await this.renderCards(true);
new import_obsidian4.Notice("Cards reloaded");
})();
});
const sortBtn = buttonContainer.createEl("button");
sortBtn.addClass("sc-icon-btn");
sortBtn.title = "Sort";
try {
(0, import_obsidian4.setIcon)(sortBtn, "sort-desc");
} catch (e) {
sortBtn.textContent = "Sort";
}
sortBtn.addEventListener("click", (e) => {
const menu = new import_obsidian4.Menu();
const modes = [
{ key: "manual", label: "Manual" },
{ key: "created", label: "Created Time" },
{ key: "modified", label: "Modified Time" },
{ key: "alpha", label: "A \u2192 Z" },
{ key: "status", label: "Status" }
];
modes.forEach((m) => {
menu.addItem((item) => {
item.setTitle(m.label);
if (this.currentSortMode === m.key)
item.setChecked(true);
item.onClick(async () => {
this.currentSortMode = m.key;
this.plugin.settings.sortMode = m.key;
await this.plugin.saveSettings();
await this.renderCards();
});
});
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle(this.sortAscending ? "Direction: Ascending" : "Direction: Descending");
item.onClick(async () => {
this.sortAscending = !this.sortAscending;
this.plugin.settings.sortAscending = this.sortAscending;
await this.plugin.saveSettings();
await this.renderCards();
});
});
menu.showAtMouseEvent(e);
});
const pinToggleBtn = buttonContainer.createEl("button");
pinToggleBtn.addClass("sc-icon-btn");
try {
(0, import_obsidian4.setIcon)(pinToggleBtn, "pin");
} catch (e) {
pinToggleBtn.textContent = "Pin";
}
pinToggleBtn.title = "Show pinned only";
pinToggleBtn.addEventListener("click", () => {
void (async () => {
this.activeFilters.pinnedOnly = !this.activeFilters.pinnedOnly;
await this.renderCards();
})();
});
const gridToggleBtn = buttonContainer.createEl("button");
gridToggleBtn.addClass("sc-icon-btn");
try {
(0, import_obsidian4.setIcon)(gridToggleBtn, "layout-grid");
} catch (e) {
gridToggleBtn.textContent = "Grid";
}
gridToggleBtn.title = "Toggle grid layout";
gridToggleBtn.addEventListener("click", () => {
void (async () => {
this.plugin.settings.verticalCardMode = !this.plugin.settings.verticalCardMode;
await this.plugin.saveSettings();
await flipAnimateAsync(this.cardsContainer, () => {
this.applyLayoutMode();
}, {}, this.store.settings);
})();
});
const moreBtn = buttonContainer.createEl("button");
moreBtn.addClass("sc-icon-btn");
try {
(0, import_obsidian4.setIcon)(moreBtn, "more-vertical");
} catch (e) {
moreBtn.textContent = "\u2026";
}
moreBtn.title = "More";
moreBtn.addEventListener("click", (e) => {
const menu = new import_obsidian4.Menu();
menu.addItem((item) => {
var _a;
item.setTitle("Show Tags").setChecked((_a = this.plugin.settings.sidebarShowTags) != null ? _a : true).onClick(async () => {
var _a2;
const current = (_a2 = this.plugin.settings.sidebarShowTags) != null ? _a2 : true;
this.plugin.settings.sidebarShowTags = !current;
await this.plugin.saveSettings();
await this.renderCards();
});
});
menu.addItem((item) => {
var _a;
item.setTitle("Show Timestamps").setChecked((_a = this.plugin.settings.sidebarShowTimestamps) != null ? _a : this.plugin.settings.showTimestamps).onClick(async () => {
var _a2;
const current = (_a2 = this.plugin.settings.sidebarShowTimestamps) != null ? _a2 : this.plugin.settings.showTimestamps;
this.plugin.settings.sidebarShowTimestamps = !current;
await this.plugin.saveSettings();
await this.renderCards();
});
});
menu.showAtMouseEvent(e);
});
const addButton = buttonContainer.createEl("button");
addButton.addClass("sc-add-btn");
addButton.textContent = "Add card";
addButton.addEventListener("click", () => {
void (async () => {
var _a;
const content = (_a = editorEl.textContent) == null ? void 0 : _a.trim();
if (!content)
return;
const tags = [];
const tagRegex = /#([^\s#@,.]+)/g;
let match;
while ((match = tagRegex.exec(content)) !== null) {
tags.push(match[1]);
}
const catRegex = /@([^\s#@,.]+)/g;
const catMatch = catRegex.exec(content);
const category = catMatch ? catMatch[1] : this.activeFilters.category || void 0;
const cleanContent = content.replace(/@[^\s#@,.]+/g, "").replace(/#[^\s#@,.]+/g, "").replace(/\s{2,}/g, " ").trim();
const autoColor = resolveAutoColor(content, tags, this.plugin.settings);
const color = autoColor || "var(--card-color-1)";
const card = new Card({
content: cleanContent,
tags,
color,
category: category === "all" ? void 0 : category
});
await this.store.add(card);
editorEl.textContent = "";
updatePlaceholder();
})();
});
}
setupListeners() {
this.eventBus.on("card:added", () => {
void this.renderCards();
});
this.eventBus.on("card:deleted", () => {
void this.renderCards();
});
this.eventBus.on("card:updated", () => {
void this.renderCards();
});
this.eventBus.on("filter:tag", (tag) => {
if (this.activeFilters.tags.includes(tag)) {
this.activeFilters.tags = this.activeFilters.tags.filter((t) => t !== tag);
} else {
this.activeFilters.tags.push(tag);
}
void this.renderCards();
});
this.eventBus.on("card:contextmenu", ({ card, event }) => {
const menu = new import_obsidian4.Menu();
menu.addItem((item) => {
item.setTitle("Delete").setIcon("trash").onClick(() => {
void this.store.delete(card.id);
});
});
menu.addItem((item) => {
item.setTitle(card.archived ? "Unarchive" : "Archive").setIcon("archive").onClick(() => {
void this.store.update(card.id, { archived: !card.archived });
});
});
menu.showAtMouseEvent(event);
});
}
renderCardsDebounced() {
if (this.renderTimeout)
window.clearTimeout(this.renderTimeout);
this.renderTimeout = window.setTimeout(() => {
void this.renderCards();
}, 300);
}
async renderCards(_isManualReload = false) {
var _a, _b;
const settings = { ...this.store.settings };
const scrollTop = ((_a = this.cardsContainer) == null ? void 0 : _a.scrollTop) || 0;
await flipAnimateAsync(this.cardsContainer, () => {
var _a2, _b2, _c;
this.cardComponents.forEach((comp) => comp.destroy());
this.cardComponents.clear();
this.cardsContainer.empty();
let cards = this.store.getAll();
cards = this.filterService.filter(cards, this.activeFilters);
cards = this.sortService.sort(cards, this.currentSortMode, this.sortAscending, this.app);
for (const card of cards) {
const comp = new CardComponent(this.cardsContainer, card, this.store, this.app, this.plugin, {
groupTags: (_a2 = this.plugin.settings.sidebarGroupTags) != null ? _a2 : this.plugin.settings.groupTags,
showTimestamps: (_b2 = this.plugin.settings.sidebarShowTimestamps) != null ? _b2 : this.plugin.settings.showTimestamps,
showTags: (_c = this.plugin.settings.sidebarShowTags) != null ? _c : true
});
this.cardComponents.set(card.id, comp);
}
this.applyLayoutMode();
if (this.cardsContainer) {
this.cardsContainer.scrollTop = scrollTop;
}
}, {}, settings);
(_b = this.dragDropCleanup) == null ? void 0 : _b.call(this);
this.dragDropCleanup = attachDragToReorder(
this.cardsContainer,
this.plugin,
() => this.currentSortMode,
async () => {
await this.plugin.saveSettings();
},
() => {
this.refreshMasonrySpans();
}
);
}
onClose() {
return Promise.resolve();
}
applyLayoutMode() {
if (!this.cardsContainer)
return;
const grid = !!this.plugin.settings.verticalCardMode;
if (grid) {
this.cardsContainer.addClass("grid-mode");
this.cardsContainer.addClass("vertical-card-mode");
this.setupMasonryObserver();
this.refreshMasonrySpans();
} else {
this.cardsContainer.removeClass("grid-mode");
this.cardsContainer.removeClass("vertical-card-mode");
if (this._masonryObserver) {
this._masonryObserver.disconnect();
this._masonryObserver = null;
}
if (this._masonryMutationObserver) {
this._masonryMutationObserver.disconnect();
this._masonryMutationObserver = null;
}
this.cardsContainer.querySelectorAll(".sc-card").forEach((el) => {
el.style.removeProperty("grid-row-end");
});
}
}
setupMasonryObserver() {
if (!this.cardsContainer)
return;
if (typeof ResizeObserver === "undefined") {
this.setupMasonryMutationObserver();
return;
}
if (this._masonryObserver)
this._masonryObserver.disconnect();
this._masonryObserver = new ResizeObserver(() => {
if (this._masonryTimeout)
window.clearTimeout(this._masonryTimeout);
this._masonryTimeout = window.setTimeout(() => this.refreshMasonrySpans(), 50);
});
this._masonryObserver.observe(this.cardsContainer);
this.cardsContainer.querySelectorAll(".sc-card").forEach((el) => {
var _a;
(_a = this._masonryObserver) == null ? void 0 : _a.observe(el);
});
this.setupMasonryMutationObserver();
}
refreshMasonrySpans() {
if (!this.plugin.settings.verticalCardMode || !this.cardsContainer)
return;
const cards = this.cardsContainer.querySelectorAll(".sc-card:not(.drag-spacer)");
const maxH = this.plugin.settings.maxCardHeight;
const hasMaxHeight = !!maxH && maxH > 0;
cards.forEach((el) => {
const card = el;
card.style.removeProperty("grid-row-end");
if (hasMaxHeight)
card.setCssProps({ "max-height": "none" });
const naturalH = card.getBoundingClientRect().height;
if (hasMaxHeight)
card.style.removeProperty("max-height");
const h = hasMaxHeight ? Math.min(naturalH, maxH) : naturalH;
if (h > 0) {
const span = Math.max(1, Math.ceil(h + 8));
card.setCssProps({ "grid-row-end": `span ${span}` });
}
});
}
setupMasonryMutationObserver() {
if (!this.cardsContainer || typeof MutationObserver === "undefined")
return;
if (this._masonryMutationObserver)
this._masonryMutationObserver.disconnect();
let recalcTimeout = null;
const debounced = () => {
if (recalcTimeout)
window.clearTimeout(recalcTimeout);
recalcTimeout = window.setTimeout(() => this.refreshMasonrySpans(), 120);
};
this._masonryMutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === "childList" || mutation.type === "characterData" || mutation.type === "attributes" && mutation.attributeName === "class") {
debounced();
break;
}
}
});
this._masonryMutationObserver.observe(this.cardsContainer, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeFilter: ["class"]
});
}
};
// src/services/CardStore.ts
init_Card();
var import_obsidian5 = require("obsidian");
// src/utils/frontmatter.ts
function parseTagsFromFrontmatter(fm) {
const lines = fm.split(/\r?\n/);
const tagsLine = lines.find((l) => /^\s*tags\s*:/i.test(l));
if (!tagsLine)
return [];
const content = tagsLine.replace(/^\s*tags\s*:/i, "").trim();
const sanitize = (t) => t.trim().replace(/^[-#\s]+/, "").replace(/^- /g, "").trim();
if (!content) {
const tagsIdx = lines.findIndex((l) => /^\s*tags\s*:/i.test(l));
const listTags = [];
for (let i = tagsIdx + 1; i < lines.length; i++) {
const line = lines[i];
const match = line.match(/^\s*-\s+(.*)$/);
if (match) {
listTags.push(sanitize(match[1]));
} else if (line.trim() && !line.startsWith(" ")) {
break;
}
}
return listTags;
}
if (content.startsWith("[")) {
try {
const parsed = JSON.parse(content.replace(/'/g, '"'));
return Array.isArray(parsed) ? parsed.map(sanitize) : [sanitize(String(parsed))];
} catch (e) {
return content.replace(/[[\]"']/g, "").split(",").map(sanitize).filter(Boolean);
}
}
return content.split(",").map(sanitize).filter(Boolean);
}
// src/services/CardStore.ts
var CardStore = class {
constructor(app, plugin, eventBus, settings) {
this.app = app;
this.plugin = plugin;
this.eventBus = eventBus;
this.settings = settings;
this.cards = /* @__PURE__ */ new Map();
this.pendingWrites = /* @__PURE__ */ new Map();
this._pendingTagWrites = /* @__PURE__ */ new Map();
this._reapplyingTags = /* @__PURE__ */ new Set();
// Paths currently being written by syncCardToFrontmatter — skip vault modify re-reads for these
this._syncingPaths = /* @__PURE__ */ new Set();
this.expiryInterval = null;
this.dateRolloverTimeout = null;
this.loadFromSettings();
this.setupExpiryTimer();
this.handleDateRollover();
void this.runStartupDateCleanup();
}
loadFromSettings() {
this.cards.clear();
const rawCards = this.settings.cards || [];
rawCards.forEach((data) => {
const card = new Card(data);
this.cards.set(card.id, card);
});
}
parseColorIndex(value) {
if (typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 10) {
return value;
}
const raw = String(value != null ? value : "").trim();
if (!raw)
return null;
if (/^\d+$/.test(raw)) {
const parsed = Number(raw);
if (parsed >= 1 && parsed <= 10)
return parsed;
}
const varMatch = raw.match(/var\(\s*--card-color-?(\d+)\s*\)/i);
if (varMatch) {
const parsed = Number(varMatch[1]);
if (parsed >= 1 && parsed <= 10)
return parsed;
}
return null;
}
normalizeCardColorValue(value, fallback) {
const idx = this.parseColorIndex(value);
if (idx !== null) {
return { color: `var(--card-color-${idx})`, frontmatterColor: idx };
}
const raw = String(value != null ? value : "").trim();
if (raw) {
return { color: raw, frontmatterColor: null };
}
return { color: fallback, frontmatterColor: this.parseColorIndex(fallback) };
}
getFrontmatterColorValueForCard(color) {
const idx = this.parseColorIndex(color);
if (idx !== null)
return idx;
return color;
}
getAll() {
return Array.from(this.cards.values());
}
get(id) {
return this.cards.get(id);
}
async add(card) {
this.cards.set(card.id, card);
await this.persist();
await this.createNoteFromCard(card.id);
this.eventBus.emit("card:added", card);
}
async update(id, updates) {
const card = this.cards.get(id);
if (!card)
throw new Error("Card not found");
Object.assign(card, updates);
card.modified = Date.now();
this.eventBus.emit("card:updated", card);
await this.persist();
return card;
}
async delete(id) {
const card = this.cards.get(id);
this.cards.delete(id);
this.eventBus.emit("card:deleted", id);
await this.persist();
if (card == null ? void 0 : card.notePath) {
const file = this.app.vault.getAbstractFileByPath(card.notePath);
if (file instanceof import_obsidian5.TFile) {
this._syncingPaths.add(card.notePath);
try {
await this.app.fileManager.trashFile(file);
} catch (e) {
} finally {
window.setTimeout(() => this._syncingPaths.delete(card.notePath), 500);
}
}
}
}
async toggleArchive(id, archived) {
const updates = { archived };
if (!archived)
updates.expiresAt = null;
const card = await this.update(id, updates);
await this.syncCardToFrontmatter(card, updates);
}
async togglePin(id, pinned) {
const card = await this.update(id, { pinned });
await this.syncCardToFrontmatter(card, { pinned });
}
async duplicateCard(id) {
const src = this.get(id);
if (!src)
throw new Error("Card not found");
const duplicated = new Card({
...src.toJSON(),
id: Math.random().toString(36).slice(2, 10),
created: Date.now(),
notePath: null
});
await this.add(duplicated);
return duplicated;
}
async createNoteFromCard(id) {
const card = this.get(id);
if (!card)
return null;
if (card.notePath)
return card.notePath;
const folder = (this.settings.storageFolder || "").trim();
if (folder && folder !== "/" && !await this.app.vault.adapter.exists(folder)) {
await this.app.vault.createFolder(folder);
}
const fmt = this.settings.noteTitleFormat || "words3_hhmm";
const d = new Date(card.created);
const hhmm = `${d.getHours().toString().padStart(2, "0")}${d.getMinutes().toString().padStart(2, "0")}`;
let title;
if (fmt === "datetime") {
const yyyy = d.getFullYear();
const mo = (d.getMonth() + 1).toString().padStart(2, "0");
const dy = d.getDate().toString().padStart(2, "0");
title = `${yyyy}${mo}${dy} ${hhmm}`;
} else {
const wordCount = fmt === "words5_hhmm" ? 5 : 3;
const words = (card.content || "card").split(/\s+/).slice(0, wordCount).join(" ").replace(/[^a-zA-Z0-9\s-]/g, "").trim() || `card-${Date.now()}`;
title = `${words} ${hhmm}`;
}
let fileName = `${title}.md`;
let filePath = folder && folder !== "/" ? `${folder}/${fileName}` : fileName;
if (await this.app.vault.adapter.exists(filePath)) {
fileName = `${title}-${Date.now()}.md`;
filePath = folder && folder !== "/" ? `${folder}/${fileName}` : fileName;
}
const fmData = {
Tags: card.tags || [],
"card-color": this.getFrontmatterColorValueForCard(card.color)
};
if (card.archived)
fmData["Archived"] = true;
if (card.pinned)
fmData["Pinned"] = true;
if (card.category)
fmData["Category"] = card.category;
if (card.expiresAt)
fmData["Expires-At"] = new Date(card.expiresAt).toISOString();
if (card.status)
fmData["Status"] = card.status.name;
const frontmatter = "---\n" + (0, import_obsidian5.stringifyYaml)(fmData) + "---\n\n" + card.content;
await this.app.vault.create(filePath, frontmatter);
await this.update(id, { notePath: filePath });
return filePath;
}
async setCategory(id, category) {
const card = await this.update(id, { category });
await this.syncCardToFrontmatter(card, { category });
}
async setStatus(id, status) {
const card = await this.update(id, { status });
if (status && status.colorIndex) {
const colorVar = `var(--card-color-${status.colorIndex})`;
await this.update(id, { color: colorVar });
await this.syncCardToFrontmatter(card, { status, color: colorVar });
} else {
await this.syncCardToFrontmatter(card, { status });
}
}
async setExpiry(id, expiresAt) {
const card = await this.update(id, { expiresAt });
await this.syncCardToFrontmatter(card, { expiresAt });
}
async setColor(id, color) {
const card = await this.update(id, { color });
await this.syncCardToFrontmatter(card, { color });
}
async persist() {
const key = "cards";
if (this.pendingWrites.has(key)) {
await this.pendingWrites.get(key);
}
const promise = this.saveToStorage();
this.pendingWrites.set(key, promise);
await promise;
this.pendingWrites.delete(key);
}
async saveToStorage() {
this.settings.cards = this.getAll().map((c) => c.toJSON());
await this.plugin.saveSettings();
}
async importNotesFromFolderToSettings(folder, silent = false) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
const abstractFolder = this.app.vault.getAbstractFileByPath(folder);
if (!abstractFolder || !(abstractFolder instanceof import_obsidian5.TFolder)) {
if (!silent)
new import_obsidian5.Notice(`Folder "${folder}" not found`);
return;
}
const files = [];
const stack = [abstractFolder];
while (stack.length) {
const current = stack.pop();
if (!current)
break;
for (const child of current.children) {
if (child instanceof import_obsidian5.TFile) {
if (child.extension === "md")
files.push(child);
} else if (child instanceof import_obsidian5.TFolder) {
stack.push(child);
}
}
}
for (const file of files) {
const exists = this.getAll().some((c) => c.notePath === file.path);
if (!exists) {
const cache = this.app.metadataCache.getFileCache(file);
const fm = (_a = cache == null ? void 0 : cache.frontmatter) != null ? _a : {};
const raw = await this.app.vault.cachedRead(file);
const fmMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/);
const cardContent = fmMatch ? raw.slice(fmMatch[0].length).trim() : raw.trim();
const tags = Array.isArray((_b = fm["Tags"]) != null ? _b : fm["tags"]) ? ((_c = fm["Tags"]) != null ? _c : fm["tags"]).map((t) => String(t).trim()).filter(Boolean) : parseTagsFromFrontmatter(Object.keys(fm).length ? "" : (_d = fmMatch == null ? void 0 : fmMatch[0]) != null ? _d : "");
const archived = !!((_e = fm["Archived"]) != null ? _e : fm["archived"]);
const pinned = !!((_f = fm["Pinned"]) != null ? _f : fm["pinned"]);
const category = (_h = (_g = fm["Category"]) != null ? _g : fm["category"]) != null ? _h : null;
const expiresRaw = (_i = fm["Expires-At"]) != null ? _i : fm["expires-at"];
const expiresAt = expiresRaw ? new Date(String(expiresRaw)).getTime() || null : null;
let color = "var(--card-color-1)";
const colorRaw = fm["card-color"];
if (colorRaw !== void 0) {
color = this.normalizeCardColorValue(colorRaw, color).color;
}
const card = new Card({
content: cardContent,
notePath: file.path,
created: file.stat.ctime,
tags,
category: category ? String(category) : null,
expiresAt: expiresAt && !Number.isNaN(expiresAt) ? expiresAt : null,
color,
archived,
pinned
});
this.cards.set(card.id, card);
}
}
await this.persist();
if (!silent)
new import_obsidian5.Notice(`Imported ${files.length} notes from ${folder}`);
}
async switchStorageFolder(newFolder) {
const oldIds = Array.from(this.cards.keys());
this.cards.clear();
this.settings.cards = [];
await this.plugin.saveSettings();
for (const id of oldIds) {
this.eventBus.emit("card:deleted", id);
}
if (!newFolder || newFolder === "/")
return;
if (!await this.app.vault.adapter.exists(newFolder)) {
await this.app.vault.createFolder(newFolder);
}
await this.importNotesFromFolderToSettings(newFolder, true);
const allCards = this.getAll();
if (allCards.length > 0) {
this.eventBus.emit("card:added", allCards[0]);
}
}
async saveCards() {
await this.saveToStorage();
}
async updateCardFromNotePath(path) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
const card = this.getAll().find((c) => c.notePath === path);
if (!card)
return;
const file = this.app.vault.getAbstractFileByPath(path);
if (!(file instanceof import_obsidian5.TFile))
return;
const cache = this.app.metadataCache.getFileCache(file);
const fm = (_a = cache == null ? void 0 : cache.frontmatter) != null ? _a : {};
const content = await this.app.vault.cachedRead(file);
const fmMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/);
const cardContent = fmMatch ? content.slice(fmMatch[0].length).trim() : content.trim();
const tags = Array.isArray((_b = fm["Tags"]) != null ? _b : fm["tags"]) ? ((_c = fm["Tags"]) != null ? _c : fm["tags"]).map((t) => String(t).trim()).filter(Boolean) : parseTagsFromFrontmatter((_d = fmMatch == null ? void 0 : fmMatch[0]) != null ? _d : "");
const archived = !!((_e = fm["Archived"]) != null ? _e : fm["archived"]);
const pinned = !!((_f = fm["Pinned"]) != null ? _f : fm["pinned"]);
const category = (_h = (_g = fm["Category"]) != null ? _g : fm["category"]) != null ? _h : null;
const expiresRaw = (_i = fm["Expires-At"]) != null ? _i : fm["expires-at"];
const expiresAt = expiresRaw ? new Date(String(expiresRaw)).getTime() || null : null;
const colorRaw = fm["card-color"];
const normalizedColor = this.normalizeCardColorValue(colorRaw, card.color);
const statusRaw = (_j = fm["Status"]) != null ? _j : fm["status"];
await this.update(card.id, {
tags,
archived,
pinned,
category: category ? String(category) : null,
expiresAt: expiresAt && !Number.isNaN(expiresAt) ? expiresAt : null,
status: statusRaw ? { name: String(statusRaw), color: ((_k = card.status) == null ? void 0 : _k.color) || "", textColor: ((_l = card.status) == null ? void 0 : _l.textColor) || "#000" } : null,
color: normalizedColor.color,
created: file.stat.ctime,
content: cardContent
});
if (colorRaw !== void 0 && normalizedColor.frontmatterColor !== null && String(colorRaw) !== String(normalizedColor.frontmatterColor)) {
await this.app.fileManager.processFrontMatter(file, (fmObj) => {
fmObj["card-color"] = normalizedColor.frontmatterColor;
});
}
}
async handlePendingTagReapply(file, pending) {
var _a, _b, _c;
try {
const desired = Array.isArray(pending.tags) ? pending.tags.map((t) => String(t).trim()).filter(Boolean) : [];
const cache = this.app.metadataCache.getFileCache(file);
const rawTags = (_c = (_a = cache == null ? void 0 : cache.frontmatter) == null ? void 0 : _a["Tags"]) != null ? _c : (_b = cache == null ? void 0 : cache.frontmatter) == null ? void 0 : _b["tags"];
const existing = Array.isArray(rawTags) ? rawTags.map((t) => String(t).trim()) : [];
if (existing.length === desired.length && desired.every((t) => existing.includes(t))) {
this._pendingTagWrites.delete(file.path);
return;
}
this._reapplyingTags.add(file.path);
await this.app.fileManager.processFrontMatter(file, (fm) => {
fm["Tags"] = desired;
});
} catch (e) {
console.error("Error reapplying tags:", e);
} finally {
this._reapplyingTags.delete(file.path);
this._pendingTagWrites.delete(file.path);
}
}
setupExpiryTimer() {
if (this.expiryInterval) {
window.clearInterval(this.expiryInterval);
}
this.expiryInterval = window.setInterval(() => {
void (async () => {
const now = Date.now();
for (const card of this.getAll()) {
if (!card.expiresAt || card.archived)
continue;
if (card.expiresAt <= now) {
if (this.settings.autoArchiveOnExpiry) {
const updated = await this.update(card.id, { archived: true, expiresAt: null });
await this.syncCardToFrontmatter(updated, { archived: true, expiresAt: null });
} else {
await this.delete(card.id);
}
}
}
})();
}, 1e3);
}
async runStartupDateCleanup() {
const todayStart = new Date();
todayStart.setHours(0, 0, 0, 0);
const todayStartMs = todayStart.getTime();
const tomorrowCards = this.getAll().filter((c) => {
var _a, _b;
if (String(c.category || "").toLowerCase() !== "tomorrow")
return false;
const lastActivity = Math.max((_a = c.modified) != null ? _a : 0, (_b = c.created) != null ? _b : 0);
return lastActivity < todayStartMs;
});
for (const card of tomorrowCards) {
await this.setCategory(card.id, "today");
}
await this.cleanupStaleTodayCards(todayStartMs);
}
async cleanupStaleTodayCards(todayStartMs) {
const stale = this.getAll().filter((c) => {
var _a, _b;
if (String(c.category || "").toLowerCase() !== "today")
return false;
const lastActivity = Math.max((_a = c.modified) != null ? _a : 0, (_b = c.created) != null ? _b : 0);
return lastActivity < todayStartMs;
});
for (const card of stale) {
await this.setCategory(card.id, null);
}
}
handleDateRollover() {
if (this.dateRolloverTimeout) {
window.clearTimeout(this.dateRolloverTimeout);
}
const now = new Date();
const next = new Date(now);
next.setHours(24, 0, 1, 0);
const delay = Math.max(1e3, next.getTime() - now.getTime());
this.dateRolloverTimeout = window.setTimeout(() => {
void (async () => {
await this.runStartupDateCleanup();
this.handleDateRollover();
})();
}, delay);
}
async syncCardToFrontmatter(card, updates) {
if (!card.notePath)
return;
const file = this.app.vault.getAbstractFileByPath(card.notePath);
if (!(file instanceof import_obsidian5.TFile))
return;
this._syncingPaths.add(card.notePath);
try {
await this.app.fileManager.processFrontMatter(file, (fm) => {
var _a, _b;
if (typeof updates.archived !== "undefined") {
if (updates.archived)
fm["Archived"] = true;
else
delete fm["Archived"];
}
if (typeof updates.pinned !== "undefined") {
if (updates.pinned)
fm["Pinned"] = true;
else
delete fm["Pinned"];
}
if (typeof updates.category !== "undefined") {
if (updates.category)
fm["Category"] = updates.category;
else
delete fm["Category"];
}
if (typeof updates.expiresAt !== "undefined") {
if (updates.expiresAt)
fm["Expires-At"] = new Date(updates.expiresAt).toISOString();
else
delete fm["Expires-At"];
}
if (typeof updates.color !== "undefined") {
const normalizedColor = this.normalizeCardColorValue(updates.color, card.color);
fm["card-color"] = (_a = normalizedColor.frontmatterColor) != null ? _a : normalizedColor.color;
}
if (typeof updates.status !== "undefined") {
if ((_b = updates.status) == null ? void 0 : _b.name)
fm["Status"] = updates.status.name;
else
delete fm["Status"];
}
});
} finally {
window.setTimeout(() => this._syncingPaths.delete(card.notePath), 500);
}
}
async migrateCardColorFrontmatterFormat() {
var _a;
const seen = /* @__PURE__ */ new Set();
for (const card of this.getAll()) {
if (!card.notePath || seen.has(card.notePath))
continue;
seen.add(card.notePath);
const file = this.app.vault.getAbstractFileByPath(card.notePath);
if (!(file instanceof import_obsidian5.TFile))
continue;
const cache = this.app.metadataCache.getFileCache(file);
const colorRaw = (_a = cache == null ? void 0 : cache.frontmatter) == null ? void 0 : _a["card-color"];
if (colorRaw === void 0)
continue;
const idx = this.parseColorIndex(colorRaw);
if (idx === null)
continue;
if (String(colorRaw) === String(idx))
continue;
await this.app.fileManager.processFrontMatter(file, (fm) => {
fm["card-color"] = idx;
});
}
}
};
// src/services/FilterService.ts
var FilterService = class {
filter(cards, filters) {
return cards.filter((card) => {
if (filters.archivedOnly && !card.archived)
return false;
if (!filters.archivedOnly && card.archived)
return false;
if (filters.pinnedOnly && !card.pinned)
return false;
if (filters.untaggedOnly && (card.tags.length || card.category))
return false;
if (filters.tags && filters.tags.length > 0) {
const cardTags = new Set(card.tags.map((t) => t.toLowerCase()));
if (!filters.tags.every((t) => cardTags.has(t.toLowerCase())))
return false;
}
if (filters.category) {
const cat = filters.category.toLowerCase();
const cardCat = (card.category || "").toLowerCase();
if (cat !== cardCat)
return false;
}
if (filters.query) {
const q = filters.query.toLowerCase();
const content = card.content.toLowerCase();
const tags = card.tags.join(" ").toLowerCase();
if (!content.includes(q) && !tags.includes(q))
return false;
}
return true;
});
}
matches(card, filters) {
return this.filter([card], filters).length > 0;
}
};
// src/services/SortService.ts
var import_obsidian6 = require("obsidian");
var SortService = class {
constructor(settings) {
this.settings = settings;
}
sort(cards, mode, ascending, app) {
const sorted = [...cards];
const getCreated = (c) => {
if (app && c.notePath) {
const file = app.vault.getAbstractFileByPath(c.notePath);
if (file instanceof import_obsidian6.TFile)
return file.stat.ctime;
}
return c.created;
};
const getModified = (c) => {
if (app && c.notePath) {
const file = app.vault.getAbstractFileByPath(c.notePath);
if (file instanceof import_obsidian6.TFile)
return file.stat.mtime;
}
return typeof c.modified === "number" ? c.modified : c.created;
};
switch (mode) {
case "manual":
return this.sortManual(sorted, ascending);
case "created":
return sorted.sort((a, b) => {
const ac = getCreated(a), bc = getCreated(b);
const primary = ascending ? ac - bc : bc - ac;
if (primary !== 0)
return primary;
const am = getModified(a), bm = getModified(b);
const secondary = ascending ? am - bm : bm - am;
if (secondary !== 0)
return secondary;
return ascending ? a.content.localeCompare(b.content) : b.content.localeCompare(a.content);
});
case "modified":
return sorted.sort((a, b) => {
const am = getModified(a), bm = getModified(b);
const primary = ascending ? am - bm : bm - am;
if (primary !== 0)
return primary;
const ac = getCreated(a), bc = getCreated(b);
const secondary = ascending ? ac - bc : bc - ac;
if (secondary !== 0)
return secondary;
return ascending ? a.content.localeCompare(b.content) : b.content.localeCompare(a.content);
});
case "alpha":
return sorted.sort(
(a, b) => ascending ? a.content.localeCompare(b.content) : b.content.localeCompare(a.content)
);
case "status":
return sorted.sort((a, b) => {
const list = this.settings.cardStatuses || [];
const indexFor = (c) => {
if (!c.status)
return Infinity;
const idx = list.findIndex((s) => (s.name || "").toLowerCase() === (c.status.name || "").toLowerCase());
return idx >= 0 ? idx : Infinity;
};
const ai = indexFor(a);
const bi = indexFor(b);
if (ai !== bi)
return ascending ? ai - bi : bi - ai;
return ascending ? a.content.localeCompare(b.content) : b.content.localeCompare(a.content);
});
default:
return sorted;
}
}
sortManual(cards, ascending) {
const order = this.settings.manualOrder || [];
const orderMap = new Map(order.map((id, i) => [id, i]));
return cards.sort((a, b) => {
if (a.pinned && !b.pinned)
return -1;
if (!a.pinned && b.pinned)
return 1;
const aIdx = orderMap.has(a.id) ? orderMap.get(a.id) : Infinity;
const bIdx = orderMap.has(b.id) ? orderMap.get(b.id) : Infinity;
if (aIdx !== bIdx)
return aIdx - bIdx;
return ascending ? a.created - b.created : b.created - a.created;
});
}
};
// src/core/EventBus.ts
var EventBus = class {
constructor() {
this.listeners = /* @__PURE__ */ new Map();
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, /* @__PURE__ */ new Set());
}
this.listeners.get(event).add(callback);
return () => {
var _a;
return (_a = this.listeners.get(event)) == null ? void 0 : _a.delete(callback);
};
}
emit(event, data) {
var _a;
(_a = this.listeners.get(event)) == null ? void 0 : _a.forEach((cb) => {
try {
void cb(data);
} catch (e) {
console.error(`Error in event handler for ${event}:`, e);
}
});
}
async emitAsync(event, data) {
const handlers = Array.from(this.listeners.get(event) || []);
await Promise.all(handlers.map((cb) => {
try {
return Promise.resolve(cb(data));
} catch (e) {
console.error(`Error in async event handler for ${event}:`, e);
return Promise.resolve();
}
}));
}
};
// src/core/Settings.ts
var DEFAULT_SETTINGS = {
storageFolder: "",
cards: [],
cardStyle: 2,
cardBgOpacity: 0.08,
cardBorderShadowOpacity: 1,
borderThickness: 2,
disableFilterButtons: false,
disableTimeBasedFiltering: false,
enableCustomCategories: true,
customCategories: [],
hideArchivedFilterButton: false,
animatedCards: true,
disableCardFadeIn: false,
openCategoryOnLoad: "all",
manualOrder: [],
showTimestamps: true,
omitTagHash: false,
groupTags: false,
disableCardRendering: false,
minCardWidth: 250,
colorNames: ["Gray", "Red", "Orange", "Yellow", "Green", "Blue", "Purple", "Magenta", "Pink", "Brown"],
color1: "#8392a4",
color2: "#eb3b5a",
color3: "#fa8231",
color4: "#e5a216",
color5: "#20bf6b",
color6: "#2d98da",
color7: "#8854d0",
color8: "#e832c1",
color9: "#e83289",
color10: "#965b3b",
twoRowSwatches: false,
verticalCardMode: false,
filterColors: {},
saveKey: "enter",
nextLineKey: "shift-enter",
autoPairBrackets: true,
sortMode: "manual",
sortAscending: true,
showPinnedOnly: false,
enableCardStatus: false,
cardStatuses: [],
autoArchiveOnExpiry: false,
borderRadius: 6,
buttonPadding: 26,
buttonPaddingBottom: 26,
maxCardHeight: 0,
datetimeFormat: "YYYY-MM-DD HH:mm",
timestampBelowTags: false,
tutorialShown: false,
enableCopyCardContent: false,
hideScrollbar: false,
autoOpen: false,
allItemsOrder: [],
builtinCategoryIcons: { today: "calendar-check", tomorrow: "calendar-plus", archived: "archive" },
autoColorRules: [],
inheritStatusColor: false,
replaceHomepageWithSidecards: false,
searchBarVisible: false,
pinnedNotes: [],
homepageName: "SideCards",
hideCategoryDropdown: false,
hideColorSwatches: false,
showPinnedNotes: true,
showRecentNotes: true,
recentNotesLimit: 5,
notesPlacement: "left",
noteTitleFormat: "words3_hhmm",
showExpiryTimeLeft: false,
expiryTimeFormat: "human",
homepageMaxWidth: 1e3,
homepageTopMargin: 70
};
// src/views/modals/QuickCardWithFilterModal.ts
var import_obsidian7 = require("obsidian");
init_dom();
init_Card();
var QuickCardWithFilterModal = class extends import_obsidian7.Modal {
constructor(app, plugin, store) {
super(app);
this.plugin = plugin;
this.store = store;
this.editorScope = new import_obsidian7.Scope(this.app.scope);
this.setupMockEditor();
}
setupMockEditor() {
this.editor = {
getSelection: () => {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return "";
const selectedText = sel.toString();
if (selectedText.length > 0)
return selectedText;
const wordRange = this.getWordRangeAtCaret(sel);
return wordRange ? wordRange.toString() : "";
},
replaceSelection: (text) => {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const currentRange = sel.getRangeAt(0);
const range = currentRange.collapsed ? this.getWordRangeAtCaret(sel) || currentRange : currentRange;
range.deleteContents();
const node = document.createTextNode(text);
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
},
toggleBold: () => this.toggleMarkdownWrapper("**"),
toggleItalic: () => this.toggleMarkdownWrapper("*"),
toggleHighlight: () => this.toggleMarkdownWrapper("=="),
toggleComment: () => this.toggleMarkdownWrapper("%%", "%%", true)
};
this.owner = {
editor: this.editor,
editMode: true
};
}
isWordChar(char) {
return /[A-Za-z0-9_]/.test(char);
}
getWordRangeAtCaret(selection) {
if (!selection.rangeCount)
return null;
const baseRange = selection.getRangeAt(0);
if (!baseRange.collapsed)
return baseRange;
const node = baseRange.startContainer;
if (!(node instanceof Text))
return null;
const text = node.data;
if (!text)
return null;
const offset = baseRange.startOffset;
const leftChar = offset > 0 ? text[offset - 1] : "";
const rightChar = offset < text.length ? text[offset] : "";
if (!this.isWordChar(leftChar) && !this.isWordChar(rightChar))
return null;
let start = offset;
let end = offset;
while (start > 0 && this.isWordChar(text[start - 1]))
start--;
while (end < text.length && this.isWordChar(text[end]))
end++;
const wordRange = document.createRange();
wordRange.setStart(node, start);
wordRange.setEnd(node, end);
return wordRange;
}
toggleMarkdownWrapper(wrapper, closeWrapper, includeInnerPadding = false) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const currentRange = sel.getRangeAt(0);
const range = currentRange.collapsed ? this.getWordRangeAtCaret(sel) || currentRange : currentRange;
const selectedText = range.toString();
const endWrapper = closeWrapper != null ? closeWrapper : wrapper;
if (selectedText.length === 0) {
const text = wrapper + endWrapper;
const node = document.createTextNode(text);
range.insertNode(node);
const cursorOffset = wrapper.length;
range.setStart(node, cursorOffset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return;
}
const alreadyWrapped = selectedText.startsWith(wrapper) && selectedText.endsWith(endWrapper);
const newText = alreadyWrapped ? selectedText.slice(wrapper.length, selectedText.length - endWrapper.length) : includeInnerPadding ? `${wrapper} ${selectedText} ${endWrapper}` : `${wrapper}${selectedText}${endWrapper}`;
sel.removeAllRanges();
sel.addRange(range);
this.editor.replaceSelection(newText);
}
applySelectionWrapShortcut(event, root) {
if (event.ctrlKey || event.metaKey || event.altKey)
return false;
const wrapMap = {
"[": ["[", "]"],
"(": ["(", ")"],
"{": ["{", "}"],
"`": ["`", "`"]
};
const pair = wrapMap[event.key];
if (!pair)
return false;
const sel = window.getSelection();
if (!sel || !sel.rangeCount || sel.isCollapsed)
return false;
const range = sel.getRangeAt(0);
const selectedText = sel.toString();
if (!selectedText || !root.contains(range.commonAncestorContainer))
return false;
event.preventDefault();
const [open, close] = pair;
const newText = `${open}${selectedText}${close}`;
this.editor.replaceSelection(newText);
return true;
}
getEffectiveHotkeys(commandId) {
var _a, _b, _c, _d, _e, _f, _g;
const appAny = this.app;
const fromManager = (_b = (_a = appAny.hotkeyManager) == null ? void 0 : _a.getHotkeys) == null ? void 0 : _b.call(_a, commandId);
if (Array.isArray(fromManager) && fromManager.length > 0)
return fromManager;
const custom = (_d = (_c = appAny.hotkeyManager) == null ? void 0 : _c.customKeys) == null ? void 0 : _d[commandId];
if (Array.isArray(custom) && custom.length > 0)
return custom;
const defaults = (_g = (_f = (_e = appAny.commands) == null ? void 0 : _e.commands) == null ? void 0 : _f[commandId]) == null ? void 0 : _g.hotkeys;
if (Array.isArray(defaults) && defaults.length > 0)
return defaults;
return [];
}
getFormattingCommandIds(kind) {
var _a;
const defaults = {
bold: ["editor:toggle-bold", "custom-wrap-bold"],
italic: ["editor:toggle-italic", "editor:toggle-emphasis", "custom-wrap-italic"],
highlight: ["editor:toggle-highlight", "custom-wrap-highlight"],
comment: ["editor:toggle-comment", "custom-wrap-comment"]
};
const appAny = this.app;
const commands = ((_a = appAny.commands) == null ? void 0 : _a.commands) || {};
const matcher = {
bold: /bold/i,
italic: /italic|emphasis/i,
highlight: /highlight/i,
comment: /comment/i
};
const discovered = Object.values(commands).filter((cmd) => typeof (cmd == null ? void 0 : cmd.id) === "string" && cmd.id.startsWith("editor:")).filter((cmd) => matcher[kind].test(String((cmd == null ? void 0 : cmd.name) || ""))).map((cmd) => String(cmd.id));
return Array.from(/* @__PURE__ */ new Set([...defaults[kind], ...discovered]));
}
eventMatchesHotkey(event, hotkey) {
const key = String((hotkey == null ? void 0 : hotkey.key) || "").toLowerCase();
if (!key)
return false;
const eventKey = String(event.key || "").toLowerCase();
if (eventKey !== key)
return false;
const modifierSet = new Set((hotkey.modifiers || []).map((m) => String(m).toLowerCase()));
const hasMod = modifierSet.has("mod");
const isMac = import_obsidian7.Platform.isMacOS;
const expectsCtrl = modifierSet.has("ctrl") || hasMod && !isMac;
const expectsMeta = modifierSet.has("meta") || hasMod && isMac;
const expectsAlt = modifierSet.has("alt");
const expectsShift = modifierSet.has("shift");
if (expectsCtrl !== event.ctrlKey)
return false;
if (expectsMeta !== event.metaKey)
return false;
if (expectsAlt !== event.altKey)
return false;
if (expectsShift !== event.shiftKey)
return false;
return true;
}
applyFormattingHotkey(event, root) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return false;
const range = sel.getRangeAt(0);
if (!root.contains(range.commonAncestorContainer))
return false;
const targets = [
{ kind: "bold", run: () => this.toggleMarkdownWrapper("**") },
{ kind: "italic", run: () => this.toggleMarkdownWrapper("*") },
{ kind: "highlight", run: () => this.toggleMarkdownWrapper("==") },
{ kind: "comment", run: () => this.toggleMarkdownWrapper("%%", "%%", true) }
];
for (const target of targets) {
const commandIds = this.getFormattingCommandIds(target.kind);
const hotkeys = commandIds.flatMap((id) => this.getEffectiveHotkeys(id));
if (!hotkeys.length)
continue;
if (!hotkeys.some((h) => this.eventMatchesHotkey(event, h)))
continue;
event.preventDefault();
event.stopPropagation();
target.run();
root.dispatchEvent(new Event("input"));
return true;
}
return false;
}
getAvailableFilters() {
const filters = [
{ type: "all", label: "All", value: "all" }
];
if (!this.store.settings.hideTodayFilter) {
filters.push({ type: "category", label: "Today", value: "today" });
}
if (!this.store.settings.hideTomorrowFilter) {
filters.push({ type: "category", label: "Tomorrow", value: "tomorrow" });
}
if (this.store.settings.enableCustomCategories) {
const cats = this.store.settings.customCategories || [];
cats.forEach((cat) => {
filters.push({
type: "category",
label: cat.label,
value: cat.id || cat.label
});
});
}
if (!this.store.settings.hideArchivedFilterButton) {
filters.push({ type: "archived", label: "Archived", value: "archived" });
}
return filters;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("sc-quick-card-modal");
contentEl.createDiv({ text: "Quick card add", cls: "sc-modal-title" });
contentEl.createDiv({ text: "Card content", cls: "sc-modal-section-title" });
const editorEl = contentEl.createDiv({
cls: "sc-modal-textarea"
});
editorEl.setAttribute("contenteditable", "true");
editorEl.dataset.placeholder = "Type here... (@category, #tag)";
new InlineAutocomplete(editorEl, this.store, this.app);
editorEl.addEventListener("input", () => {
editorEl.toggleClass("is-empty", !editorEl.textContent);
});
if (!editorEl.textContent) {
editorEl.addClass("is-empty");
}
editorEl.focus();
editorEl.addEventListener("focusin", () => {
this.app.keymap.pushScope(this.editorScope);
this.app.workspace.activeEditor = this.owner;
});
editorEl.addEventListener("blur", () => {
this.app.keymap.popScope(this.editorScope);
if (this.app.workspace.activeEditor === this.owner) {
this.app.workspace.activeEditor = null;
}
});
contentEl.createDiv({ text: "Color", cls: "sc-modal-section-title" });
const colorContainer = contentEl.createDiv("sc-modal-color-container");
let selectedColor = "var(--card-color-1)";
const colors = [
{ name: "Gray", var: "var(--card-color-1)" },
{ name: "Red", var: "var(--card-color-2)" },
{ name: "Orange", var: "var(--card-color-3)" },
{ name: "Yellow", var: "var(--card-color-4)" },
{ name: "Green", var: "var(--card-color-5)" },
{ name: "Blue", var: "var(--card-color-6)" },
{ name: "Purple", var: "var(--card-color-7)" },
{ name: "Magenta", var: "var(--card-color-8)" },
{ name: "Pink", var: "var(--card-color-9)" },
{ name: "Brown", var: "var(--card-color-10)" }
];
colors.forEach((color, idx) => {
const swatch = colorContainer.createDiv("sc-modal-color-swatch");
swatch.style.backgroundColor = this.resolveColor(color.var);
swatch.title = this.store.settings.colorNames[idx] || color.name;
if (selectedColor === color.var)
swatch.addClass("is-selected");
swatch.addEventListener("click", () => {
colorContainer.querySelectorAll(".sc-modal-color-swatch").forEach((s) => s.removeClass("is-selected"));
swatch.addClass("is-selected");
selectedColor = color.var;
});
});
contentEl.createDiv({ text: "Tags", cls: "sc-modal-section-title" });
const tagsWrapper = contentEl.createDiv("sc-modal-tags-wrapper");
const tagsInput = tagsWrapper.createEl("input", {
placeholder: "Tags (comma separated)...",
cls: "sc-modal-tags-input"
});
const tagsAutocomplete = tagsWrapper.createDiv("sc-modal-tags-autocomplete");
tagsAutocomplete.addClass("sc-hidden");
let selectedTagIdx = -1;
const updateAutocomplete = () => {
const val = tagsInput.value;
const lastComma = val.lastIndexOf(",");
const currentTag = val.substring(lastComma + 1).trim().toLowerCase();
if (!currentTag) {
tagsAutocomplete.addClass("sc-hidden");
return;
}
const allTags = this.getAllTags();
const suggestions = allTags.filter((t) => t.startsWith(currentTag)).slice(0, 8);
if (suggestions.length === 0) {
tagsAutocomplete.addClass("sc-hidden");
return;
}
tagsAutocomplete.empty();
selectedTagIdx = -1;
suggestions.forEach((tag) => {
const item = tagsAutocomplete.createDiv("sc-modal-autocomplete-item");
item.textContent = tag;
item.addEventListener("click", () => {
const before = val.substring(0, lastComma + 1);
tagsInput.value = (before ? before + " " : "") + tag + ", ";
tagsAutocomplete.addClass("sc-hidden");
tagsInput.focus();
});
});
tagsAutocomplete.removeClass("sc-hidden");
};
tagsInput.addEventListener("input", updateAutocomplete);
tagsInput.addEventListener("keydown", (e) => {
if (tagsAutocomplete.hasClass("sc-hidden"))
return;
const items = tagsAutocomplete.querySelectorAll(".sc-modal-autocomplete-item");
if (e.key === "ArrowDown") {
e.preventDefault();
selectedTagIdx = (selectedTagIdx + 1) % items.length;
items.forEach((it, i) => it.toggleClass("is-selected", i === selectedTagIdx));
} else if (e.key === "ArrowUp") {
e.preventDefault();
selectedTagIdx = (selectedTagIdx - 1 + items.length) % items.length;
items.forEach((it, i) => it.toggleClass("is-selected", i === selectedTagIdx));
} else if (e.key === "Enter" && selectedTagIdx >= 0) {
e.preventDefault();
items[selectedTagIdx].click();
}
});
contentEl.createDiv({ text: "Apply category", cls: "sc-modal-section-title" });
const select = contentEl.createEl("select", { cls: "sc-modal-select" });
this.getAvailableFilters().forEach((f) => {
const opt = select.createEl("option", { value: f.value, text: f.label });
opt.dataset.type = f.type;
});
const btnContainer = contentEl.createDiv("sc-modal-buttons");
const cancelBtn = btnContainer.createEl("button", { text: "Cancel" });
cancelBtn.addEventListener("click", () => this.close());
const createBtn = btnContainer.createEl("button", { text: "Create card", cls: "mod-cta" });
const handleCreate = async () => {
var _a, _b;
const content = (_a = editorEl.textContent) == null ? void 0 : _a.trim();
if (!content) {
new import_obsidian7.Notice("Content cannot be empty");
return;
}
const tags = tagsInput.value.split(",").map((t) => t.trim()).filter((t) => !!t);
const category = select.value === "all" ? null : select.value;
const inlineCatMatch = /@([^\s#@,.]+)/.exec(content);
const effectiveCategory = inlineCatMatch ? inlineCatMatch[1] : category;
const cleanContent = content.replace(/@[^\s#@,.]+/g, "").replace(/\s{2,}/g, " ").trim();
const autoColor = resolveAutoColor(content, tags, this.store.settings);
const effectiveColor = autoColor || selectedColor;
const card = new Card({ content: cleanContent, color: effectiveColor, tags, category: effectiveCategory === "all" ? null : effectiveCategory });
await this.store.add(card);
if (category) {
const view = (_b = this.app.workspace.getLeavesOfType("card-sidebar")[0]) == null ? void 0 : _b.view;
if (view) {
view.activeFilters.category = category;
view.renderCards();
}
}
this.close();
};
createBtn.addEventListener("click", () => {
void handleCreate();
});
editorEl.addEventListener("keydown", (e) => {
var _a;
if (this.applyFormattingHotkey(e, editorEl))
return;
if (handleKeyWrap(e, editorEl, this.editor, ((_a = this.plugin.settings) == null ? void 0 : _a.autoPairBrackets) !== false)) {
editorEl.dispatchEvent(new Event("input"));
return;
}
const settings = this.store.settings;
const normalizeKey = (v) => String(v || "").toLowerCase().replace(/[\s+_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
const saveKey = normalizeKey(settings.saveKey || "enter");
let pressed = "";
if (e.ctrlKey)
pressed += "ctrl-";
if (e.shiftKey)
pressed += "shift-";
if (e.altKey)
pressed += "alt-";
if (e.key && e.key.toLowerCase() === "enter")
pressed += "enter";
if (pressed === saveKey) {
e.preventDefault();
void handleCreate();
}
});
}
resolveColor(colorVar) {
const root = document.documentElement;
const clean = colorVar.replace("var(", "").replace(")", "");
return getComputedStyle(root).getPropertyValue(clean).trim() || colorVar;
}
getAllTags() {
const tags = /* @__PURE__ */ new Set();
this.store.getAll().forEach((c) => {
if (c.tags)
c.tags.forEach((t) => tags.add(t.toLowerCase()));
});
return Array.from(tags).sort();
}
};
// src/views/modals/SearchModal.ts
var import_obsidian8 = require("obsidian");
var SearchModal = class extends import_obsidian8.Modal {
constructor(app, plugin, store) {
super(app);
this.plugin = plugin;
this.store = store;
this.searchResults = [];
this.selectedIndex = -1;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
this.titleEl.setText("Search cards");
const searchWrapper = contentEl.createDiv("sc-search-wrapper");
this.searchInput = searchWrapper.createEl("input", {
type: "text",
placeholder: "Search cards...",
cls: "sc-search-input"
});
this.resultsContainer = contentEl.createDiv("sc-search-results");
this.searchInput.focus();
this.searchInput.addEventListener("input", () => this.renderResults());
this.searchInput.addEventListener("keydown", (e) => this.handleKeydown(e));
}
renderResults() {
const query = this.searchInput.value.toLowerCase();
this.resultsContainer.empty();
this.searchResults = this.store.getAll().filter(
(card) => card.content.toLowerCase().includes(query)
);
this.selectedIndex = -1;
this.searchResults.forEach((card, idx) => {
const result = this.resultsContainer.createDiv("sc-search-result");
result.textContent = card.content.substring(0, 100) + (card.content.length > 100 ? "..." : "");
result.style.borderLeft = `4px solid ${card.color}`;
result.dataset.index = String(idx);
result.addEventListener("click", () => this.selectResult(card));
result.addEventListener("mouseenter", () => {
this.selectedIndex = idx;
this.updateSelection();
});
});
}
updateSelection() {
const els = Array.from(this.resultsContainer.querySelectorAll(".sc-search-result"));
els.forEach((el, idx) => {
if (idx === this.selectedIndex) {
el.addClass("selected");
el.addClass("sc-search-result--selected");
} else {
el.removeClass("selected");
el.removeClass("sc-search-result--selected");
}
});
}
handleKeydown(e) {
if (e.key === "ArrowDown") {
e.preventDefault();
this.selectedIndex = (this.selectedIndex + 1) % this.searchResults.length;
this.updateSelection();
} else if (e.key === "ArrowUp") {
e.preventDefault();
this.selectedIndex = (this.selectedIndex - 1 + this.searchResults.length) % this.searchResults.length;
this.updateSelection();
} else if (e.key === "Enter") {
e.preventDefault();
if (this.selectedIndex >= 0) {
this.selectResult(this.searchResults[this.selectedIndex]);
}
}
}
selectResult(card) {
this.close();
this.store.eventBus.emit("card:focus", card.id);
}
};
// src/views/HomeView.ts
var import_obsidian9 = require("obsidian");
init_Card();
init_dom();
var SideCardsHomeView = class extends import_obsidian9.ItemView {
constructor(leaf, plugin, store, sortService) {
super(leaf);
this.plugin = plugin;
this.store = store;
this.sortService = sortService;
this.selectedColor = "var(--card-color-1)";
this.selectedTags = [];
this.filterType = "";
this.filterValue = "";
this.cardListEl = null;
this.recentFiles = [];
this.pinnedFiles = [];
this.cardComponents = /* @__PURE__ */ new Map();
this.currentSortMode = "created";
this.sortAscending = false;
this.pinnedOnly = false;
this.dragDropCleanup = null;
this.currentSearchQuery = "";
this.iconicFileIconsCache = null;
this.cardRenderGen = 0;
this.editorScope = new import_obsidian9.Scope(this.app.scope);
this.setupMockEditor();
}
setupMockEditor() {
this.editor = {
getSelection: () => {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return "";
const selectedText = sel.toString();
if (selectedText.length > 0)
return selectedText;
const wordRange = this.getWordRangeAtCaret(sel);
return wordRange ? wordRange.toString() : "";
},
replaceSelection: (text) => {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const currentRange = sel.getRangeAt(0);
const range = currentRange.collapsed ? this.getWordRangeAtCaret(sel) || currentRange : currentRange;
range.deleteContents();
const node = document.createTextNode(text);
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
},
toggleBold: () => this.toggleMarkdownWrapper("**"),
toggleItalic: () => this.toggleMarkdownWrapper("*"),
toggleHighlight: () => this.toggleMarkdownWrapper("=="),
toggleComment: () => this.toggleMarkdownWrapper("%%", "%%", true)
};
this.owner = {
editor: this.editor,
editMode: true
};
}
isWordChar(char) {
return /[A-Za-z0-9_]/.test(char);
}
getWordRangeAtCaret(selection) {
if (!selection.rangeCount)
return null;
const baseRange = selection.getRangeAt(0);
if (!baseRange.collapsed)
return baseRange;
const node = baseRange.startContainer;
if (!(node == null ? void 0 : node.data))
return null;
const text = node.data;
if (!text)
return null;
const offset = baseRange.startOffset;
const leftChar = offset > 0 ? text[offset - 1] : "";
const rightChar = offset < text.length ? text[offset] : "";
if (!this.isWordChar(leftChar) && !this.isWordChar(rightChar))
return null;
let start = offset;
let end = offset;
while (start > 0 && this.isWordChar(text[start - 1]))
start--;
while (end < text.length && this.isWordChar(text[end]))
end++;
const wordRange = document.createRange();
wordRange.setStart(node, start);
wordRange.setEnd(node, end);
return wordRange;
}
toggleMarkdownWrapper(wrapper, closeWrapper, includeInnerPadding = false) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return;
const currentRange = sel.getRangeAt(0);
const range = currentRange.collapsed ? this.getWordRangeAtCaret(sel) || currentRange : currentRange;
const selectedText = range.toString();
const endWrapper = closeWrapper != null ? closeWrapper : wrapper;
if (selectedText.length === 0) {
const text = wrapper + endWrapper;
const node = document.createTextNode(text);
range.insertNode(node);
const cursorOffset = wrapper.length;
range.setStart(node, cursorOffset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return;
}
const alreadyWrapped = selectedText.startsWith(wrapper) && selectedText.endsWith(endWrapper);
const newText = alreadyWrapped ? selectedText.slice(wrapper.length, selectedText.length - endWrapper.length) : includeInnerPadding ? `${wrapper} ${selectedText} ${endWrapper}` : `${wrapper}${selectedText}${endWrapper}`;
sel.removeAllRanges();
sel.addRange(range);
this.editor.replaceSelection(newText);
}
handleKeyWrap(event) {
if (this.plugin.settings.autoPairBrackets === false)
return { handled: false };
if (event.ctrlKey || event.metaKey || event.altKey)
return { handled: false };
const key = event.key;
const wrapMap = {
"[": ["[", "]"],
"(": ["(", ")"],
"{": ["{", "}"],
"`": ["`", "`"],
"%": ["%", "%"],
"=": ["=", "="],
'"': ['"', '"']
};
const pair = wrapMap[key];
if (!pair)
return { handled: false };
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return { handled: false };
const range = sel.getRangeAt(0);
const editorEl = this.containerEl.querySelector(".sc-home-editor");
if (!editorEl || !editorEl.contains(range.commonAncestorContainer))
return { handled: false };
const selectedText = range.toString();
const [open, close] = pair;
let newText = `${open}${selectedText}${close}`;
if (key === "%") {
if (selectedText.startsWith("%") && selectedText.endsWith("%") && !selectedText.startsWith("%%")) {
const inner = selectedText.slice(1, -1).trim();
newText = `%% ${inner} %%`;
}
} else if (key === "=") {
if (selectedText.startsWith("=") && selectedText.endsWith("=") && !selectedText.startsWith("==")) {
const inner = selectedText.slice(1, -1);
newText = `==${inner}==`;
}
} else if (key === "[") {
if (selectedText.startsWith("[") && selectedText.endsWith("]") && !selectedText.startsWith("[[")) {
const inner = selectedText.slice(1, -1);
newText = `[[${inner}]]`;
}
}
range.deleteContents();
const node = document.createTextNode(newText);
range.insertNode(node);
const newRange = document.createRange();
if (selectedText.length === 0) {
newRange.setStart(node, open.length);
newRange.collapse(true);
} else {
newRange.selectNode(node);
}
sel.removeAllRanges();
sel.addRange(newRange);
return { handled: true };
}
getEffectiveHotkeys(commandId) {
var _a, _b, _c, _d, _e, _f, _g;
const appAny = this.app;
const fromManager = (_b = (_a = appAny.hotkeyManager) == null ? void 0 : _a.getHotkeys) == null ? void 0 : _b.call(_a, commandId);
if (Array.isArray(fromManager) && fromManager.length > 0)
return fromManager;
const custom = (_d = (_c = appAny.hotkeyManager) == null ? void 0 : _c.customKeys) == null ? void 0 : _d[commandId];
if (Array.isArray(custom) && custom.length > 0)
return custom;
const defaults = (_g = (_f = (_e = appAny.commands) == null ? void 0 : _e.commands) == null ? void 0 : _f[commandId]) == null ? void 0 : _g.hotkeys;
if (Array.isArray(defaults) && defaults.length > 0)
return defaults;
return [];
}
getFormattingCommandIds(kind) {
var _a;
const defaults = {
bold: ["editor:toggle-bold", "custom-wrap-bold"],
italic: ["editor:toggle-italic", "editor:toggle-emphasis", "custom-wrap-italic"],
highlight: ["editor:toggle-highlight", "custom-wrap-highlight"],
comment: ["editor:toggle-comment", "custom-wrap-comment"]
};
const appAny = this.app;
const commands = ((_a = appAny.commands) == null ? void 0 : _a.commands) || {};
const matcher = {
bold: /bold/i,
italic: /italic|emphasis/i,
highlight: /highlight/i,
comment: /comment/i
};
const discovered = Object.values(commands).filter((cmd) => typeof (cmd == null ? void 0 : cmd.id) === "string" && cmd.id.startsWith("editor:")).filter((cmd) => matcher[kind].test(String((cmd == null ? void 0 : cmd.name) || ""))).map((cmd) => String(cmd.id));
return Array.from(/* @__PURE__ */ new Set([...defaults[kind], ...discovered]));
}
eventMatchesHotkey(event, hotkey) {
const key = String((hotkey == null ? void 0 : hotkey.key) || "").toLowerCase();
if (!key)
return false;
const eventKey = String(event.key || "").toLowerCase();
if (eventKey !== key)
return false;
const modifierSet = new Set((hotkey.modifiers || []).map((m) => String(m).toLowerCase()));
const hasMod = modifierSet.has("mod");
const expectsCtrl = modifierSet.has("ctrl") || hasMod && !import_obsidian9.Platform.isMacOS;
const expectsMeta = modifierSet.has("meta") || hasMod && import_obsidian9.Platform.isMacOS;
const expectsAlt = modifierSet.has("alt");
const expectsShift = modifierSet.has("shift");
if (expectsCtrl !== event.ctrlKey)
return false;
if (expectsMeta !== event.metaKey)
return false;
if (expectsAlt !== event.altKey)
return false;
if (expectsShift !== event.shiftKey)
return false;
return true;
}
applyFormattingHotkey(event, root) {
const sel = window.getSelection();
if (!sel || !sel.rangeCount)
return false;
const range = sel.getRangeAt(0);
if (!root.contains(range.commonAncestorContainer))
return false;
const targets = [
{ kind: "bold", run: () => this.toggleMarkdownWrapper("**") },
{ kind: "italic", run: () => this.toggleMarkdownWrapper("*") },
{ kind: "highlight", run: () => this.toggleMarkdownWrapper("==") },
{ kind: "comment", run: () => this.toggleMarkdownWrapper("%%", "%%", true) }
];
for (const target of targets) {
const commandIds = this.getFormattingCommandIds(target.kind);
const hotkeys = commandIds.flatMap((id) => this.getEffectiveHotkeys(id));
if (!hotkeys.length)
continue;
if (!hotkeys.some((h) => this.eventMatchesHotkey(event, h)))
continue;
event.preventDefault();
event.stopPropagation();
target.run();
root.dispatchEvent(new Event("input"));
return true;
}
return false;
}
getViewType() {
return "sidecards-home";
}
getDisplayText() {
return "Sidecards";
}
getIcon() {
return "home";
}
async onOpen() {
var _a;
const container = this.containerEl;
container.empty();
container.addClass("sc-home-container");
this.currentSortMode = this.plugin.settings.sortMode || "created";
this.sortAscending = typeof this.plugin.settings.sortAscending === "boolean" ? this.plugin.settings.sortAscending : false;
const main = container.createDiv({ cls: "sc-home-main" });
const maxW = (_a = this.plugin.settings.homepageMaxWidth) != null ? _a : 1e3;
container.setCssProps({ "--sc-home-max-width": `${maxW}px` });
const topSection = main.createDiv({ cls: "sc-home-top" });
topSection.createDiv({ text: this.plugin.settings.homepageName || "SideCards", cls: "sc-home-title" });
const paletteRow = topSection.createDiv({ cls: "sc-home-palette-row" });
if (this.plugin.settings.hideCategoryDropdown && this.plugin.settings.hideColorSwatches) {
paletteRow.addClass("sc-hidden");
}
if (!this.plugin.settings.hideCategoryDropdown) {
const categoryBtn = paletteRow.createEl("button", { text: "Category", cls: "sc-home-category-btn" });
categoryBtn.addEventListener("click", (e) => {
const menu = new import_obsidian9.Menu();
this.getAvailableFilters().forEach((f) => {
menu.addItem((item) => item.setTitle(f.label).onClick(() => {
this.filterType = f.type;
this.filterValue = f.value;
categoryBtn.textContent = f.label;
if (this.cardListEl)
void this.renderCards(this.cardListEl);
}));
});
menu.showAtMouseEvent(e);
});
const separator = paletteRow.createDiv({ cls: "sc-home-separator" });
separator.textContent = "|";
}
if (!this.plugin.settings.hideColorSwatches) {
const colors = [
{ name: "gray", var: "var(--card-color-1)" },
{ name: "red", var: "var(--card-color-2)" },
{ name: "orange", var: "var(--card-color-3)" },
{ name: "yellow", var: "var(--card-color-4)" },
{ name: "green", var: "var(--card-color-5)" },
{ name: "blue", var: "var(--card-color-6)" },
{ name: "purple", var: "var(--card-color-7)" },
{ name: "magenta", var: "var(--card-color-8)" },
{ name: "pink", var: "var(--card-color-9)" },
{ name: "brown", var: "var(--card-color-10)" }
];
const swatches = [];
colors.forEach((color) => {
const swatch = paletteRow.createDiv({ cls: "sc-home-color-dot" });
const root = document.documentElement;
const computedColor = getComputedStyle(root).getPropertyValue(color.var.replace("var(", "").replace(")", ""));
swatch.style.backgroundColor = computedColor.trim() || color.var;
if (this.selectedColor === color.var)
swatch.addClass("is-selected");
swatch.addEventListener("click", () => {
swatches.forEach((s) => s.removeClass("is-selected"));
swatch.addClass("is-selected");
this.selectedColor = color.var;
});
swatches.push(swatch);
});
}
const editorEl = topSection.createDiv({ cls: "sc-home-editor" });
editorEl.setAttribute("contenteditable", "true");
editorEl.dataset.placeholder = "Type here... (@category, #tag)";
new InlineAutocomplete(editorEl, this.store, this.app);
const updatePlaceholder = () => {
var _a2;
if (!((_a2 = editorEl.textContent) == null ? void 0 : _a2.trim()))
editorEl.addClass("is-empty");
else
editorEl.removeClass("is-empty");
};
updatePlaceholder();
editorEl.addEventListener("input", updatePlaceholder);
editorEl.addEventListener("focusin", () => {
this.app.keymap.pushScope(this.editorScope);
this.app.workspace.activeEditor = this.owner;
});
editorEl.addEventListener("blur", () => {
this.app.keymap.popScope(this.editorScope);
if (this.app.workspace.activeEditor === this.owner)
this.app.workspace.activeEditor = null;
});
editorEl.addEventListener("keydown", (e) => {
if (this.applyFormattingHotkey(e, editorEl))
return;
const wrapResult = this.handleKeyWrap(e);
if (wrapResult.handled) {
e.preventDefault();
e.stopPropagation();
editorEl.dispatchEvent(new Event("input"));
return;
}
let pressed = "";
if (e.ctrlKey)
pressed += "ctrl-";
if (e.shiftKey)
pressed += "shift-";
if (e.altKey)
pressed += "alt-";
if (e.key && e.key.toLowerCase() === "enter")
pressed += "enter";
const normalizeKey = (v) => String(v || "").toLowerCase().replace(/[\s+_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
const saveKey = normalizeKey(this.plugin.settings.saveKey || "enter");
if (pressed === saveKey || e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.altKey) {
e.preventDefault();
void this.createCardFromHomeInput(editorEl, cardList);
}
});
const contentGrid = main.createDiv({ cls: "sc-home-grid" });
const bothNotesHidden = this.plugin.settings.showPinnedNotes === false && this.plugin.settings.showRecentNotes === false;
const notesOnRight = this.plugin.settings.notesPlacement === "right";
if (notesOnRight)
contentGrid.addClass("notes-right");
const leftCol = contentGrid.createDiv({ cls: "sc-home-left" });
const rightCol = contentGrid.createDiv({ cls: "sc-home-right" });
if (notesOnRight) {
leftCol.addClass("sc-home-left--right");
rightCol.addClass("sc-home-right--left");
}
if (bothNotesHidden) {
leftCol.addClass("sc-home-left--hidden");
contentGrid.addClass("sc-home-grid--full");
}
let pinnedList = null;
let recentList = null;
if (this.plugin.settings.showPinnedNotes !== false) {
leftCol.createEl("h3", { text: "Pinned notes", cls: "sc-home-section-title" });
pinnedList = leftCol.createDiv({ cls: "sc-home-file-list pinned" });
await this.renderFileList(pinnedList, "pinned");
}
if (this.plugin.settings.showRecentNotes !== false) {
leftCol.createEl("h3", { text: "Recent notes", cls: "sc-home-section-title" });
recentList = leftCol.createDiv({ cls: "sc-home-file-list" });
await this.renderFileList(recentList, "recent");
}
const toolbar = rightCol.createDiv({ cls: "sc-home-toolbar" });
const cardList = rightCol.createDiv({ cls: "sc-home-card-list" });
this.cardListEl = cardList;
const categoryBar = rightCol.createDiv({ cls: "sc-home-category-bar" });
rightCol.insertBefore(categoryBar, cardList);
this.renderToolbar(toolbar, editorEl, cardList, pinnedList != null ? pinnedList : leftCol, recentList != null ? recentList : leftCol);
const openOn = this.plugin.settings.openCategoryOnLoad || "all";
const availableFilters = this.getAvailableFilters();
const matchedFilter = availableFilters.find((f) => f.value === openOn);
if (matchedFilter) {
this.filterType = matchedFilter.type;
this.filterValue = matchedFilter.value;
}
this.renderCategoryBar(categoryBar, cardList);
const onCardChange = () => {
void this.renderCards(cardList);
};
this.store.eventBus.on("card:added", onCardChange);
this.store.eventBus.on("card:updated", onCardChange);
this.store.eventBus.on("card:deleted", onCardChange);
this.renderCards(cardList);
}
async renderFileList(container, type) {
container.empty();
const files = type === "recent" ? this.getRecentFiles() : this.getPinnedFiles();
if (files.length === 0) {
container.createDiv({ text: `No ${type} notes found`, cls: "sc-home-empty-msg" });
return;
}
for (const file of files) {
const item = container.createDiv({ cls: "sc-home-file-item" });
if (type === "pinned")
item.dataset.path = file.path;
const iconSpan = item.createSpan({ cls: "sc-home-file-icon" });
await this.renderCustomFileIcon(iconSpan, file);
item.createSpan({ text: file.basename, cls: "sc-home-file-name" });
item.addEventListener("click", () => {
void this.app.workspace.getLeaf(false).openFile(file);
});
if (type === "pinned") {
item.addEventListener("contextmenu", (e) => {
e.preventDefault();
const menu = new import_obsidian9.Menu();
menu.addItem((i) => i.setTitle("Unpin from Homepage").setIcon("pin-off").onClick(async () => {
this.plugin.settings.pinnedNotes = (this.plugin.settings.pinnedNotes || []).filter((p) => p !== file.path);
await this.plugin.saveSettings();
await this.renderFileList(container, "pinned");
}));
menu.showAtMouseEvent(e);
});
}
}
if (type === "pinned") {
attachPinnedListDragToReorder(
container,
() => {
var _a;
return (_a = this.plugin.settings.pinnedNotes) != null ? _a : [];
},
async (newPaths) => {
this.plugin.settings.pinnedNotes = newPaths;
await this.plugin.saveSettings();
await this.renderFileList(container, "pinned");
}
);
}
}
async renderCustomFileIcon(iconEl, file) {
var _a;
const iconInfo = await this.getIconicFileIcon(file);
if (!iconInfo) {
try {
(0, import_obsidian9.setIcon)(iconEl, "file-text");
} catch (e) {
(0, import_obsidian9.setIcon)(iconEl, "file");
}
return;
}
const iconValue = String(iconInfo.icon || "").trim();
if (!iconValue) {
try {
(0, import_obsidian9.setIcon)(iconEl, "file-text");
} catch (e) {
(0, import_obsidian9.setIcon)(iconEl, "file");
}
return;
}
let rendered = false;
const normalizedLucide = this.normalizeLucideIconName(iconValue);
if (normalizedLucide) {
try {
(0, import_obsidian9.setIcon)(iconEl, normalizedLucide);
rendered = true;
} catch (e) {
}
}
if (!rendered && iconValue.includes("<svg")) {
iconEl.empty();
const parser = new DOMParser();
const doc = parser.parseFromString(iconValue, "image/svg+xml");
const svgEl = doc.querySelector("svg");
if (svgEl)
iconEl.appendChild(svgEl);
rendered = true;
}
if (!rendered && /^:.*:$/.test(iconValue)) {
iconEl.setText(iconValue);
rendered = true;
}
if (!rendered) {
try {
(0, import_obsidian9.setIcon)(iconEl, iconValue);
rendered = true;
} catch (e) {
try {
(0, import_obsidian9.setIcon)(iconEl, "file-text");
} catch (e2) {
(0, import_obsidian9.setIcon)(iconEl, "file");
}
rendered = true;
}
}
if (iconInfo.color) {
const colorNameToVar = {
red: "var(--color-red)",
orange: "var(--color-orange)",
yellow: "var(--color-yellow)",
green: "var(--color-green)",
cyan: "var(--color-cyan)",
blue: "var(--color-blue)",
purple: "var(--color-purple)",
pink: "var(--color-pink)",
magenta: "var(--color-pink)",
gray: "var(--color-base-70)",
grey: "var(--color-base-70)"
};
const normalized = iconInfo.color.trim().toLowerCase();
iconEl.style.color = (_a = colorNameToVar[normalized]) != null ? _a : iconInfo.color;
}
}
normalizeLucideIconName(rawIcon) {
const cleaned = rawIcon.trim();
if (!cleaned)
return null;
if (cleaned.includes("<svg"))
return null;
if (/^:.*:$/.test(cleaned))
return null;
if (/[\u{1F300}-\u{1FAFF}]/u.test(cleaned))
return null;
let iconName = cleaned;
const prefixes = ["lucide:", "lucide/", "lucide-"];
for (const prefix of prefixes) {
if (iconName.startsWith(prefix)) {
iconName = iconName.slice(prefix.length);
}
}
if (iconName.includes(":")) {
const parts = iconName.split(":");
iconName = parts[parts.length - 1];
}
if (iconName.includes("/")) {
const parts = iconName.split("/");
iconName = parts[parts.length - 1];
}
iconName = iconName.trim();
if (!/^[a-z0-9-]+$/i.test(iconName))
return null;
return iconName.toLowerCase();
}
resolveIconicIconEntry(entry) {
var _a, _b;
if (!entry)
return null;
if (typeof entry === "string") {
return { icon: entry };
}
if (typeof entry === "object") {
const iconValue = (_b = (_a = entry.icon) != null ? _a : entry.name) != null ? _b : entry.value;
if (!iconValue)
return null;
return {
icon: String(iconValue),
color: typeof entry.color === "string" ? entry.color : void 0
};
}
return null;
}
async getIconicFileIcon(file) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
const iconicPlugin = (_b = (_a = this.app.plugins) == null ? void 0 : _a.getPlugin) == null ? void 0 : _b.call(_a, "iconic");
if (!iconicPlugin)
return null;
const path = file.path;
try {
const ruled = (_d = (_c = iconicPlugin.ruleManager) == null ? void 0 : _c.checkRuling) == null ? void 0 : _d.call(_c, "file", path);
if (ruled) {
const iconValue = (_e = ruled.icon) != null ? _e : ruled.iconDefault;
if (iconValue) {
return { icon: String(iconValue), color: (_f = ruled.color) != null ? _f : void 0 };
}
}
} catch (e) {
}
try {
const item = (_g = iconicPlugin.getFileItem) == null ? void 0 : _g.call(iconicPlugin, path);
if (item) {
const iconValue = (_h = item.icon) != null ? _h : item.iconDefault;
if (iconValue) {
return { icon: String(iconValue), color: (_i = item.color) != null ? _i : void 0 };
}
}
} catch (e) {
}
const immediateEntry = this.resolveIconicIconEntry(
(_p = (_n = (_k = (_j = iconicPlugin.settings) == null ? void 0 : _j.fileIcons) == null ? void 0 : _k[path]) != null ? _n : (_m = (_l = iconicPlugin.data) == null ? void 0 : _l.fileIcons) == null ? void 0 : _m[path]) != null ? _p : (_o = iconicPlugin.fileIcons) == null ? void 0 : _o[path]
);
if (immediateEntry)
return immediateEntry;
if (this.iconicFileIconsCache === null) {
try {
const loaded = await ((_q = iconicPlugin.loadData) == null ? void 0 : _q.call(iconicPlugin));
this.iconicFileIconsCache = (loaded == null ? void 0 : loaded.fileIcons) && typeof loaded.fileIcons === "object" ? loaded.fileIcons : {};
} catch (e) {
this.iconicFileIconsCache = {};
}
}
const cache = this.iconicFileIconsCache || {};
return this.resolveIconicIconEntry(cache[path]);
}
getRecentFiles() {
var _a;
const limit = (_a = this.plugin.settings.recentNotesLimit) != null ? _a : 5;
return this.app.vault.getMarkdownFiles().sort((a, b) => b.stat.mtime - a.stat.mtime).slice(0, limit);
}
getPinnedFiles() {
const pinned = [];
if (this.plugin.settings.pinnedNotes) {
this.plugin.settings.pinnedNotes.forEach((path) => {
const file = this.app.vault.getAbstractFileByPath(path);
if (file instanceof import_obsidian9.TFile)
pinned.push(file);
});
}
return pinned;
}
async refreshPinnedNotes() {
const pinnedList = this.containerEl.querySelector(".sc-home-file-list.pinned");
if (pinnedList) {
await this.renderFileList(pinnedList, "pinned");
}
}
async refresh() {
var _a, _b;
const main = this.containerEl.querySelector(".sc-home-main");
if (!main)
return;
const titleEl = main.querySelector(".sc-home-title");
if (titleEl)
titleEl.textContent = this.plugin.settings.homepageName || "Sidecards";
const maxW = (_a = this.plugin.settings.homepageMaxWidth) != null ? _a : 1e3;
this.containerEl.setCssProps({ "--sc-home-max-width": `${maxW}px` });
const topMargin = (_b = this.plugin.settings.homepageTopMargin) != null ? _b : 70;
this.containerEl.setCssProps({ "--sc-home-top-margin": `${topMargin}px` });
const categoryBar = main.querySelector(".sc-home-category-bar");
if (categoryBar) {
this.renderCategoryBar(categoryBar, this.cardListEl);
}
const pinnedList = main.querySelector(".sc-home-file-list.pinned");
if (pinnedList)
await this.renderFileList(pinnedList, "pinned");
const recentList = main.querySelector(".sc-home-file-list:not(.pinned)");
if (recentList)
await this.renderFileList(recentList, "recent");
if (this.cardListEl)
this.renderCards(this.cardListEl);
}
async refreshHomeContent(cardList, pinnedList, recentList) {
this.iconicFileIconsCache = null;
await this.renderFileList(pinnedList, "pinned");
await this.renderFileList(recentList, "recent");
this.renderCards(cardList);
}
renderToolbar(container, _editorEl, cardList, pinnedList, recentList) {
container.empty();
const leftActions = container.createDiv({ cls: "sc-home-toolbar-left" });
const searchWrap = container.createDiv({ cls: "sc-home-search-wrap" });
const rightActions = container.createDiv({ cls: "sc-home-toolbar-right" });
const sortBtn = leftActions.createEl("button", { cls: "sc-icon-btn" });
(0, import_obsidian9.setIcon)(sortBtn, "sort-desc");
sortBtn.title = "Sort";
sortBtn.addEventListener("click", (e) => {
const menu = new import_obsidian9.Menu();
const modes = [
{ label: "Manual", mode: "manual" },
{ label: "Created Time", mode: "created" },
{ label: "Modified Time", mode: "modified" },
{ label: "A \u2192 Z", mode: "alpha" },
{ label: "Status", mode: "status" }
];
modes.forEach(({ label, mode }) => {
menu.addItem((item) => {
item.setTitle(label).setChecked(this.currentSortMode === mode).onClick(async () => {
this.currentSortMode = mode;
this.plugin.settings.sortMode = mode;
await this.plugin.saveSettings();
this.renderCards(cardList);
});
});
});
menu.addSeparator();
menu.addItem((item) => {
item.setTitle("Direction: " + (this.sortAscending ? "Ascending" : "Descending")).onClick(async () => {
this.sortAscending = !this.sortAscending;
this.plugin.settings.sortAscending = this.sortAscending;
await this.plugin.saveSettings();
this.renderCards(cardList);
});
});
menu.showAtMouseEvent(e);
});
const refreshBtn = leftActions.createEl("button", { cls: "sc-icon-btn" });
(0, import_obsidian9.setIcon)(refreshBtn, "refresh-cw");
refreshBtn.title = "Refresh";
refreshBtn.addEventListener("click", () => {
void this.refreshHomeContent(cardList, pinnedList, recentList);
});
const searchIcon = searchWrap.createSpan({ cls: "sc-home-search-icon" });
(0, import_obsidian9.setIcon)(searchIcon, "search");
const searchInput = searchWrap.createEl("input", { cls: "sc-home-search-input", placeholder: "Search cards..." });
searchInput.addEventListener("input", () => {
this.currentSearchQuery = searchInput.value;
void this.renderCards(cardList);
});
const pinBtn = rightActions.createEl("button", { cls: "sc-icon-btn" });
(0, import_obsidian9.setIcon)(pinBtn, "pin");
pinBtn.title = "Pinned";
pinBtn.addEventListener("click", () => {
pinBtn.toggleClass("active", !pinBtn.hasClass("active"));
this.pinnedOnly = pinBtn.hasClass("active");
void this.renderCards(cardList);
});
const moreBtn = rightActions.createEl("button", { cls: "sc-icon-btn" });
(0, import_obsidian9.setIcon)(moreBtn, "more-vertical");
moreBtn.title = "More";
moreBtn.addEventListener("click", (e) => {
const menu = new import_obsidian9.Menu();
menu.addItem((item) => {
var _a;
item.setTitle("Show Tags").setChecked((_a = this.plugin.settings.homeShowTags) != null ? _a : true).onClick(async () => {
var _a2;
const current = (_a2 = this.plugin.settings.homeShowTags) != null ? _a2 : true;
this.plugin.settings.homeShowTags = !current;
await this.plugin.saveSettings();
void this.renderCards(cardList);
});
});
menu.addItem((item) => {
var _a;
item.setTitle("Show Timestamps").setChecked((_a = this.plugin.settings.homeShowTimestamps) != null ? _a : this.plugin.settings.showTimestamps).onClick(async () => {
var _a2;
const current = (_a2 = this.plugin.settings.homeShowTimestamps) != null ? _a2 : this.plugin.settings.showTimestamps;
this.plugin.settings.homeShowTimestamps = !current;
await this.plugin.saveSettings();
void this.renderCards(cardList);
});
});
menu.showAtMouseEvent(e);
});
const sidebarBtn = rightActions.createEl("button", { cls: "sc-icon-btn" });
(0, import_obsidian9.setIcon)(sidebarBtn, "panel-right");
sidebarBtn.title = "Open sidebar";
sidebarBtn.addEventListener("click", () => {
void this.plugin.activateView();
});
}
renderCategoryBar(container, cardList) {
container.empty();
const filters = this.getAvailableFilters();
filters.forEach((f) => {
var _a;
const btn = container.createEl("button", { text: f.label, cls: "sc-category-btn" });
if (this.filterValue === f.value)
btn.addClass("active");
const colorKey = f.value === "all" ? "all" : f.value === "today" ? "today" : f.value === "tomorrow" ? "tomorrow" : f.value === "archived" ? "archived" : f.value;
btn.dataset.filterValue = f.value;
btn.dataset.filterColorKey = colorKey;
const customColors = (_a = this.plugin.settings.filterColors) == null ? void 0 : _a[colorKey];
if (customColors == null ? void 0 : customColors.bgColor)
btn.setCssProps({ "background-color": customColors.bgColor });
if (customColors == null ? void 0 : customColors.textColor) {
btn.setCssProps({
"color": customColors.textColor,
"--sc-btn-text-color": customColors.textColor
});
}
btn.addEventListener("click", () => {
container.querySelectorAll(".sc-category-btn").forEach((b) => {
var _a2;
b.removeClass("active");
const bColorKey = b.dataset.filterColorKey || "";
const bColors = (_a2 = this.plugin.settings.filterColors) == null ? void 0 : _a2[bColorKey];
if (bColors == null ? void 0 : bColors.bgColor)
b.setCssProps({ "background-color": bColors.bgColor });
else
b.style.removeProperty("background-color");
if (bColors == null ? void 0 : bColors.textColor) {
b.setCssProps({
"color": bColors.textColor,
"--sc-btn-text-color": bColors.textColor
});
} else {
b.style.removeProperty("color");
b.style.removeProperty("--sc-btn-text-color");
}
});
btn.addClass("active");
this.filterType = f.type;
this.filterValue = f.value;
void this.renderCards(cardList);
});
});
}
renderCards(container) {
var _a;
const gen = ++this.cardRenderGen;
container.empty();
this.cardComponents.forEach((c) => c.destroy());
this.cardComponents.clear();
let cards = this.store.getAll();
if (this.pinnedOnly) {
cards = cards.filter((c) => c.pinned);
}
if (this.currentSearchQuery) {
cards = cards.filter((c) => c.content.toLowerCase().includes(this.currentSearchQuery.toLowerCase()));
}
const category = this.filterValue;
if (category && category !== "all") {
if (category === "archived") {
cards = cards.filter((c) => c.archived);
} else if (category === "today") {
cards = cards.filter((c) => (c.category || "").toLowerCase() === "today" && !c.archived);
} else if (category === "tomorrow") {
cards = cards.filter((c) => (c.category || "").toLowerCase() === "tomorrow" && !c.archived);
} else {
const customCats = this.plugin.settings.customCategories || [];
const matched = customCats.find((c) => c.id === category || c.label === category);
const categoryLabel = matched ? matched.label : category;
cards = cards.filter((c) => (c.category === categoryLabel || c.category === category) && !c.archived);
}
} else if (!category || category === "all") {
cards = cards.filter((c) => !c.archived);
}
cards = this.sortService.sort(cards, this.currentSortMode, this.sortAscending, this.app);
const CHUNK_SIZE = 10;
const renderChunk = (startIdx) => {
if (gen !== this.cardRenderGen)
return;
const chunk = cards.slice(startIdx, startIdx + CHUNK_SIZE);
if (chunk.length === 0)
return;
chunk.forEach((card) => {
var _a2, _b, _c;
const comp = new CardComponent(container, card, this.store, this.app, this.plugin, {
groupTags: (_a2 = this.plugin.settings.homeGroupTags) != null ? _a2 : this.plugin.settings.groupTags,
showTimestamps: (_b = this.plugin.settings.homeShowTimestamps) != null ? _b : this.plugin.settings.showTimestamps,
showTags: (_c = this.plugin.settings.homeShowTags) != null ? _c : true
});
this.cardComponents.set(card.id, comp);
});
if (startIdx + CHUNK_SIZE < cards.length) {
requestAnimationFrame(() => renderChunk(startIdx + CHUNK_SIZE));
}
};
renderChunk(0);
requestAnimationFrame(() => animateCardsEntrance(container, {}, this.plugin.settings));
(_a = this.dragDropCleanup) == null ? void 0 : _a.call(this);
this.dragDropCleanup = attachDragToReorder(
container,
this.plugin,
() => this.currentSortMode,
async () => {
await this.plugin.saveSettings();
}
);
}
getAvailableFilters() {
const settings = this.plugin.settings;
const cats = Array.isArray(settings.customCategories) ? settings.customCategories : [];
const defaultOrder = ["filter-all"].concat(!settings.hideTodayFilter ? ["filter-today"] : []).concat(!settings.hideTomorrowFilter ? ["filter-tomorrow"] : []).concat(!settings.hideArchivedFilterButton ? ["filter-archived"] : []).concat(cats.map((c) => String(c.id || "")));
const combinedOrder = Array.isArray(settings.allItemsOrder) && settings.allItemsOrder.length > 0 ? settings.allItemsOrder : defaultOrder;
const filters = [];
combinedOrder.forEach((itemId) => {
if (!itemId)
return;
if (itemId === "filter-all") {
filters.push({ type: "all", label: "All", value: "all" });
return;
}
if (itemId === "filter-today") {
if (!settings.hideTodayFilter)
filters.push({ type: "category", label: "Today", value: "today" });
return;
}
if (itemId === "filter-tomorrow") {
if (!settings.hideTomorrowFilter)
filters.push({ type: "category", label: "Tomorrow", value: "tomorrow" });
return;
}
if (itemId === "filter-archived") {
if (!settings.hideArchivedFilterButton)
filters.push({ type: "archived", label: "Archived", value: "archived" });
return;
}
if (settings.enableCustomCategories) {
const cat = cats.find((c) => String(c.id) === String(itemId));
if (cat && cat.showInMenu !== false) {
filters.push({ type: "category", label: cat.label || "", value: cat.id || cat.label || "" });
}
}
});
if (!filters.find((f) => f.value === "all")) {
filters.unshift({ type: "all", label: "All", value: "all" });
}
return filters;
}
getAllUsedTags() {
try {
const tags = /* @__PURE__ */ new Set();
const allCards = this.store.getAll();
allCards.forEach((c) => c.tags.forEach((t) => tags.add(String(t).toLowerCase())));
return Array.from(tags).sort();
} catch (e) {
return [];
}
}
async createCardFromHomeInput(editorEl, _cardList) {
var _a;
const content = (_a = editorEl.textContent) == null ? void 0 : _a.trim();
if (!content)
return;
const tags = [];
const tagRegex = /#([^\s#@,.]+)/g;
let match;
while ((match = tagRegex.exec(content)) !== null) {
tags.push(match[1]);
}
const catRegex = /@([^\s#@,.]+)/g;
const catMatch = catRegex.exec(content);
const category = catMatch ? catMatch[1] : this.filterValue === "all" ? void 0 : this.filterValue;
const cleanContent = content.replace(/@[^\s#@,.]+/g, "").replace(/#[^\s#@,.]+/g, "").replace(/\s{2,}/g, " ").trim();
const allTags = [.../* @__PURE__ */ new Set([...tags, ...this.selectedTags])];
const autoColor = resolveAutoColor(content, allTags, this.plugin.settings);
const effectiveColor = autoColor || this.selectedColor;
const card = new Card({
content: cleanContent,
color: effectiveColor,
tags: allTags,
category
});
await this.store.add(card);
editorEl.textContent = "";
editorEl.dispatchEvent(new Event("input"));
this.selectedTags = [];
new import_obsidian9.Notice("Card created");
}
onClose() {
return Promise.resolve();
}
};
// src/core/Plugin.ts
var SideCardsPlugin = class extends import_obsidian10.Plugin {
async onload() {
var _a;
await this.loadSettings();
document.documentElement.setCssProps({
"--card-color-1": this.settings.color1 || "#8392a4",
"--card-color-2": this.settings.color2 || "#eb3b5a",
"--card-color-3": this.settings.color3 || "#fa8231",
"--card-color-4": this.settings.color4 || "#e5a216",
"--card-color-5": this.settings.color5 || "#20bf6b",
"--card-color-6": this.settings.color6 || "#2d98da",
"--card-color-7": this.settings.color7 || "#8854d0",
"--card-color-8": this.settings.color8 || "#e832c1",
"--card-color-9": this.settings.color9 || "#e83289",
"--card-color-10": this.settings.color10 || "#965b3b",
"--card-border-radius": `${(_a = this.settings.borderRadius) != null ? _a : 6}px`
});
this.eventBus = new EventBus();
this.filterService = new FilterService();
this.sortService = new SortService(this.settings);
this.store = new CardStore(this.app, this, this.eventBus, this.settings);
await this.store.migrateCardColorFrontmatterFormat();
this.registerView(
"card-sidebar",
(leaf) => new CardSidebarView(
leaf,
this,
this.store,
this.eventBus,
this.filterService,
this.sortService
)
);
this.registerView(
"sidecards-home",
(leaf) => new SideCardsHomeView(
leaf,
this,
this.store,
this.sortService
)
);
this.addCommand({
id: "open-sidebar",
name: "Open sidebar",
callback: () => void this.activateView()
});
this.addCommand({
id: "open-home",
name: "Open home",
callback: () => void this.activateHomeView()
});
this.addCommand({
id: "quick-card-add",
name: "Quick card add",
callback: () => new QuickCardWithFilterModal(this.app, this, this.store).open()
});
this.addCommand({
id: "search-cards",
name: "Search cards",
callback: () => new SearchModal(this.app, this, this.store).open()
});
this.addCommand({
id: "pin-to-homepage",
name: "Pin to homepage",
checkCallback: (checking) => {
var _a2;
const file = this.app.workspace.getActiveFile();
if (!file)
return false;
if (!checking) {
const isPinned = (_a2 = this.settings.pinnedNotes) == null ? void 0 : _a2.includes(file.path);
if (!this.settings.pinnedNotes)
this.settings.pinnedNotes = [];
if (isPinned) {
this.settings.pinnedNotes = this.settings.pinnedNotes.filter((p) => p !== file.path);
new import_obsidian10.Notice(`Unpinned ${file.name} from SideCards Homepage.`);
} else {
if (this.settings.showPinnedNotes !== false) {
this.settings.pinnedNotes.push(file.path);
new import_obsidian10.Notice(`Pinned ${file.name} to SideCards Homepage.`);
}
}
void this.saveSettings();
const leaves = this.app.workspace.getLeavesOfType("sidecards-home");
leaves.forEach((leaf) => {
const v = leaf.view;
if (typeof v.refreshPinnedNotes === "function")
v.refreshPinnedNotes();
});
}
return this.settings.showPinnedNotes !== false;
}
});
this.addCommand({
id: "custom-wrap-comment",
name: "Wrap with comment %% (any focused editable)",
checkCallback: (checking) => {
const activeEl = document.activeElement;
if (activeEl instanceof HTMLElement && activeEl.isContentEditable) {
if (!checking) {
this.wrapWith("%%", "%%");
}
return true;
}
return false;
}
});
this.addCommand({
id: "custom-wrap-bold",
name: "Wrap with **bold**",
checkCallback: (checking) => {
const activeEl = document.activeElement;
if (activeEl instanceof HTMLElement && activeEl.isContentEditable) {
if (!checking) {
this.wrapWith("**", "**");
}
return true;
}
return false;
}
});
this.addCommand({
id: "custom-wrap-italic",
name: "Wrap with *italic*",
checkCallback: (checking) => {
const activeEl = document.activeElement;
if (activeEl instanceof HTMLElement && activeEl.isContentEditable) {
if (!checking) {
this.wrapWith("*", "*");
}
return true;
}
return false;
}
});
this.addCommand({
id: "custom-wrap-highlight",
name: "Wrap with ==highlight==",
checkCallback: (checking) => {
const activeEl = document.activeElement;
if (activeEl instanceof HTMLElement && activeEl.isContentEditable) {
if (!checking) {
this.wrapWith("==", "==");
}
return true;
}
return false;
}
});
this.addRibbonIcon("home", "Open Homepage", () => void this.activateHomeView());
this.addRibbonIcon("rows-3", "Open Sidebar", () => void this.activateView());
this.addRibbonIcon("rectangle-horizontal", "Add Card", () => new QuickCardWithFilterModal(this.app, this, this.store).open());
this.addSettingTab(new SideCardsSettingTab(this.app, this));
this.registerEvent(
this.app.vault.on("rename", (file, oldPath) => {
if (!(file instanceof import_obsidian10.TFile))
return;
const card = this.store.getAll().find((c) => c.notePath === oldPath);
if (!card)
return;
card.notePath = file.path;
void this.store.saveCards();
})
);
this.registerEvent(
this.app.workspace.on("file-menu", (menu, file) => {
if (file instanceof import_obsidian10.TFile) {
menu.addItem((item) => {
var _a2;
const isPinned = (_a2 = this.settings.pinnedNotes) == null ? void 0 : _a2.includes(file.path);
item.setTitle(isPinned ? "Unpin from SideCards Homepage" : "Pin to SideCards Homepage").setIcon("pin").onClick(async () => {
if (!this.settings.pinnedNotes)
this.settings.pinnedNotes = [];
if (isPinned) {
this.settings.pinnedNotes = this.settings.pinnedNotes.filter((p) => p !== file.path);
} else {
this.settings.pinnedNotes.push(file.path);
}
await this.saveSettings();
const leaves = this.app.workspace.getLeavesOfType("sidecards-home");
leaves.forEach((leaf) => {
const v = leaf.view;
if (typeof v.refreshPinnedNotes === "function")
v.refreshPinnedNotes();
});
});
});
}
})
);
this.registerEvent(
this.app.workspace.on("active-leaf-change", (leaf) => {
var _a2, _b, _c, _d;
if (!this.settings.replaceHomepageWithSidecards)
return;
if (!leaf)
return;
try {
const viewType = (_b = (_a2 = leaf.view) == null ? void 0 : _a2.getViewType) == null ? void 0 : _b.call(_a2);
if (viewType === "sidecards-home")
return;
const state = (_c = leaf.getViewState) == null ? void 0 : _c.call(leaf);
if ((state == null ? void 0 : state.type) === "empty" && !((_d = state == null ? void 0 : state.state) == null ? void 0 : _d.file)) {
void this.replaceWithHomepage(leaf);
}
} catch (e) {
}
})
);
this.injectStatusColors();
this.applyButtonPadding();
this.applyMaxCardHeight();
this.applyHomepageTopMargin();
this.registerDomEvent(document, "dragover", (ev) => {
if (!ev.dataTransfer)
return;
const types = Array.from(ev.dataTransfer.types || []);
if (!types.includes("text/x-card-sidebar"))
return;
ev.preventDefault();
ev.dataTransfer.dropEffect = "copy";
});
let lastDropTime = 0;
const cardDropHandler = (ev) => {
var _a2;
if (!ev.dataTransfer)
return;
const types = Array.from(ev.dataTransfer.types || []);
if (!types.includes("text/x-card-sidebar"))
return;
const now = Date.now();
if (now - lastDropTime < 200) {
ev.preventDefault();
ev.stopImmediatePropagation();
return;
}
lastDropTime = now;
ev.preventDefault();
ev.stopImmediatePropagation();
let content = null;
try {
const json = ev.dataTransfer.getData("text/x-card-sidebar");
const payload = JSON.parse(json);
content = (_a2 = payload.content) != null ? _a2 : null;
} catch (e) {
}
if (!content)
return;
let mdView = null;
const target = ev.target;
if (target) {
this.app.workspace.iterateAllLeaves((leaf) => {
var _a3, _b;
if (mdView)
return;
const view = leaf.view;
if (!(view instanceof import_obsidian10.MarkdownView))
return;
const editorEl = (_b = (_a3 = view.editor) == null ? void 0 : _a3.containerEl) != null ? _b : view.contentEl;
if (editorEl && editorEl.contains(target))
mdView = view;
});
}
if (!mdView)
mdView = this.app.workspace.getActiveViewOfType(import_obsidian10.MarkdownView);
if (!(mdView == null ? void 0 : mdView.editor))
return;
const cm = mdView.editor.cm;
if (cm == null ? void 0 : cm.posAtCoords) {
const pos = cm.posAtCoords({ x: ev.clientX, y: ev.clientY }, false);
if (pos != null && cm.dispatch)
cm.dispatch({ selection: { anchor: pos } });
}
mdView.editor.replaceSelection(content);
mdView.editor.focus();
};
document.addEventListener(
"drop",
cardDropHandler,
true
/* capture */
);
this._cardDropCleanup = () => document.removeEventListener("drop", cardDropHandler, true);
this.app.workspace.onLayoutReady(() => {
if (!this.settings.storageFolder) {
this.showStorageFolderSetupModal();
} else {
void this.store.importNotesFromFolderToSettings(this.settings.storageFolder, true);
}
if (this.settings.autoOpen) {
void this.activateView();
}
});
}
onunload() {
if (this._cardDropCleanup)
this._cardDropCleanup();
}
applyButtonPadding() {
var _a;
const paddingPx = (_a = this.settings.buttonPaddingBottom) != null ? _a : 26;
document.documentElement.setCssProps({
"--sc-button-padding-bottom": `${paddingPx}px`
});
}
applyMaxCardHeight() {
const h = this.settings.maxCardHeight;
if (h && h > 0) {
document.documentElement.setCssProps({ "--sc-max-card-height": `${h}px` });
document.body.addClass("sc-max-card-height-active");
} else {
document.documentElement.style.removeProperty("--sc-max-card-height");
document.body.removeClass("sc-max-card-height-active");
}
}
applyHomepageTopMargin() {
var _a;
const topMargin = (_a = this.settings.homepageTopMargin) != null ? _a : 70;
document.documentElement.style.setProperty("--sc-home-top-margin", `${topMargin}px`);
}
injectStatusColors() {
}
async activateView() {
const { workspace } = this.app;
let leaf = null;
const leaves = workspace.getLeavesOfType("card-sidebar");
if (leaves.length > 0) {
leaf = leaves[0];
} else {
leaf = workspace.getRightLeaf(false);
if (leaf) {
await leaf.setViewState({ type: "card-sidebar", active: true });
}
}
if (leaf) {
void workspace.revealLeaf(leaf);
}
}
wrapWith(start, end) {
const activeEl = document.activeElement;
if (!(activeEl instanceof HTMLElement) || activeEl.isContentEditable !== true) {
new import_obsidian10.Notice("No editable field focused");
return;
}
const sel = window.getSelection();
if (!sel || sel.rangeCount === 0)
return;
const range = sel.getRangeAt(0);
if (range.collapsed) {
const wordRange = this.getWordRangeAtCaret(sel);
if (wordRange) {
sel.removeAllRanges();
sel.addRange(wordRange);
}
}
const text = range.toString();
const wrapped = start + text + end;
range.deleteContents();
range.insertNode(document.createTextNode(wrapped));
const newRange = document.createRange();
newRange.setStart(range.startContainer, range.startOffset + start.length);
newRange.collapse(true);
sel.removeAllRanges();
sel.addRange(newRange);
}
isWordChar(char) {
return /[A-Za-z0-9_]/.test(char);
}
getWordRangeAtCaret(selection) {
if (!selection.rangeCount)
return null;
const baseRange = selection.getRangeAt(0);
if (!baseRange.collapsed)
return baseRange;
const node = baseRange.startContainer;
if (!(node == null ? void 0 : node.data))
return null;
const text = node.data;
if (!text)
return null;
const offset = baseRange.startOffset;
const leftChar = offset > 0 ? text[offset - 1] : "";
const rightChar = offset < text.length ? text[offset] : "";
if (!this.isWordChar(leftChar) && !this.isWordChar(rightChar))
return null;
let start = offset;
let end = offset;
while (start > 0 && this.isWordChar(text[start - 1]))
start--;
while (end < text.length && this.isWordChar(text[end]))
end++;
const wordRange = document.createRange();
wordRange.setStart(node, start);
wordRange.setEnd(node, end);
return wordRange;
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
async activateHomeView() {
const existing = this.app.workspace.getLeavesOfType("sidecards-home");
if (existing.length > 0) {
void this.app.workspace.revealLeaf(existing[0]);
return;
}
const leaf = this.app.workspace.getLeaf(true);
if (leaf) {
await leaf.setViewState({ type: "sidecards-home", active: true });
void this.app.workspace.revealLeaf(leaf);
}
}
refreshHomepageViews() {
this.app.workspace.getLeavesOfType("sidecards-home").forEach((leaf) => {
const view = leaf.view;
if (typeof view.onOpen === "function") {
try {
void view.onOpen();
} catch (e) {
}
}
});
}
async replaceWithHomepage(leaf) {
try {
await leaf.setViewState({ type: "sidecards-home", active: true });
} catch (e) {
}
}
showStorageFolderSetupModal() {
const modal = new import_obsidian10.Modal(this.app);
modal.modalEl.addClass("sc-setup-modal");
modal.titleEl.setText("Welcome to sidecards");
const content = modal.contentEl;
content.createEl("p", { text: "Set a storage folder to save your cards as notes." });
const folderRow = content.createDiv({ cls: "sc-setup-folder-row" });
const folderInput = folderRow.createEl("input", {
type: "text",
placeholder: "e.g. Cards",
cls: "sc-setup-folder-input"
});
const suggestEl = folderRow.createDiv({ cls: "sc-setup-folder-suggest" });
const folderSet = /* @__PURE__ */ new Set();
this.app.vault.getAllLoadedFiles().forEach((f) => {
if (f.children) {
if (f.path && f.path !== "/")
folderSet.add(f.path);
} else if (f.parent && f.parent.path && f.parent.path !== "/") {
folderSet.add(f.parent.path);
}
});
const allFolders = Array.from(folderSet).sort();
const renderSuggestions = (query) => {
suggestEl.empty();
const matches = query ? allFolders.filter((p) => p.toLowerCase().includes(query.toLowerCase())) : allFolders;
if (matches.length === 0) {
suggestEl.removeClass("is-visible");
return;
}
matches.slice(0, 10).forEach((path) => {
const item = suggestEl.createDiv({ cls: "sc-setup-folder-item", text: path });
item.addEventListener("mousedown", (e) => {
e.preventDefault();
folderInput.value = path;
suggestEl.removeClass("is-visible");
});
});
suggestEl.addClass("is-visible");
};
folderInput.addEventListener("focus", () => renderSuggestions(folderInput.value));
folderInput.addEventListener("input", () => renderSuggestions(folderInput.value));
folderInput.addEventListener("blur", () => window.setTimeout(() => suggestEl.removeClass("is-visible"), 150));
const btnRow = content.createDiv({ cls: "sc-setup-btn-row" });
const cancelBtn = btnRow.createEl("button", { text: "Cancel" });
cancelBtn.addEventListener("click", () => {
this.settings.tutorialShown = true;
void this.saveSettings();
modal.close();
});
const saveBtn = btnRow.createEl("button", { text: "Save", cls: "mod-cta" });
saveBtn.addEventListener("click", () => {
void (async () => {
const val = folderInput.value.trim();
if (val) {
this.settings.storageFolder = val;
this.settings.tutorialShown = true;
await this.saveSettings();
await this.store.switchStorageFolder(val);
}
modal.close();
})();
});
modal.open();
window.setTimeout(() => folderInput.focus(), 50);
}
async fetchAllReleases() {
const allReleases = [];
let page = 1;
let hasMorePages = true;
while (hasMorePages) {
const url = `https://api.github.com/repos/Kazi-Aidah/sidecards/releases?page=${page}&per_page=100`;
try {
const res = await (0, import_obsidian10.requestUrl)({
url,
headers: {
"Accept": "application/vnd.github.v3+json",
"User-Agent": "Obsidian-Sidecards"
}
});
const data = res.json;
if (!Array.isArray(data) || data.length === 0) {
hasMorePages = false;
} else {
allReleases.push(...data);
if (data.length < 100)
hasMorePages = false;
else
page++;
}
} catch (e) {
hasMorePages = false;
}
}
return allReleases;
}
};
var SideCardsSettingTab = class extends import_obsidian10.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
updateCSSVariables() {
const root = document.documentElement;
root.setCssProps({
"--card-color-1": this.plugin.settings.color1 || "#8392a4",
"--card-color-2": this.plugin.settings.color2 || "#eb3b5a",
"--card-color-3": this.plugin.settings.color3 || "#fa8231",
"--card-color-4": this.plugin.settings.color4 || "#e5a216",
"--card-color-5": this.plugin.settings.color5 || "#20bf6b",
"--card-color-6": this.plugin.settings.color6 || "#2d98da",
"--card-color-7": this.plugin.settings.color7 || "#8854d0",
"--card-color-8": this.plugin.settings.color8 || "#e832c1",
"--card-color-9": this.plugin.settings.color9 || "#e83289",
"--card-color-10": this.plugin.settings.color10 || "#965b3b"
});
}
applyColorToDropdown(selectEl, colorIndex) {
const colorKey = `color${colorIndex}`;
const color = this.plugin.settings[colorKey] || "#ffffff";
selectEl.style.backgroundColor = color;
selectEl.style.color = this.getContrastColor(color);
Array.from(selectEl.options).forEach((option, i) => {
const idx = i + 1;
const cKey = `color${idx}`;
const c = this.plugin.settings[cKey] || "#ffffff";
option.style.backgroundColor = c;
option.style.color = this.getContrastColor(c);
});
}
getContrastColor(hex) {
var _a;
if (hex && hex.startsWith("var")) {
const varName = (_a = hex.match(/--[a-zA-Z0-9-]+/)) == null ? void 0 : _a[0];
if (varName) {
hex = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
}
}
if (typeof hex !== "string" || !hex.startsWith("#")) {
return "#1a1a1a";
}
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
if (isNaN(r) || isNaN(g) || isNaN(b)) {
return "#1a1a1a";
}
const yiq = (r * 299 + g * 587 + b * 114) / 1e3;
if (yiq >= 128) {
const dr = Math.round(r * 0.15);
const dg = Math.round(g * 0.15);
const db = Math.round(b * 0.15);
return `#${dr.toString(16).padStart(2, "0")}${dg.toString(16).padStart(2, "0")}${db.toString(16).padStart(2, "0")}`;
} else {
const lr = Math.round(r + (255 - r) * 0.85);
const lg = Math.round(g + (255 - g) * 0.85);
const lb = Math.round(b + (255 - b) * 0.85);
return `#${lr.toString(16).padStart(2, "0")}${lg.toString(16).padStart(2, "0")}${lb.toString(16).padStart(2, "0")}`;
}
}
/**
* Attaches touch-based drag-to-reorder to a settings container.
* Used as a mobile fallback since HTML5 drag API doesn't work on touch devices.
* Items must have a [data-drag-id] attribute.
* @param container The element containing the draggable setting rows
* @param getItemSelector CSS selector for draggable rows within container
* @param getDragId Returns the drag ID from a row element
* @param onDrop Called with [fromId, toId, insertBefore] when a drop occurs
*/
attachSettingsTouchDrag(container, getItemSelector, getDragId, onDrop) {
let ghost = null;
let draggedEl = null;
let draggedId = null;
let offsetY = 0;
let active = false;
const THRESHOLD = 8;
let startY = 0;
let startX = 0;
let pending = false;
const getRows = () => Array.from(container.querySelectorAll(getItemSelector));
const onPointerDown = (e) => {
const handle = e.target.closest(".sc-drag-handle");
if (!handle)
return;
const row = handle.closest(getItemSelector);
if (!row)
return;
const id = getDragId(row);
if (!id)
return;
draggedEl = row;
draggedId = id;
startX = e.clientX;
startY = e.clientY;
pending = true;
const rect = row.getBoundingClientRect();
offsetY = e.clientY - rect.top;
document.addEventListener("pointermove", onPointerMove, { passive: false });
document.addEventListener("pointerup", onPointerUp);
};
const onPointerMove = (e) => {
if (pending) {
const dy = Math.abs(e.clientY - startY);
const dx = Math.abs(e.clientX - startX);
if (Math.hypot(dx, dy) < THRESHOLD)
return;
pending = false;
active = true;
const rect = draggedEl.getBoundingClientRect();
ghost = draggedEl.cloneNode(true);
ghost.classList.add("sc-drag-ghost", "sc-settings-drag-ghost");
const cs = getComputedStyle(draggedEl);
ghost.setCssStyles({
position: "fixed",
zIndex: "9999",
pointerEvents: "none",
width: rect.width + "px",
left: rect.left + "px",
top: e.clientY - offsetY + "px",
background: cs.background,
backgroundColor: cs.backgroundColor,
borderRadius: cs.borderRadius,
padding: cs.padding,
fontSize: cs.fontSize,
color: cs.color,
boxShadow: "0 4px 16px rgba(0,0,0,0.25)",
opacity: "0.9"
});
document.body.appendChild(ghost);
draggedEl.setCssProps({ "opacity": "0.3" });
}
if (!active || !ghost || !draggedEl)
return;
e.preventDefault();
ghost.setCssProps({ "top": e.clientY - offsetY + "px" });
getRows().forEach((row) => row.removeClass("sc-drag-over-top", "sc-drag-over-bottom"));
const target = getDropTarget(e.clientY);
if (target && target.el !== draggedEl) {
target.el.addClass(target.insertBefore ? "sc-drag-over-top" : "sc-drag-over-bottom");
}
};
const getDropTarget = (clientY) => {
const rows = getRows().filter((r) => r !== draggedEl);
for (const row of rows) {
const r = row.getBoundingClientRect();
if (clientY >= r.top && clientY <= r.bottom) {
return { el: row, insertBefore: clientY < r.top + r.height / 2 };
}
}
return null;
};
const onPointerUp = (e) => {
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
getRows().forEach((row) => row.removeClass("sc-drag-over-top", "sc-drag-over-bottom"));
if (active && draggedEl && draggedId) {
const target = getDropTarget(e.clientY);
if (target && target.el !== draggedEl) {
const toId = getDragId(target.el);
if (toId)
onDrop(draggedId, toId, target.insertBefore);
}
}
ghost == null ? void 0 : ghost.remove();
ghost = null;
if (draggedEl)
draggedEl.setCssProps({ "opacity": "" });
draggedEl = null;
draggedId = null;
active = false;
pending = false;
};
container.addEventListener("pointerdown", onPointerDown);
return () => {
container.removeEventListener("pointerdown", onPointerDown);
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
ghost == null ? void 0 : ghost.remove();
if (draggedEl)
draggedEl.setCssProps({ "opacity": "" });
};
}
display() {
const { containerEl } = this;
containerEl.empty();
const updateButtonPadding = (paddingPx) => {
this.plugin.settings.buttonPaddingBottom = paddingPx;
this.plugin.applyButtonPadding();
};
const refreshHomepage = () => this.plugin.refreshHomepageViews();
const refreshSidebarHeader = () => {
var _a;
try {
const view = (_a = this.app.workspace.getLeavesOfType("card-sidebar")[0]) == null ? void 0 : _a.view;
if (!view)
return;
const main = view.containerEl.querySelector(".sc-sidebar-main");
const old = main == null ? void 0 : main.querySelector(".sc-sidebar-header");
if (old)
old.remove();
if (main && typeof view.createHeader === "function") {
view.createHeader(main);
const header = main.querySelector(".sc-sidebar-header");
if (header)
main.prepend(header);
}
try {
if (typeof view.applyFilters === "function")
view.applyFilters();
} catch (e) {
}
} catch (e) {
}
refreshHomepage();
};
const refreshSidebarCards = () => {
var _a;
try {
const view = (_a = this.app.workspace.getLeavesOfType("card-sidebar")[0]) == null ? void 0 : _a.view;
if (view && typeof view.renderCards === "function") {
view.renderCards();
}
} catch (e) {
}
};
new import_obsidian10.Setting(containerEl).setName("Latest release notes").setDesc("View the most recent plugin release notes.").addButton((b) => {
b.setButtonText("Open Changelog").onClick(() => {
new ChangelogModal(this.app, this.plugin).open();
});
});
new import_obsidian10.Setting(containerEl).setName("Storage folder").setDesc("Choose where to save notes created from cards.").addButton((b) => {
var _a;
b.setButtonText(((_a = this.plugin.settings.storageFolder) == null ? void 0 : _a.trim()) ? this.plugin.settings.storageFolder : "Unset").onClick(() => {
const folders = this.app.vault.getAllLoadedFiles().filter((f) => f instanceof import_obsidian10.TFolder).map((f) => f.path).sort();
const suggest = new FolderSuggestModal(this.app, folders, async (folder) => {
var _a2;
const newFolder = folder || "";
const oldFolder = this.plugin.settings.storageFolder;
this.plugin.settings.storageFolder = newFolder;
this.plugin.settings.tutorialShown = true;
await this.plugin.saveSettings();
if (newFolder !== oldFolder) {
await this.plugin.store.switchStorageFolder(newFolder);
}
b.setButtonText(((_a2 = this.plugin.settings.storageFolder) == null ? void 0 : _a2.trim()) ? this.plugin.settings.storageFolder : "Unset");
refreshSidebarCards();
refreshHomepage();
});
suggest.setPlaceholder("Select storage folder...");
suggest.open();
});
});
new import_obsidian10.Setting(containerEl).setName("Behaviour").setDesc("").setHeading();
new import_obsidian10.Setting(containerEl).setName("Note title format").setDesc("Format used when creating a note from a card.").addDropdown((dd) => dd.addOption("words3_hhmm", "First 3 words + hhmm").addOption("words5_hhmm", "First 5 words + hhmm").addOption("datetime", "Date and time").setValue(this.plugin.settings.noteTitleFormat || "words3_hhmm").onChange(async (value) => {
this.plugin.settings.noteTitleFormat = value;
await this.plugin.saveSettings();
}));
new import_obsidian10.Setting(containerEl).setName("Save key").setDesc("Choose which key combo saves/submits a card").addDropdown((dropdown) => dropdown.addOption("enter", "Enter").addOption("shift-enter", "Shift+Enter").addOption("ctrl-enter", "Ctrl+Enter").addOption("alt-enter", "Alt+Enter").addOption("ctrl-shift-enter", "Ctrl+Shift+Enter").setValue(this.plugin.settings.saveKey || "enter").onChange(async (value) => {
this.plugin.settings.saveKey = value;
await this.plugin.saveSettings();
}));
new import_obsidian10.Setting(containerEl).setName("Next line key").setDesc("Choose which key combo inserts a new line inside a card (does not save)").addDropdown((dropdown) => dropdown.addOption("enter", "Enter").addOption("shift-enter", "Shift+Enter").addOption("ctrl-enter", "Ctrl+Enter").addOption("alt-enter", "Alt+Enter").addOption("ctrl-shift-enter", "Ctrl+Shift+Enter").setValue(this.plugin.settings.nextLineKey || "shift-enter").onChange(async (value) => {
this.plugin.settings.nextLineKey = value;
await this.plugin.saveSettings();
}));
new import_obsidian10.Setting(containerEl).setName("Auto-pair brackets, quotes and code").setDesc('Automatically wrap selected text or place cursor between pairs when typing (, [, {, `, =, %, or "').addToggle((toggle) => toggle.setValue(this.plugin.settings.autoPairBrackets !== false).onChange(async (value) => {
this.plugin.settings.autoPairBrackets = value;
await this.plugin.saveSettings();
}));
new import_obsidian10.Setting(containerEl).setName("Auto-open sidebar").setDesc("Automatically open the sidebar when Obsidian starts").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.autoOpen).onChange(async (value) => {
this.plugin.settings.autoOpen = value;
await this.plugin.saveSettings();
}));
new import_obsidian10.Setting(containerEl).setName("Auto-archive on expiry").setDesc("Automatically archive cards when their expiry time passes").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.autoArchiveOnExpiry).onChange(async (value) => {
this.plugin.settings.autoArchiveOnExpiry = value;
await this.plugin.saveSettings();
}));
new import_obsidian10.Setting(containerEl).setName("Show time left to expire").setDesc("Show a pill on cards with the remaining time until expiry.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.showExpiryTimeLeft).onChange(async (value) => {
this.plugin.settings.showExpiryTimeLeft = value;
await this.plugin.saveSettings();
refreshSidebarCards();
}));
new import_obsidian10.Setting(containerEl).setName("Expiry time format").setDesc("How to display the remaining expiry time.").addDropdown((dd) => dd.addOption("human", "Human (2 years 3 months 5 days)").addOption("short", "Short (2y 3mo 5d 4h 3m)").setValue(this.plugin.settings.expiryTimeFormat || "human").onChange(async (value) => {
this.plugin.settings.expiryTimeFormat = value;
await this.plugin.saveSettings();
refreshSidebarCards();
}));
new import_obsidian10.Setting(containerEl).setName("Homepage").setDesc("Configure the sidecards homepage tab.").setHeading();
new import_obsidian10.Setting(containerEl).setName("Replace default tab with homepage").setDesc("Open the sidecards homepage instead of the default new tab.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.replaceHomepageWithSidecards).onChange(async (value) => {
this.plugin.settings.replaceHomepageWithSidecards = value;
await this.plugin.saveSettings();
}));
new import_obsidian10.Setting(containerEl).setName("Replace sidecards name").setDesc("Title shown in the homepage.").addText((text) => {
text.setPlaceholder("Sidecards").setValue(this.plugin.settings.homepageName || "Sidecards").onChange(async (value) => {
this.plugin.settings.homepageName = value || "Sidecards";
await this.plugin.saveSettings();
refreshHomepage();
});
text.inputEl.addClass("sc-full-width");
}).addExtraButton((btn) => {
btn.setIcon("rotate-ccw").setTooltip("Reset to default").onClick(async () => {
this.plugin.settings.homepageName = "Sidecards";
await this.plugin.saveSettings();
refreshHomepage();
this.display();
});
});
new import_obsidian10.Setting(containerEl).setName("Hide category dropdown").setDesc("Hides the category button and separator in the homepage palette row.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.hideCategoryDropdown).onChange(async (value) => {
this.plugin.settings.hideCategoryDropdown = value;
await this.plugin.saveSettings();
refreshHomepage();
}));
new import_obsidian10.Setting(containerEl).setName("Hide color swatches").setDesc("Hides the color dots from the homepage.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.hideColorSwatches).onChange(async (value) => {
this.plugin.settings.hideColorSwatches = value;
await this.plugin.saveSettings();
refreshHomepage();
}));
new import_obsidian10.Setting(containerEl).setName("Show pinned notes").setDesc("Show pinned notes in the homepage notes column.").addToggle((toggle) => toggle.setValue(this.plugin.settings.showPinnedNotes !== false).onChange((value) => {
void (async () => {
this.plugin.settings.showPinnedNotes = value;
await this.plugin.saveSettings();
refreshHomepage();
})();
}));
new import_obsidian10.Setting(containerEl).setName("Show recent notes").setDesc("Show recently opened notes in the homepage notes column.").addToggle((toggle) => toggle.setValue(this.plugin.settings.showRecentNotes !== false).onChange((value) => {
void (async () => {
this.plugin.settings.showRecentNotes = value;
await this.plugin.saveSettings();
refreshHomepage();
})();
}));
new import_obsidian10.Setting(containerEl).setName("Recent notes limit").setDesc("How many recent notes to show.").addDropdown((dd) => {
var _a;
[3, 5, 10, 15, 20, 25].forEach((n) => {
dd.addOption(String(n), String(n));
});
dd.setValue(String((_a = this.plugin.settings.recentNotesLimit) != null ? _a : 5));
dd.onChange((value) => {
void (async () => {
this.plugin.settings.recentNotesLimit = Number(value);
await this.plugin.saveSettings();
refreshHomepage();
})();
});
});
new import_obsidian10.Setting(containerEl).setName("Notes column placement").setDesc("Place the pinned/recent notes column on the left or right.").addDropdown((dd) => dd.addOption("left", "Left").addOption("right", "Right").setValue(this.plugin.settings.notesPlacement || "left").onChange((value) => {
void (async () => {
this.plugin.settings.notesPlacement = value;
await this.plugin.saveSettings();
refreshHomepage();
})();
}));
new import_obsidian10.Setting(containerEl).setName("Homepage max width").setDesc("Maximum width of the homepage content area in pixels. Drag to adjust.").addSlider((slider) => {
var _a, _b;
const label = containerEl.createSpan({ text: `${(_a = this.plugin.settings.homepageMaxWidth) != null ? _a : 1e3}px`, cls: "sc-slider-label" });
slider.setLimits(400, 2400, 50).setValue((_b = this.plugin.settings.homepageMaxWidth) != null ? _b : 1e3).setDynamicTooltip().onChange(async (value) => {
this.plugin.settings.homepageMaxWidth = value;
label.textContent = `${value}px`;
await this.plugin.saveSettings();
document.querySelectorAll(".sc-home-container").forEach((el) => {
el.setCssProps({ "--sc-home-max-width": `${value}px` });
});
});
slider.sliderEl.insertAdjacentElement("afterend", label);
});
new import_obsidian10.Setting(containerEl).setName("Homepage top spacing").setDesc("Distance from the top of the homepage to the content area, in pixels.").addSlider((slider) => {
var _a, _b;
const label = containerEl.createSpan({ text: `${(_a = this.plugin.settings.homepageTopMargin) != null ? _a : 70}px`, cls: "sc-slider-label" });
slider.setLimits(0, 300, 5).setValue((_b = this.plugin.settings.homepageTopMargin) != null ? _b : 70).setDynamicTooltip().onChange(async (value) => {
this.plugin.settings.homepageTopMargin = value;
label.textContent = `${value}px`;
await this.plugin.saveSettings();
document.documentElement.style.setProperty("--sc-home-top-margin", `${value}px`);
});
slider.sliderEl.insertAdjacentElement("afterend", label);
});
new import_obsidian10.Setting(containerEl).setName("Appearance").setDesc("Customize how cards and the sidebar look.").setHeading();
const previewContainer = containerEl.createDiv({ cls: "sc-preview-wrapper" });
const previewCard = previewContainer.createDiv({ cls: "sc-card sc-settings-preview-card" });
const updatePreview = () => {
var _a;
const settings = this.plugin.settings;
previewCard.className = "sc-card";
previewCard.addClass(`sc-style-${settings.cardStyle || 2}`);
const color1 = settings.color1 || "#8392a4";
const root = document.documentElement;
root.setCssProps({ "--card-color-1": color1 });
previewCard.setCssProps({
"border-radius": `${settings.borderRadius || 0}px`,
"border-width": `${(_a = settings.borderThickness) != null ? _a : 2}px`
});
const colorSettings = {
cardStyle: settings.cardStyle,
cardBgOpacity: settings.cardBgOpacity,
borderThickness: settings.borderThickness,
cardBorderShadowOpacity: settings.cardBorderShadowOpacity
};
void Promise.resolve().then(() => (init_dom(), dom_exports)).then(({ applyCardColorToElement: applyCardColorToElement2 }) => {
applyCardColorToElement2(previewCard, "var(--card-color-1)", colorSettings);
});
previewCard.empty();
previewCard.createDiv({ text: "This is how yours cards will look like!", cls: "sc-content" });
if (settings.groupTags) {
if (settings.showTimestamps && settings.timestampBelowTags) {
previewCard.createDiv({ cls: "sc-timestamp", text: window.moment ? window.moment().format(settings.datetimeFormat || "ddd D") : "Today 12:00" });
}
const tagsEl = previewCard.createDiv({ cls: "sc-tags" });
["ideas", "project"].forEach((t) => {
tagsEl.createSpan({ cls: "sc-tag", text: settings.omitTagHash ? t : `#${t}` });
});
if (settings.showTimestamps && !settings.timestampBelowTags) {
previewCard.createDiv({ cls: "sc-timestamp sc-timestamp--inline-spaced", text: window.moment ? window.moment().format(settings.datetimeFormat || "ddd D") : "Today 12:00" });
}
} else {
if (settings.showTimestamps) {
previewCard.createDiv({ cls: "sc-timestamp", text: window.moment ? window.moment().format(settings.datetimeFormat || "ddd D") : "Today 12:00" });
}
}
};
updatePreview();
new import_obsidian10.Setting(containerEl).setName("Card style").setDesc("Choose card design style").addDropdown((dropdown) => dropdown.addOption("1", "Style 1 (flat)").addOption("2", "Style 2 (shadow)").addOption("3", "Style 3 (left accent)").setValue(String(this.plugin.settings.cardStyle || 2)).onChange(async (value) => {
this.plugin.settings.cardStyle = Number(value);
await this.plugin.saveSettings();
updatePreview();
refreshSidebarCards();
}));
new import_obsidian10.Setting(containerEl).setName("Card background opacity").setDesc("Set the transparency of the card background").addSlider((slider) => slider.setLimits(0.01, 1, 0.01).setValue(this.plugin.settings.cardBgOpacity || 0.08).setDynamicTooltip().onChange(async (value) => {
this.plugin.settings.cardBgOpacity = value;
await this.plugin.saveSettings();
updatePreview();
refreshSidebarCards();
}));
new import_obsidian10.Setting(containerEl).setName("Card border thickness").setDesc("Set the thickness of the card border").addSlider((slider) => slider.setLimits(1, 20, 1).setValue(this.plugin.settings.borderThickness || 2).setDynamicTooltip().onChange(async (value) => {
this.plugin.settings.borderThickness = value;
await this.plugin.saveSettings();
updatePreview();
refreshSidebarCards();
}));
new import_obsidian10.Setting(containerEl).setName("Card border and shadow opacity").setDesc("Set the opacity of the card border and shadow color").addSlider((slider) => {
var _a;
return slider.setLimits(0, 1, 0.05).setValue((_a = this.plugin.settings.cardBorderShadowOpacity) != null ? _a : 1).setDynamicTooltip().onChange(async (value) => {
this.plugin.settings.cardBorderShadowOpacity = value;
await this.plugin.saveSettings();
updatePreview();
refreshSidebarCards();
});
});
new import_obsidian10.Setting(containerEl).setName("Card border radius").setDesc("Set the corner rounding of the card").addSlider((slider) => slider.setLimits(0, 30, 1).setValue(this.plugin.settings.borderRadius || 0).setDynamicTooltip().onChange(async (value) => {
this.plugin.settings.borderRadius = value;
await this.plugin.saveSettings();
document.documentElement.setCssProps({ "--card-border-radius": `${value}px` });
updatePreview();
refreshSidebarCards();
}));
new import_obsidian10.Setting(containerEl).setName("Maximum card height").setDesc("Limit card height in pixels (0 = no limit)").addSlider((slider) => slider.setLimits(0, 800, 10).setValue(this.plugin.settings.maxCardHeight || 0).setDynamicTooltip().onChange(async (value) => {
this.plugin.settings.maxCardHeight = Number(value) || 0;
await this.plugin.saveSettings();
this.plugin.applyMaxCardHeight();
}));
new import_obsidian10.Setting(containerEl).setName("Bottom space under input row").setDesc("Padding to accommodate the status bar").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.plugin.settings.buttonPaddingBottom || 26).onChange(async (value) => {
this.plugin.settings.buttonPaddingBottom = Number(value) || 0;
await this.plugin.saveSettings();
updateButtonPadding(this.plugin.settings.buttonPaddingBottom || 0);
}));
new import_obsidian10.Setting(containerEl).setName("Group tags under content").setDesc("Show tags grouped below card content.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.groupTags).onChange(async (value) => {
this.plugin.settings.groupTags = value;
await this.plugin.saveSettings();
updatePreview();
}));
new import_obsidian10.Setting(containerEl).setName("Omit # prefix for tags").setDesc("Display tags without the leading #").addToggle((toggle) => {
var _a;
return toggle.setValue((_a = this.plugin.settings.omitTagHash) != null ? _a : true).onChange(async (value) => {
this.plugin.settings.omitTagHash = value;
await this.plugin.saveSettings();
updatePreview();
});
});
new import_obsidian10.Setting(containerEl).setName("Show timestamps").setDesc("Show creation timestamps on cards").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.showTimestamps).onChange(async (value) => {
this.plugin.settings.showTimestamps = value;
await this.plugin.saveSettings();
updatePreview();
}));
const timestampSetting = new import_obsidian10.Setting(containerEl).setName("Date and time format for timestamps").addText((text) => text.setPlaceholder("Example format").setValue(this.plugin.settings.datetimeFormat || "YYYY-MM-DD hh:mma").onChange(async (value) => {
this.plugin.settings.datetimeFormat = value;
await this.plugin.saveSettings();
updateTimestampPreview(value);
updatePreview();
}));
const updateTimestampPreview = (val) => {
timestampSetting.descEl.empty();
timestampSetting.descEl.createSpan({ text: "Your current format: " });
const m = window.moment;
const span = timestampSetting.descEl.createSpan({ text: m ? m().format(val || "ddd D") : new Date().toLocaleString() });
span.setCssStyles({ fontWeight: "bold", color: "var(--color-accent)" });
};
updateTimestampPreview(this.plugin.settings.datetimeFormat || "YYYY-MM-DD hh:mma");
new import_obsidian10.Setting(containerEl).setName("Bring timestamp above tags").setDesc("Render timestamp above grouped tags.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.timestampBelowTags).onChange(async (value) => {
this.plugin.settings.timestampBelowTags = value;
await this.plugin.saveSettings();
updatePreview();
}));
new import_obsidian10.Setting(containerEl).setName("Animate cards").setDesc("Cards slide and fade.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.animatedCards).onChange(async (value) => {
this.plugin.settings.animatedCards = value;
await this.plugin.saveSettings();
}));
new import_obsidian10.Setting(containerEl).setName("Disable card fade in").setDesc("Cards appear without fading.").addToggle((toggle) => {
var _a;
return toggle.setValue((_a = this.plugin.settings.disableCardFadeIn) != null ? _a : false).onChange(async (value) => {
this.plugin.settings.disableCardFadeIn = value;
await this.plugin.saveSettings();
});
});
new import_obsidian10.Setting(containerEl).setName("Hide card container scrollbar").setDesc("Hides the scrollbar visually.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.hideScrollbar).onChange(async (value) => {
var _a;
this.plugin.settings.hideScrollbar = value;
await this.plugin.saveSettings();
try {
const view = (_a = this.app.workspace.getLeavesOfType("card-sidebar")[0]) == null ? void 0 : _a.view;
if (view && typeof view.applyScrollbarSetting === "function")
view.applyScrollbarSetting();
} catch (e) {
}
}));
new import_obsidian10.Setting(containerEl).setName("Hide categories topbar").setDesc("Hide the category filter button bar.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.disableFilterButtons).onChange(async (value) => {
this.plugin.settings.disableFilterButtons = value;
await this.plugin.saveSettings();
refreshSidebarHeader();
}));
new import_obsidian10.Setting(containerEl).setName("Enable copy card content").setDesc("Show a copy icon on card hover.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.enableCopyCardContent).onChange(async (value) => {
this.plugin.settings.enableCopyCardContent = value;
await this.plugin.saveSettings();
}));
this.updateCSSVariables();
updateButtonPadding(this.plugin.settings.buttonPaddingBottom || 26);
this.plugin.applyMaxCardHeight();
new import_obsidian10.Setting(containerEl).setName("Colors").setDesc("Card colors used for tagging. Names are written to notes.").setHeading();
new import_obsidian10.Setting(containerEl).setName("Two row color swatches in menu").setDesc("Show colors in 2 rows of 5 in the card menu.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.twoRowSwatches).onChange(async (value) => {
this.plugin.settings.twoRowSwatches = value;
await this.plugin.saveSettings();
}));
const colorVars = [
{ name: "Color 1", key: "color1", default: "#8392a4" },
{ name: "Color 2", key: "color2", default: "#eb3b5a" },
{ name: "Color 3", key: "color3", default: "#fa8231" },
{ name: "Color 4", key: "color4", default: "#e5a216" },
{ name: "Color 5", key: "color5", default: "#20bf6b" },
{ name: "Color 6", key: "color6", default: "#2d98da" },
{ name: "Color 7", key: "color7", default: "#8854d0" },
{ name: "Color 8", key: "color8", default: "#e832c1" },
{ name: "Color 9", key: "color9", default: "#e83289" },
{ name: "Color 10", key: "color10", default: "#965b3b" }
];
colorVars.forEach((color, i) => {
const row = new import_obsidian10.Setting(containerEl).setName(color.name);
row.addText((txt) => txt.setPlaceholder("Example: red").setValue(this.plugin.settings.colorNames && this.plugin.settings.colorNames[i] || "").onChange(async (v) => {
if (!this.plugin.settings.colorNames)
this.plugin.settings.colorNames = [];
this.plugin.settings.colorNames[i] = v || "";
await this.plugin.saveSettings();
}));
row.addColorPicker((cp) => cp.setValue(this.plugin.settings[color.key] || color.default).onChange(async (value) => {
this.plugin.settings[color.key] = value;
await this.plugin.saveSettings();
this.updateCSSVariables();
if (color.key === "color1")
updatePreview();
refreshSidebarCards();
}));
row.addExtraButton((btn) => btn.setIcon("rotate-ccw").setTooltip("Reset to default").onClick(async () => {
this.plugin.settings[color.key] = color.default;
await this.plugin.saveSettings();
this.updateCSSVariables();
if (color.key === "color1")
updatePreview();
refreshSidebarCards();
this.display();
}));
});
new import_obsidian10.Setting(containerEl).setName("Categories").setDesc("Configure category display and reordering.").setHeading();
const catsContainer = containerEl.createDiv({ cls: "categories-list sc-cats-container" });
const renderCategories = () => {
catsContainer.empty();
const customList = Array.isArray(this.plugin.settings.customCategories) ? this.plugin.settings.customCategories : [];
const builtinItems = [
{ id: "filter-all", label: "All", canHide: false, canRemove: false },
{ id: "filter-today", label: "Today", canHide: true, canRemove: false, settingKey: "hideTodayFilter", defaultIcon: "calendar-check", builtinKey: "today" },
{ id: "filter-tomorrow", label: "Tomorrow", canHide: true, canRemove: false, settingKey: "hideTomorrowFilter", defaultIcon: "calendar-plus", builtinKey: "tomorrow" },
{ id: "filter-archived", label: "Archived", canHide: true, canRemove: false, settingKey: "hideArchivedFilterButton", defaultIcon: "archive", builtinKey: "archived" }
];
const customItems = customList.map((c) => ({
id: c.id,
label: c.label,
canHide: true,
canRemove: true,
isCustom: true,
data: c
}));
const allItems = [...builtinItems, ...customItems];
const validIds = new Set(allItems.map((item) => item.id));
let orderedIds = (this.plugin.settings.allItemsOrder || []).filter((id) => validIds.has(id));
allItems.forEach((item) => {
if (!orderedIds.includes(item.id))
orderedIds.push(item.id);
});
let dragSrcId = null;
const renderRow = (itemId) => {
var _a, _b, _c, _d, _e;
const itemInfo = allItems.find((i) => i.id === itemId);
if (!itemInfo)
return;
const isBuiltin = itemId.startsWith("filter-");
const colorKey = isBuiltin ? itemId.replace("filter-", "") : itemId;
let isVisible = true;
if (isBuiltin) {
const sKey = itemInfo.settingKey;
if (sKey === "hideTodayFilter") {
isVisible = !this.plugin.settings.hideTodayFilter;
} else if (sKey === "hideTomorrowFilter") {
isVisible = !this.plugin.settings.hideTomorrowFilter;
} else if (sKey === "hideArchivedFilterButton") {
isVisible = !this.plugin.settings.hideArchivedFilterButton;
}
} else {
isVisible = ((_a = itemInfo.data) == null ? void 0 : _a.showInMenu) !== false;
}
const setting = new import_obsidian10.Setting(catsContainer);
setting.settingEl.setAttr("data-cat-id", itemId);
setting.settingEl.setAttr("draggable", "true");
setting.settingEl.addEventListener("dragstart", (e) => {
var _a2;
dragSrcId = itemId;
(_a2 = e.dataTransfer) == null ? void 0 : _a2.setData("text/plain", itemId);
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = "move";
}
window.setTimeout(() => {
setting.settingEl.addClass("sc-dragging");
}, 0);
});
setting.settingEl.addEventListener("dragend", () => {
catsContainer.querySelectorAll(".sc-dragging").forEach((el) => el.removeClass("sc-dragging"));
dragSrcId = null;
});
setting.settingEl.addEventListener("dragover", (e) => {
e.preventDefault();
if (dragSrcId === itemId)
return;
const target = setting.settingEl;
const rect = target.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
if (e.clientY < midY) {
target.removeClass("sc-drag-over-bottom");
target.addClass("sc-drag-over-top");
} else {
target.removeClass("sc-drag-over-top");
target.addClass("sc-drag-over-bottom");
}
});
setting.settingEl.addEventListener("dragleave", () => {
setting.settingEl.removeClass("sc-drag-over-top", "sc-drag-over-bottom");
});
setting.settingEl.addEventListener("drop", (e) => {
void (async () => {
e.preventDefault();
if (!dragSrcId || dragSrcId === itemId)
return;
const target = setting.settingEl;
target.removeClass("sc-drag-over-top", "sc-drag-over-bottom");
const allIds = Array.from(catsContainer.querySelectorAll(".setting-item[data-cat-id]")).map((el) => el.dataset.catId);
const srcIndex = allIds.indexOf(dragSrcId);
const destIndex = allIds.indexOf(itemId);
if (srcIndex === -1 || destIndex === -1)
return;
const rect = target.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
const insertBefore = e.clientY < midY;
const newOrder = allIds.filter((id) => id !== dragSrcId);
let finalIndex = destIndex;
if (srcIndex < destIndex && !insertBefore) {
finalIndex = destIndex;
} else if (srcIndex > destIndex && insertBefore) {
finalIndex = destIndex;
} else if (srcIndex < destIndex && insertBefore) {
finalIndex = destIndex - 1;
} else if (srcIndex > destIndex && !insertBefore) {
finalIndex = destIndex + 1;
}
newOrder.splice(finalIndex, 0, dragSrcId);
catsContainer.querySelectorAll(".sc-dragging").forEach((el) => el.removeClass("sc-dragging"));
this.plugin.settings.allItemsOrder = newOrder;
await this.plugin.saveSettings();
renderCategories();
refreshSidebarHeader();
})();
});
setting.infoEl.remove();
const row = setting.controlEl;
row.addClass("sc-cat-row-controls");
setting.addExtraButton((btn) => {
btn.setIcon("grip-vertical").setTooltip("Drag to reorder");
btn.extraSettingsEl.addClass("sc-drag-handle");
row.prepend(btn.extraSettingsEl);
});
const eyeBtn = row.createEl("button", { cls: "clickable-icon sc-eye sc-round-btn" });
const updateEye = () => {
eyeBtn.empty();
const iconName = isVisible ? "eye" : "eye-off";
try {
(0, import_obsidian10.setIcon)(eyeBtn, iconName);
} catch (e) {
eyeBtn.textContent = isVisible ? "\u{1F441}" : "\u{1F6AB}";
}
eyeBtn.title = isVisible ? "Visible" : "Hidden";
eyeBtn.removeClass("sc-round-btn--green", "sc-round-btn--red");
eyeBtn.addClass(isVisible ? "sc-round-btn--green" : "sc-round-btn--red");
};
if (itemInfo.canHide) {
updateEye();
eyeBtn.addEventListener("click", () => {
isVisible = !isVisible;
void (async () => {
if (isBuiltin) {
const sKey = itemInfo.settingKey;
if (sKey === "hideTodayFilter") {
this.plugin.settings.hideTodayFilter = !isVisible;
} else if (sKey === "hideTomorrowFilter") {
this.plugin.settings.hideTomorrowFilter = !isVisible;
} else if (sKey === "hideArchivedFilterButton") {
this.plugin.settings.hideArchivedFilterButton = !isVisible;
}
} else {
const idx = customList.findIndex((c) => c.id === itemId);
if (idx >= 0)
this.plugin.settings.customCategories[idx].showInMenu = isVisible;
}
await this.plugin.saveSettings();
updateEye();
refreshSidebarHeader();
applyPreviewColors();
})();
});
} else {
isVisible = true;
updateEye();
eyeBtn.addClass("sc-round-btn--disabled");
}
if (itemInfo.isCustom || itemInfo.builtinKey) {
const iconBtn = row.createEl("button", { cls: "clickable-icon sc-cat-icon-btn sc-round-btn" });
const getCurrentIcon = () => {
var _a2, _b2, _c2;
if (itemInfo.builtinKey) {
return (_b2 = (_a2 = this.plugin.settings.builtinCategoryIcons) == null ? void 0 : _a2[itemInfo.builtinKey]) != null ? _b2 : itemInfo.defaultIcon;
}
return (_c2 = itemInfo.data) == null ? void 0 : _c2.icon;
};
const updateIconBtn = () => {
iconBtn.empty();
const currentIcon = getCurrentIcon();
if (currentIcon) {
try {
(0, import_obsidian10.setIcon)(iconBtn, currentIcon);
} catch (e) {
iconBtn.textContent = "+";
}
iconBtn.removeClass("sc-icon-btn--plus");
} else {
iconBtn.textContent = "+";
iconBtn.addClass("sc-icon-btn--plus");
}
iconBtn.title = "Icon in context menu";
};
updateIconBtn();
iconBtn.addEventListener("click", () => {
const modal = new SidecardsIconPickerModal(
this.plugin.app,
(pickedIcon) => {
void (async () => {
if (itemInfo.builtinKey) {
if (!this.plugin.settings.builtinCategoryIcons)
this.plugin.settings.builtinCategoryIcons = {};
this.plugin.settings.builtinCategoryIcons[itemInfo.builtinKey] = pickedIcon;
} else {
const idx = customList.findIndex((c) => c.id === itemId);
if (idx >= 0) {
this.plugin.settings.customCategories[idx].icon = pickedIcon;
if (itemInfo.data)
itemInfo.data.icon = pickedIcon;
}
}
await this.plugin.saveSettings();
updateIconBtn();
})();
},
itemInfo.isCustom ? () => {
void (async () => {
const idx = customList.findIndex((c) => c.id === itemId);
if (idx >= 0) {
this.plugin.settings.customCategories[idx].icon = void 0;
if (itemInfo.data)
itemInfo.data.icon = void 0;
}
await this.plugin.saveSettings();
updateIconBtn();
})();
} : void 0
);
modal.open();
});
}
const textColorPicker = row.createEl("input", { cls: "sc-color-picker-btn" });
textColorPicker.type = "color";
textColorPicker.value = ((_c = (_b = this.plugin.settings.filterColors) == null ? void 0 : _b[colorKey]) == null ? void 0 : _c.textColor) || "#c0c3c7";
textColorPicker.title = "Text color";
textColorPicker.addEventListener("input", (e) => {
const val = e.target.value;
void (async () => {
if (!this.plugin.settings.filterColors)
this.plugin.settings.filterColors = {};
if (!this.plugin.settings.filterColors[colorKey])
this.plugin.settings.filterColors[colorKey] = {};
this.plugin.settings.filterColors[colorKey].textColor = val;
await this.plugin.saveSettings();
refreshSidebarHeader();
applyPreviewColors();
})();
});
const bgColorPicker = row.createEl("input", { cls: "sc-color-picker-btn" });
bgColorPicker.type = "color";
bgColorPicker.value = ((_e = (_d = this.plugin.settings.filterColors) == null ? void 0 : _d[colorKey]) == null ? void 0 : _e.bgColor) || "#1a1a1a";
bgColorPicker.title = "Background color";
bgColorPicker.addEventListener("input", (e) => {
const val = e.target.value;
void (async () => {
if (!this.plugin.settings.filterColors)
this.plugin.settings.filterColors = {};
if (!this.plugin.settings.filterColors[colorKey])
this.plugin.settings.filterColors[colorKey] = {};
this.plugin.settings.filterColors[colorKey].bgColor = val;
await this.plugin.saveSettings();
refreshSidebarHeader();
applyPreviewColors();
})();
});
const previewBtn = row.createEl("button", { cls: "sc-category-preview" });
previewBtn.textContent = itemInfo.label;
const applyPreviewColors = () => {
var _a2;
const colors = (_a2 = this.plugin.settings.filterColors) == null ? void 0 : _a2[colorKey];
if (colors == null ? void 0 : colors.bgColor)
previewBtn.setCssProps({ "background-color": colors.bgColor });
else
previewBtn.style.removeProperty("background-color");
if (colors == null ? void 0 : colors.textColor)
previewBtn.setCssProps({ "color": colors.textColor });
else
previewBtn.style.removeProperty("color");
};
applyPreviewColors();
const nameInput = row.createEl("input", { cls: "sc-cat-name-input" });
nameInput.type = "text";
nameInput.value = itemInfo.label;
nameInput.placeholder = "Category name";
if (isBuiltin) {
nameInput.disabled = true;
nameInput.addClass("sc-cat-name-input--disabled");
} else {
nameInput.addEventListener("input", (e) => {
const newLabel = String(e.target.value || "").trim();
void (async () => {
const idx = customList.findIndex((c) => c.id === itemId);
if (idx >= 0) {
this.plugin.settings.customCategories[idx].label = newLabel || "New Category";
await this.plugin.saveSettings();
previewBtn.textContent = this.plugin.settings.customCategories[idx].label;
refreshSidebarHeader();
}
})();
});
}
let resetBtn;
if (import_obsidian10.Platform.isMobile) {
resetBtn = row.createEl("button", { cls: "clickable-icon" });
(0, import_obsidian10.setIcon)(resetBtn, "rotate-ccw");
resetBtn.title = "Reset colors";
} else {
resetBtn = row.createEl("button", { text: "Reset colors", cls: "sc-reset-btn-small" });
}
resetBtn.addEventListener("click", () => {
void (async () => {
var _a2;
if ((_a2 = this.plugin.settings.filterColors) == null ? void 0 : _a2[colorKey]) {
delete this.plugin.settings.filterColors[colorKey];
await this.plugin.saveSettings();
renderCategories();
refreshSidebarHeader();
}
})();
});
if (itemInfo.builtinKey) {
const resetIconBtn = row.createEl("button", { cls: "clickable-icon sc-round-btn" });
resetIconBtn.title = "Reset to default icon";
try {
(0, import_obsidian10.setIcon)(resetIconBtn, "rotate-ccw");
} catch (e) {
resetIconBtn.textContent = "\u21BA";
}
resetIconBtn.addEventListener("click", () => {
void (async () => {
if (!this.plugin.settings.builtinCategoryIcons)
this.plugin.settings.builtinCategoryIcons = {};
this.plugin.settings.builtinCategoryIcons[itemInfo.builtinKey] = itemInfo.defaultIcon;
await this.plugin.saveSettings();
const iconBtnEl = row.querySelector(".sc-cat-icon-btn");
if (iconBtnEl instanceof HTMLElement) {
iconBtnEl.empty();
try {
(0, import_obsidian10.setIcon)(iconBtnEl, itemInfo.defaultIcon);
} catch (e) {
iconBtnEl.textContent = "+";
}
}
})();
});
}
if (itemInfo.canRemove) {
const removeBtn = row.createEl("button", { cls: "clickable-icon" });
(0, import_obsidian10.setIcon)(removeBtn, "trash");
removeBtn.setAttr("title", "Delete category");
removeBtn.addEventListener("click", () => {
new ConfirmDeleteModal(this.app, `Delete category "${itemInfo.label}"?`, async () => {
const idx = customList.findIndex((c) => c.id === itemId);
if (idx >= 0) {
this.plugin.settings.customCategories.splice(idx, 1);
this.plugin.settings.allItemsOrder = (this.plugin.settings.allItemsOrder || []).filter((id) => id !== itemId);
await this.plugin.saveSettings();
renderCategories();
refreshSidebarHeader();
}
}).open();
});
} else {
row.createEl("div", { cls: "sc-spacer-32" });
}
};
orderedIds.forEach((id) => renderRow(id));
const addRow = catsContainer.createDiv({ cls: "sc-add-row" });
const addBtn = addRow.createEl("button", { text: "Add custom category", cls: "mod-cta" });
addBtn.addEventListener("click", () => {
void (async () => {
if (!Array.isArray(this.plugin.settings.customCategories))
this.plugin.settings.customCategories = [];
const id = "cat-" + Date.now();
this.plugin.settings.customCategories.push({ id, label: "New category", showInMenu: true });
if (!Array.isArray(this.plugin.settings.allItemsOrder))
this.plugin.settings.allItemsOrder = [];
this.plugin.settings.allItemsOrder.push(id);
await this.plugin.saveSettings();
renderCategories();
refreshSidebarHeader();
})();
});
this.attachSettingsTouchDrag(
catsContainer,
".setting-item[data-cat-id]",
(el) => {
var _a;
return (_a = el.dataset.catId) != null ? _a : null;
},
(fromId, toId, insertBefore) => {
const allIds = Array.from(catsContainer.querySelectorAll(".setting-item[data-cat-id]")).map((el) => el.dataset.catId);
const srcIndex = allIds.indexOf(fromId);
const destIndex = allIds.indexOf(toId);
if (srcIndex === -1 || destIndex === -1)
return;
const newOrder = allIds.filter((id) => id !== fromId);
let finalIndex = destIndex;
if (srcIndex < destIndex && !insertBefore)
finalIndex = destIndex;
else if (srcIndex > destIndex && insertBefore)
finalIndex = destIndex;
else if (srcIndex < destIndex && insertBefore)
finalIndex = destIndex - 1;
else if (srcIndex > destIndex && !insertBefore)
finalIndex = destIndex + 1;
newOrder.splice(finalIndex, 0, fromId);
this.plugin.settings.allItemsOrder = newOrder;
void this.plugin.saveSettings().then(() => {
renderCategories();
refreshSidebarHeader();
});
}
);
};
renderCategories();
new import_obsidian10.Setting(containerEl).setName("Open category on load").setDesc("Which category opens when the sidebar loads.").addDropdown((dropdown) => {
const opts = [{ value: "all", label: "All" }];
if (!this.plugin.settings.hideTodayFilter) {
opts.push({ value: "today", label: "Today" });
}
if (!this.plugin.settings.hideTomorrowFilter) {
opts.push({ value: "tomorrow", label: "Tomorrow" });
}
if (!this.plugin.settings.hideArchivedFilterButton) {
opts.push({ value: "archived", label: "Archived" });
}
(this.plugin.settings.customCategories || []).forEach((c) => opts.push({ value: String(c.id || c.label || ""), label: String(c.label || c.id || "") }));
opts.forEach((o) => {
dropdown.addOption(o.value, o.label);
});
dropdown.setValue(String(this.plugin.settings.openCategoryOnLoad || "all"));
dropdown.onChange((v) => {
void (async () => {
this.plugin.settings.openCategoryOnLoad = v;
await this.plugin.saveSettings();
})();
});
});
new import_obsidian10.Setting(containerEl).setName("Auto color").setDesc("Cards inherit a color based on text or tag rules when no color is manually set.").setHeading();
const rulesContainer = containerEl.createDiv();
const renderRules = () => {
rulesContainer.empty();
const rules = Array.isArray(this.plugin.settings.autoColorRules) ? this.plugin.settings.autoColorRules : [];
let ruleDragSrcId = null;
rules.forEach((r, idx) => {
const setting = new import_obsidian10.Setting(rulesContainer).addExtraButton((btn) => {
btn.setIcon("grip-vertical").setTooltip("Drag to reorder");
btn.extraSettingsEl.addClass("sc-drag-handle");
}).addDropdown((dropdown) => {
dropdown.addOption("text", "Text").addOption("tag", "Tag").setValue(String(r.type || "text")).onChange(async (value) => {
this.plugin.settings.autoColorRules[idx].type = value;
await this.plugin.saveSettings();
});
dropdown.selectEl.addClass("sc-dropdown-normal");
}).addText((text) => {
text.setPlaceholder("Match").setValue(r.match || "").onChange(async (value) => {
this.plugin.settings.autoColorRules[idx].match = value;
await this.plugin.saveSettings();
});
text.inputEl.addClass("sc-rule-text-input");
}).addDropdown((dropdown) => {
const colors = this.plugin.settings.colorNames || [];
for (let i = 1; i <= 10; i++) {
const colorName = colors[i - 1] || `Color ${i}`;
dropdown.addOption(String(i), colorName);
}
dropdown.setValue(String(r.colorIndex || 1));
this.applyColorToDropdown(dropdown.selectEl, String(r.colorIndex || 1));
dropdown.onChange(async (value) => {
this.plugin.settings.autoColorRules[idx].colorIndex = Number(value);
await this.plugin.saveSettings();
this.applyColorToDropdown(dropdown.selectEl, value);
});
dropdown.selectEl.addClass("sc-dropdown-normal");
Array.from(dropdown.selectEl.options).forEach((opt) => opt.addClass("sc-dropdown-normal"));
}).addExtraButton((button) => {
button.setIcon("trash").setTooltip("Remove rule").onClick(async () => {
this.plugin.settings.autoColorRules.splice(idx, 1);
await this.plugin.saveSettings();
renderRules();
});
});
setting.infoEl.remove();
setting.controlEl.addClass("sc-rule-row-controls");
setting.settingEl.setAttr("data-rule-idx", String(idx));
setting.settingEl.setAttr("draggable", "true");
setting.settingEl.addEventListener("dragstart", (e) => {
var _a;
ruleDragSrcId = `rule-${idx}`;
(_a = e.dataTransfer) == null ? void 0 : _a.setData("text/plain", ruleDragSrcId);
if (e.dataTransfer)
e.dataTransfer.effectAllowed = "move";
window.setTimeout(() => setting.settingEl.addClass("sc-dragging"), 0);
});
setting.settingEl.addEventListener("dragend", () => {
rulesContainer.querySelectorAll(".sc-dragging").forEach((el) => el.removeClass("sc-dragging"));
ruleDragSrcId = null;
});
setting.settingEl.addEventListener("dragover", (e) => {
e.preventDefault();
if (ruleDragSrcId && ruleDragSrcId.startsWith("rule-")) {
const rect = setting.settingEl.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
setting.settingEl.removeClass("sc-drag-over-top", "sc-drag-over-bottom");
setting.settingEl.addClass(e.clientY < midY ? "sc-drag-over-top" : "sc-drag-over-bottom");
}
});
setting.settingEl.addEventListener("dragleave", () => {
setting.settingEl.removeClass("sc-drag-over-top", "sc-drag-over-bottom");
});
setting.settingEl.addEventListener("drop", (e) => {
void (async () => {
e.preventDefault();
if (!ruleDragSrcId || !ruleDragSrcId.startsWith("rule-"))
return;
const srcIdx = parseInt(ruleDragSrcId.replace("rule-", ""));
if (srcIdx === idx)
return;
const rules2 = this.plugin.settings.autoColorRules || [];
const [moved] = rules2.splice(srcIdx, 1);
rules2.splice(idx, 0, moved);
rulesContainer.querySelectorAll(".sc-dragging").forEach((el) => el.removeClass("sc-dragging"));
await this.plugin.saveSettings();
renderRules();
})();
});
});
const addBtnContainer = rulesContainer.createDiv({ cls: "sc-add-row" });
const addBtn = addBtnContainer.createEl("button", { text: "Add auto color rule", cls: "mod-cta" });
addBtn.addEventListener("click", () => {
void (async () => {
if (!Array.isArray(this.plugin.settings.autoColorRules))
this.plugin.settings.autoColorRules = [];
this.plugin.settings.autoColorRules.push({ type: "text", match: "", colorIndex: 1 });
await this.plugin.saveSettings();
renderRules();
})();
});
this.attachSettingsTouchDrag(
rulesContainer,
".setting-item[data-rule-idx]",
(el) => {
var _a;
return (_a = el.dataset.ruleIdx) != null ? _a : null;
},
(fromId, toId, insertBefore) => {
const srcIdx = parseInt(fromId);
const destIdx = parseInt(toId);
if (isNaN(srcIdx) || isNaN(destIdx) || srcIdx === destIdx)
return;
const rulesList = this.plugin.settings.autoColorRules || [];
const [moved] = rulesList.splice(srcIdx, 1);
const insertAt = insertBefore ? srcIdx < destIdx ? destIdx - 1 : destIdx : srcIdx < destIdx ? destIdx : destIdx + 1;
rulesList.splice(insertAt, 0, moved);
void this.plugin.saveSettings().then(() => renderRules());
}
);
};
renderRules();
new import_obsidian10.Setting(containerEl).setName("Status").setDesc("Dropdown colors take precedence over custom unless the dropdown is set to custom.").setHeading();
const statusSection = containerEl.createDiv();
new import_obsidian10.Setting(statusSection).setName("Enable card status").setDesc("Drag to reorder status pills and set their sorting priority.").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.enableCardStatus).onChange(async (value) => {
this.plugin.settings.enableCardStatus = value;
await this.plugin.saveSettings();
renderStatusConfig();
}));
new import_obsidian10.Setting(statusSection).setName("Inherit status color").setDesc("When enabled, card color uses the status color").addToggle((toggle) => toggle.setValue(!!this.plugin.settings.inheritStatusColor).onChange(async (value) => {
this.plugin.settings.inheritStatusColor = value;
await this.plugin.saveSettings();
}));
const statusConfigContainer = statusSection.createDiv();
const renderStatusConfig = () => {
statusConfigContainer.empty();
if (!this.plugin.settings.enableCardStatus)
return;
const list = Array.isArray(this.plugin.settings.cardStatuses) ? this.plugin.settings.cardStatuses : [];
let statusDragSrcId = null;
list.forEach((s, idx) => {
const setting = new import_obsidian10.Setting(statusConfigContainer).addExtraButton((btn) => {
btn.setIcon("grip-vertical").setTooltip("Drag to reorder");
btn.extraSettingsEl.addClass("sc-drag-handle");
}).addText((text) => {
text.setValue(s.name || "").onChange(async (value) => {
this.plugin.settings.cardStatuses[idx].name = value;
await this.plugin.saveSettings();
});
text.inputEl.addClass("sc-status-text-input");
});
setting.infoEl.remove();
const colorPickerEl = setting.controlEl.createEl("input", { cls: "sc-color-picker-inline sc-hidden" });
colorPickerEl.type = "color";
colorPickerEl.value = s.color || "#20bf6b";
colorPickerEl.addEventListener("input", (e) => {
void (async () => {
this.plugin.settings.cardStatuses[idx].color = e.target.value;
await this.plugin.saveSettings();
this.plugin.injectStatusColors();
})();
});
const textColorPickerEl = setting.controlEl.createEl("input", { cls: "sc-color-picker-inline sc-hidden" });
textColorPickerEl.type = "color";
textColorPickerEl.value = s.textColor || "#000000";
textColorPickerEl.addEventListener("input", (e) => {
void (async () => {
this.plugin.settings.cardStatuses[idx].textColor = e.target.value;
await this.plugin.saveSettings();
})();
});
const updatePickersVisibility = (val) => {
if (val === "custom") {
colorPickerEl.removeClass("sc-hidden");
textColorPickerEl.removeClass("sc-hidden");
} else {
colorPickerEl.addClass("sc-hidden");
textColorPickerEl.addClass("sc-hidden");
}
};
setting.addDropdown((dropdown) => {
dropdown.addOption("custom", "Custom");
for (let i = 1; i <= 10; i++) {
const colorName = this.plugin.settings.colorNames && this.plugin.settings.colorNames[i - 1] || `Color ${i}`;
dropdown.addOption(String(i), colorName);
}
let currentVal = "custom";
if (s.colorIndex) {
currentVal = String(s.colorIndex);
} else {
for (let i = 1; i <= 10; i++) {
const colorKey = `color${i}`;
if (this.plugin.settings[colorKey] === s.color) {
currentVal = String(i);
break;
}
}
}
dropdown.setValue(currentVal);
const applyDropdownColors = (val) => {
if (val === "custom") {
dropdown.selectEl.setCssProps({
"background-color": "var(--background-modifier-form-field)",
"color": "var(--text-normal)"
});
} else {
const colorKey = `color${val}`;
const color = this.plugin.settings[colorKey];
dropdown.selectEl.setCssProps({
"background-color": color,
"color": this.getContrastColor(color)
});
}
Array.from(dropdown.selectEl.options).forEach((opt) => {
if (opt.value === "custom") {
opt.setCssProps({
"background-color": "var(--background-modifier-form-field)",
"color": "var(--text-normal)"
});
} else {
const cKey = `color${opt.value}`;
const c = this.plugin.settings[cKey];
opt.setCssProps({
"background-color": c,
"color": this.getContrastColor(c)
});
}
});
};
applyDropdownColors(currentVal);
updatePickersVisibility(currentVal);
dropdown.onChange(async (value) => {
if (value !== "custom") {
const colorKey = `color${value}`;
const color = this.plugin.settings[colorKey];
this.plugin.settings.cardStatuses[idx].color = color;
this.plugin.settings.cardStatuses[idx].colorIndex = Number(value);
} else {
this.plugin.settings.cardStatuses[idx].colorIndex = void 0;
}
await this.plugin.saveSettings();
this.plugin.injectStatusColors();
applyDropdownColors(value);
updatePickersVisibility(value);
});
});
setting.addExtraButton((button) => {
button.setIcon("trash").setTooltip("Remove status").onClick(async () => {
this.plugin.settings.cardStatuses.splice(idx, 1);
await this.plugin.saveSettings();
renderStatusConfig();
});
});
setting.controlEl.addClass("sc-status-row-controls");
setting.settingEl.setAttr("data-status-idx", String(idx));
setting.settingEl.setAttr("draggable", "true");
setting.settingEl.addEventListener("dragstart", (e) => {
var _a;
statusDragSrcId = `status-${idx}`;
(_a = e.dataTransfer) == null ? void 0 : _a.setData("text/plain", statusDragSrcId);
if (e.dataTransfer)
e.dataTransfer.effectAllowed = "move";
window.setTimeout(() => setting.settingEl.addClass("sc-dragging"), 0);
});
setting.settingEl.addEventListener("dragend", () => {
statusConfigContainer.querySelectorAll(".sc-dragging").forEach((el) => el.removeClass("sc-dragging"));
statusDragSrcId = null;
});
setting.settingEl.addEventListener("dragover", (e) => {
e.preventDefault();
if (statusDragSrcId && statusDragSrcId.startsWith("status-")) {
const rect = setting.settingEl.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
setting.settingEl.removeClass("sc-drag-over-top", "sc-drag-over-bottom");
setting.settingEl.addClass(e.clientY < midY ? "sc-drag-over-top" : "sc-drag-over-bottom");
}
});
setting.settingEl.addEventListener("dragleave", () => {
setting.settingEl.removeClass("sc-drag-over-top", "sc-drag-over-bottom");
});
setting.settingEl.addEventListener("drop", (e) => {
void (async () => {
e.preventDefault();
if (!statusDragSrcId || !statusDragSrcId.startsWith("status-"))
return;
const srcIdx = parseInt(statusDragSrcId.replace("status-", ""));
if (srcIdx === idx)
return;
const statuses = this.plugin.settings.cardStatuses || [];
const [moved] = statuses.splice(srcIdx, 1);
statuses.splice(idx, 0, moved);
statusConfigContainer.querySelectorAll(".sc-dragging").forEach((el) => el.removeClass("sc-dragging"));
await this.plugin.saveSettings();
renderStatusConfig();
})();
});
});
const addBtnContainer = statusConfigContainer.createDiv({ cls: "sc-add-row" });
const addBtn = addBtnContainer.createEl("button", { text: "Add status", cls: "mod-cta" });
addBtn.addEventListener("click", () => {
void (async () => {
if (!Array.isArray(this.plugin.settings.cardStatuses))
this.plugin.settings.cardStatuses = [];
this.plugin.settings.cardStatuses.push({ name: "focus", color: "#20bf6b", textColor: "#000000" });
await this.plugin.saveSettings();
renderStatusConfig();
})();
});
this.attachSettingsTouchDrag(
statusConfigContainer,
".setting-item[data-status-idx]",
(el) => {
var _a;
return (_a = el.dataset.statusIdx) != null ? _a : null;
},
(fromId, toId, insertBefore) => {
const srcIdx = parseInt(fromId);
const destIdx = parseInt(toId);
if (isNaN(srcIdx) || isNaN(destIdx) || srcIdx === destIdx)
return;
const statuses = this.plugin.settings.cardStatuses || [];
const [moved] = statuses.splice(srcIdx, 1);
const insertAt = insertBefore ? srcIdx < destIdx ? destIdx - 1 : destIdx : srcIdx < destIdx ? destIdx : destIdx + 1;
statuses.splice(insertAt, 0, moved);
void this.plugin.saveSettings().then(() => renderStatusConfig());
}
);
};
renderStatusConfig();
new import_obsidian10.Setting(containerEl).setName("Data Management").setHeading();
new import_obsidian10.Setting(containerEl).setName("Export Data").setDesc("Download all cards as a JSON file.").addButton((btn) => btn.setButtonText("Export Data").onClick(() => {
const cards = this.plugin.store.getAll().map((c) => c.toJSON());
const payload = JSON.stringify({ version: 1, exportedAt: new Date().toISOString(), cards }, null, 2);
const blob = new Blob([payload], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = containerEl.createEl("a");
a.href = url;
a.download = `sidecards-export-${new Date().toISOString().slice(0, 10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
new import_obsidian10.Notice("Cards exported.");
}));
new import_obsidian10.Setting(containerEl).setName("Import Data").setDesc("Import cards from a previously exported JSON file.").addButton((btn) => btn.setButtonText("Import Data").onClick(() => {
const input = containerEl.createEl("input");
input.type = "file";
input.accept = ".json,application/json";
input.addEventListener("change", () => {
var _a;
const file = (_a = input.files) == null ? void 0 : _a[0];
if (!file)
return;
const reader = new FileReader();
reader.onload = () => {
void (async () => {
try {
const parsed = JSON.parse(reader.result);
const incoming = Array.isArray(parsed) ? parsed : Array.isArray(parsed == null ? void 0 : parsed.cards) ? parsed.cards : [];
if (incoming.length === 0) {
new import_obsidian10.Notice("No cards found in file.");
return;
}
const existing = new Set(this.plugin.store.getAll().map((c) => c.id));
let added = 0;
for (const raw of incoming) {
if (!(raw == null ? void 0 : raw.id) || existing.has(raw.id))
continue;
const { Card: Card2 } = await Promise.resolve().then(() => (init_Card(), Card_exports));
await this.plugin.store.add(new Card2(raw));
added++;
}
new import_obsidian10.Notice(`Imported ${added} card${added !== 1 ? "s" : ""}.`);
} catch (e) {
new import_obsidian10.Notice("Import failed: invalid JSON file.");
console.error("Sidecards import error:", e);
}
})();
};
reader.readAsText(file);
});
input.click();
}));
}
};
var ConfirmDeleteModal = class extends import_obsidian10.Modal {
constructor(app, message, onConfirm) {
super(app);
this.message = message;
this.onConfirm = onConfirm;
}
onOpen() {
const { contentEl } = this;
contentEl.createEl("p", { text: this.message });
const btnRow = contentEl.createDiv({ cls: "modal-button-container" });
btnRow.createEl("button", { text: "Cancel" }).addEventListener("click", () => this.close());
const confirmBtn = btnRow.createEl("button", { text: "Delete", cls: "mod-warning" });
confirmBtn.addEventListener("click", () => {
this.close();
void this.onConfirm();
});
}
onClose() {
this.contentEl.empty();
}
};
var ChangelogModal = class extends import_obsidian10.Modal {
constructor(app, plugin) {
super(app);
this._mdComp = null;
this.plugin = plugin;
}
async onOpen() {
const { contentEl } = this;
contentEl.empty();
try {
this.modalEl.setCssStyles({
maxWidth: "900px",
width: "900px",
padding: "25px"
});
} catch (e) {
}
const header = contentEl.createEl("div");
header.setCssStyles({
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginBottom: "0px",
paddingBottom: "16px",
borderBottom: "1px solid var(--divider-color)"
});
const title = header.createEl("h2", { text: "Sidecards" });
title.setCssStyles({ margin: "0", fontSize: "1.5em", fontWeight: "600" });
const link = header.createEl("a", { text: "View on GitHub" });
link.href = "https://github.com/Kazi-Aidah/sidecards/releases";
link.target = "_blank";
link.setCssStyles({ fontSize: "0.9em", opacity: "0.8", transition: "opacity 0.2s" });
link.addEventListener("mouseenter", () => link.setCssStyles({ opacity: "1" }));
link.addEventListener("mouseleave", () => link.setCssStyles({ opacity: "0.8" }));
const body = contentEl.createDiv();
body.setCssStyles({ maxHeight: "70vh", overflow: "auto" });
const loading = body.createEl("div", { text: "Loading releases..." });
loading.setCssStyles({ opacity: "0.7", fontSize: "0.95em", marginTop: "12px" });
try {
const rels = await this.plugin.fetchAllReleases();
body.empty();
if (!Array.isArray(rels) || rels.length === 0) {
body.createEl("div", { text: "No release information available." }).setCssStyles({ marginTop: "12px" });
return;
}
rels.forEach((rel) => {
void (async () => {
const meta = body.createEl("div");
meta.setCssStyles({ marginBottom: "6px", borderBottom: "1px solid var(--divider-color)" });
const releaseName = meta.createEl("div", { text: rel.name || rel.tag_name || "Release" });
releaseName.setCssStyles({
fontSize: "2em",
fontWeight: "900",
marginTop: "12px",
marginBottom: "12px",
color: "var(--text-normal)"
});
try {
const dateRaw = rel.published_at || rel.created_at || null;
if (dateRaw) {
const dt = new Date(dateRaw);
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const formatted = `${dt.getFullYear()} ${monthNames[dt.getMonth()]} ${String(dt.getDate()).padStart(2, "0")}`;
const dateEl = meta.createEl("div", { text: formatted });
dateEl.setCssStyles({ display: "block", opacity: "0.8", fontSize: "0.9em", marginTop: "-4px", marginBottom: "16px" });
}
} catch (e) {
}
const notes = body.createEl("div");
notes.setCssStyles({ marginTop: "0px", lineHeight: "1.6", fontSize: "0.95em" });
notes.addClass("markdown-preview-view");
try {
notes.setCssStyles({ padding: "0 var(--file-margin)" });
} catch (e) {
}
const md = rel.body || "No notes";
try {
if (!this._mdComp)
this._mdComp = new import_obsidian10.Component();
await import_obsidian10.MarkdownRenderer.render(this.plugin.app, md, notes, "", this._mdComp);
} catch (e) {
const preEl = notes.createEl("pre");
preEl.setCssStyles({
whiteSpace: "pre-wrap",
overflowWrap: "break-word",
backgroundColor: "var(--background-secondary)",
padding: "12px",
borderRadius: "4px",
fontSize: "0.9em",
lineHeight: "1.5"
});
preEl.textContent = md;
}
})();
});
} catch (e) {
body.empty();
body.createEl("div", { text: "Failed to load release notes." }).setCssStyles({ marginTop: "12px" });
}
}
onClose() {
try {
if (this._mdComp)
this._mdComp.unload();
} catch (e) {
}
this._mdComp = null;
this.contentEl.empty();
}
};
var FolderSuggestModal = class extends import_obsidian10.FuzzySuggestModal {
constructor(app, folders, onChoose) {
super(app);
this.folders = folders;
this.onChoose = onChoose;
}
getItems() {
return this.folders;
}
getItemText(item) {
return item;
}
onChooseItem(item, evt) {
void Promise.resolve(this.onChoose(item));
}
};
var SidecardsIconPickerModal = class extends import_obsidian10.Modal {
constructor(app, onPick, onRemove) {
super(app);
this.allIcons = [];
this.onPick = onPick;
this.onRemove = onRemove;
}
onOpen() {
const c = this.contentEl;
c.empty();
c.addClass("sc-icon-picker-content");
const searchInput = c.createEl("input", { type: "text", attr: { placeholder: "Search icons..." }, cls: "sc-icon-picker-search" });
const list = c.createDiv({ cls: "sc-icon-picker-list" });
const footer = c.createDiv({ cls: "sc-icon-picker-footer" });
const removeBtn = footer.createEl("button", { text: "Remove icon" });
removeBtn.addEventListener("click", () => {
if (this.onRemove)
this.onRemove();
this.close();
});
if (!this.allIcons.length) {
try {
const ids = (0, import_obsidian10.getIconIds)();
this.allIcons = ids && ids.length > 0 ? ids : [];
} catch (e) {
this.allIcons = [];
}
}
const renderList = (icons, limit = 98) => {
list.empty();
const toShow = limit > 0 ? icons.slice(0, limit) : icons;
toShow.forEach((id) => {
const btn = list.createEl("button", { cls: "sc-icon-picker-btn" });
btn.title = id;
try {
(0, import_obsidian10.setIcon)(btn, id);
} catch (e) {
btn.textContent = id.slice(0, 2);
}
btn.addEventListener("click", () => {
this.onPick(id);
this.close();
});
});
};
const applyFilter = () => {
const q = (searchInput.value || "").toLowerCase();
if (!q)
renderList(this.allIcons, 98);
else
renderList(this.allIcons.filter((id) => id.toLowerCase().includes(q)), 500);
};
searchInput.addEventListener("input", applyFilter);
renderList(this.allIcons, 98);
}
onClose() {
this.contentEl.empty();
}
};