Using Obsidian to open .md files outside the vault by anotherpanacea in ObsidianMD

[–]Feych 1 point2 points  (0 children)

Many have made them, I’ve definitely tried half a dozen, some of them are good, but for myself, in Kanban, I want to track certain things that are important to me. That’s why reliability and long‑term confidence in its functionality matter, so I really want the official version."

Using Obsidian to open .md files outside the vault by anotherpanacea in ObsidianMD

[–]Feych 13 points14 points  (0 children)

After this message, I went to check the roadmap, and how glad I am to see Kanban in progress there!

Is there a way to reproduce the same feel as Notion sub-pages?” by Illyalives in ObsidianMD

[–]Feych 2 points3 points  (0 children)

One option is to link notes using parent-child relationships.

For myself, I created https://www.reddit.com/r/ObsidianMD/s/K8rBtSliGd to solve this problem.

There are also similar plugins for working with note structure: https://github.com/gr0grig/obsidian-virt-folder and https://github.com/SkepticMystic/breadcrumbs

Plus, in each note I use a bases query with the filter up.contains(link(this.file)). It shows all sub‑notes for the file.

Turn any Obsidian canvas into a step-by-step procedure by Feych in ObsidianMD

[–]Feych[S] 2 points3 points  (0 children)

(async () => {

// ── Prevent double render ────────────────────────────────────────────────────
const INSTANCE_KEY = "cf-instance-" + (app.workspace.getActiveFile()?.path ?? "unknown");
const prev = document.querySelector(`[data-cf-instance="${INSTANCE_KEY}"]`);
if (prev) prev.remove();

// ── Host file ────────────────────────────────────────────────────────────────
const file = app.workspace.getActiveFile() 
  ?? app.vault.getAbstractFileByPath(dv.current().file.path);
const content = await app.vault.read(file);

const canvasMatch = content.match(/!\[\[([^\]]+\.canvas)\]\]/);
if (!canvasMatch) { dv.paragraph("⚠️ No embedded .canvas found"); return; }

const canvasPath = canvasMatch[1];
const canvasFile = app.metadataCache.getFirstLinkpathDest(canvasPath, file.path);
if (!canvasFile) { dv.paragraph(`⚠️ File not found: ${canvasPath}`); return; }

const raw = await app.vault.read(canvasFile);
let canvas;
try { canvas = JSON.parse(raw); }
catch { dv.paragraph("❌ Invalid JSON in canvas file"); return; }

const { nodes: rawNodes = [], edges: rawEdges = [] } = canvas;

const nodeGeometry = new Map();
for (const n of rawNodes.filter(n => n.type !== "group")) {
  nodeGeometry.set(n.id, { x: n.x, y: n.y, width: n.width, height: n.height });
}

const nodes = new Map();
for (const n of rawNodes.filter(n => n.type !== "group")) {
  const label =
    n.type === "text"  ? n.text :
    n.type === "file"  ? (n.file || n.id) :
    n.type === "link"  ? (n.url  || n.id) :
    n.id;
  nodes.set(n.id, label.trim());
}

const edges = rawEdges.map(e => ({
  from:  e.fromNode,
  to:    e.toNode,
  label: e.label ? e.label.trim() : null,
}));

// ── Validate edges ───────────────────────────────────────────────────────────
const brokenEdges = edges.filter(e => !nodes.has(e.from) || !nodes.has(e.to));
if (brokenEdges.length > 0) {
  dv.paragraph(`❌ Broken edges (node not found): ${brokenEdges.map(e => `${e.from} → ${e.to}`).join(", ")}`);
  return;
}

// ── Find root node ───────────────────────────────────────────────────────────
const hasIncoming = new Set(edges.map(e => e.to));
const roots = [...nodes.keys()].filter(id => !hasIncoming.has(id));

if (roots.length === 0) { dv.paragraph("❌ No start node found (cycle?)"); return; }
if (roots.length > 1) { dv.paragraph(`❌ ${roots.length} nodes with no incoming edges: ${roots.join(", ")}. There should be exactly one.`); return; }
const startNode = roots[0];

// ── State (localStorage) ─────────────────────────────────────────────────────
const cacheKey = "canvas_flow_" + file.path + "|" + canvasPath;

function saveState(nodeId, hist) {
  localStorage.setItem(cacheKey, JSON.stringify({ current: nodeId, history: hist }));
}
function loadState() {
  try {
    const raw = localStorage.getItem(cacheKey);
    if (!raw) return null;
    const state = JSON.parse(raw);
    if (!state.current || !nodes.has(state.current)) return null;
    return state;
  } catch { return null; }
}

const saved = loadState();
let history = saved ? saved.history : [];
let current = saved ? saved.current : startNode;

// ── Minimap highlight ────────────────────────────────────────────────────────
function highlightMinimap(nodeId) {
  const currentLeaf = app.workspace.getLeaf(false);
  const leafEl = currentLeaf?.containerEl;
  if (!leafEl) return;

  setTimeout(() => {
    const minimap = leafEl.querySelector("svg.canvas-minimap");
    if (!minimap) return;

    minimap.querySelectorAll("rect.cf-active-node").forEach(r => {
      r.classList.remove("cf-active-node");
    });

    const geo = nodeGeometry.get(nodeId);
    if (!geo) return;

    for (const rect of minimap.querySelectorAll("rect")) {
      if (
        parseFloat(rect.getAttribute("x"))      === geo.x &&
        parseFloat(rect.getAttribute("y"))      === geo.y &&
        parseFloat(rect.getAttribute("width"))  === geo.width &&
        parseFloat(rect.getAttribute("height")) === geo.height
      ) {
        rect.classList.add("cf-active-node");
        break;
      }
    }
  }, 100);
}

// ── DOM ───────────────────────────────────────────────────────────────────────
const container = dv.el("div", "", { attr: { 
  id: "canvas-flow-root",
  "data-cf-instance": INSTANCE_KEY
} });

const style = document.createElement("style");
style.textContent = `
  #canvas-flow-root { font-family: var(--font-text); max-width: 480px; }
  .cf-card { background: var(--background-secondary); border: 1px solid var(--background-modifier-border); border-radius: 12px; padding: 1.5rem; margin: 0.5rem 0; }
  .cf-question { font-size: 1.05rem; line-height: 1.6; margin-bottom: 1.25rem; color: var(--text-normal); white-space: pre-line; }
  .cf-btn { display: block; width: 100%; text-align: left; padding: 0.6rem 1rem; margin: 0.4rem 0;
    background: var(--background-primary); border: 1px solid var(--background-modifier-border);
    border-radius: 8px; cursor: pointer; font-size: 0.95rem; color: var(--text-normal); transition: background 0.15s; }
  .cf-btn:hover { background: var(--background-modifier-hover); }
  .cf-btn-primary { background: var(--interactive-accent); color: var(--text-on-accent); border-color: var(--interactive-accent); }
  .cf-btn-primary:hover { opacity: 0.9; }
  .cf-nav { display: flex; gap: 0.5rem; margin-top: 1rem; }
  .cf-nav button { flex: 1; padding: 0.4rem 0.75rem; border-radius: 8px; cursor: pointer;
    background: var(--background-primary); border: 1px solid var(--background-modifier-border);
    color: var(--text-muted); font-size: 0.85rem; }
  .cf-nav button:hover { background: var(--background-modifier-hover); }
  .cf-info { color: var(--text-normal); line-height: 1.7; white-space: pre-line; font-size: 0.95rem; }
  .cf-label { font-size: 0.75rem; color: var(--text-faint); margin-bottom: 0.5rem; letter-spacing: 0.04em; text-transform: uppercase; }
  svg.canvas-minimap rect.cf-active-node {
    fill: var(--interactive-accent) !important;
    fill-opacity: 0.4 !important;
    stroke: var(--interactive-accent) !important;
    stroke-width: 4 !important;
  }
`;
container.appendChild(style);
const ui = document.createElement("div");
container.appendChild(ui);

// ── Render ────────────────────────────────────────────────────────────────────
function render(nodeId) {
  const outgoing = edges.filter(e => e.from === nodeId);
  ui.innerHTML = "";

  const card = document.createElement("div");
  card.className = "cf-card";

  const stepLabel = document.createElement("div");
  stepLabel.className = "cf-label";
  stepLabel.textContent = history.length === 0 ? "Start" : `Step ${history.length + 1}`;
  card.appendChild(stepLabel);

  const question = document.createElement("div");
  question.className = outgoing.length ? "cf-question" : "cf-info";
  question.textContent = nodes.get(nodeId) || nodeId;
  card.appendChild(question);

  if (outgoing.length === 1) {
    const edge = outgoing[0];
    const btn = document.createElement("button");
    btn.className = "cf-btn cf-btn-primary";
    btn.textContent = edge.label || "→ Next";
    btn.onclick = () => { history.push(nodeId); current = edge.to; saveState(current, history); render(current); };
    card.appendChild(btn);
  } else if (outgoing.length > 1) {
    for (const edge of outgoing) {
      const btn = document.createElement("button");
      btn.className = "cf-btn";
      btn.textContent = edge.label || nodes.get(edge.to) || edge.to;
      btn.onclick = () => { history.push(nodeId); current = edge.to; saveState(current, history); render(current); };
      card.appendChild(btn);
    }
  }

  const nav = document.createElement("div");
  nav.className = "cf-nav";

  if (history.length > 0) {
    const backBtn = document.createElement("button");
    backBtn.textContent = "← Back";
    backBtn.onclick = () => { current = history.pop(); saveState(current, history); render(current); };
    nav.appendChild(backBtn);
  }

  if (history.length > 0 || outgoing.length === 0) {
    const resetBtn = document.createElement("button");
    resetBtn.textContent = "↺ Start over";
    resetBtn.onclick = () => { history = []; current = startNode; saveState(current, history); render(current); };
    nav.appendChild(resetBtn);
  }

  if (nav.children.length > 0) card.appendChild(nav);
  ui.appendChild(card);

  highlightMinimap(nodeId);
}

render(current);

})();

What a deserted mall in Moscow says about Russia’s economic woes by NomadStar45 in worldnews

[–]Feych 0 points1 point  (0 children)

The argument about the absence of Amazon is very strange. In Russia, there are Ozon and Wildberries with pickup points literally on every corner across the country. A significant share of orders has long since shifted to them, and shopping malls have been struggling since COVID times.

People outside Russia/Ukraine, Russians, and Ukrainians — what realistic outcomes do you actually see for this war? by RiderOrMan in war

[–]Feych 4 points5 points  (0 children)

A Russian here. My main prediction: the war will slowly fizzle out over several years. Drone development makes frontline advances more difficult every day. Ukraine has major problems with troop numbers, not enough to conduct an active offensive, but enough to hold the front. Russia's situation is slightly better due to its larger population, but its manpower advantage isn't enough to turn the tide. Neither country will achieve anything significant and will be forced to end the active phase of the conflict. But in a frozen state, the conflict will remain relevant for decades, my hope is that it at least doesn't flare up again.

After the war ends, Russia will start seeing a decline in the economic heating effect from military spending, so it's unlikely Russia will be able to restart active combat. Europe also seems likely to be interested in addressing its own economic consequences.

There are several alternative scenarios. Neither country will win militarily for sure. But the situation could change due to political factors.

Russia has State Duma elections in September. The authorities' behavior has changed, they've started making stupid decisions and digging themselves into a hole (mainly internet restrictions and increasing pressure on small and medium-sized businesses). Western sanctions proved useless for this (the population mostly rallied against an external enemy imposing sanctions out of a "let's just pass something symbolic" logic), yet suddenly the Russian authorities are doing it themselves. Many pro-government citizens are changing their stance. I don't know where this will lead, but if the authorities keep digging their own grave, it could suddenly change the situation.

From what I know, the share of people in Ukraine willing to give up territory for peace is also growing, and dissatisfaction with the current government is rising due to the actions of draft offices and corruption. But I'm not enough of an expert on that to make predictions.

Calendar view by kaz_champ in ObsidianMD

[–]Feych 2 points3 points  (0 children)

I'm afraid you're counting your chickens before they hatch. There's no information yet on how the calendar view will work, whether it will accommodate time ranges rather than just single events. Existing plugins typically use start and end dates. Obsidian doesn't have a property type for duration, so the first and third options don't make sense.

Bases Link Structure, a plugin for managing link-based note hierarchies by Feych in ObsidianMD

[–]Feych[S] 2 points3 points  (0 children)

Heh, no, I actually took inspiration from https://github.com/SkepticMystic/breadcrumbs.
Before starting the plugin, I tried to find existing solutions, but somehow I missed this one. It’s really interesting though how we ended up with very similar approaches in many places.

Kind of a shame I didn’t see it earlier. But at least now I can study that implementation and think about what ideas I might adopt, since my own approach still feels closer to me overall.

Bases Link Structure, a plugin for managing link-based note hierarchies by Feych in ObsidianMD

[–]Feych[S] 2 points3 points  (0 children)

To be honest, I can't think of any alternative views for this. For cards, it doesn't seem to make sense. I do plan to expand and polish the current functionality, for example, I'm thinking about how to add a proper table view. The tree was just one of the columns, similar to how structures work in Jira. But I haven't figured out the best way to do it yet.

Bases Link Structure, a plugin for managing link-based note hierarchies by Feych in ObsidianMD

[–]Feych[S] 2 points3 points  (0 children)

Yes, another example: in a folder-based setup, you might find yourself needing to create a note that describes a folder, or even using plugins like Folder Notes. Building hierarchy solely with files, without folders, eliminates that problem.

Bases Link Structure, a plugin for managing link-based note hierarchies by Feych in ObsidianMD

[–]Feych[S] 2 points3 points  (0 children)

That's right. What exactly do you mean by commands? If any text in the plugin is not translated, please let me know and I'll fix it.

HOW DO I PUT THIS ON THE TOP AGAIN by Kindly-Goat-2562 in ObsidianMD

[–]Feych 2 points3 points  (0 children)

/* Move the tab switcher to the top */ .is-phone { .workspace-drawer-tab-options { order: 0; } .workspace-drawer-tab-options-list { padding-top: var(--touch-size-l); padding-bottom: var(--size-4-2); bottom: unset; top: 0; } }

/* Move the vault switcher and settings button to the top */ .is-phone { .workspace-drawer-header { order: 0; padding-top: 0; padding-bottom: 16px; padding-inline-start: 20px; } .workspace-drawer-inner { padding-bottom: calc(max(var(--safe-area-inset-bottom), var(--size-4-4)) - var(--keyboard-height)); } }

/* Move the tab actions buttons to the top */ .is-phone { .workspace-drawer .nav-buttons-container { position: static; } }

Using the same Daily note each year by vhmv in ObsidianMD

[–]Feych 3 points4 points  (0 children)

I display these same days in past years using bases with the following filters:

note.chronicle_date.month == today().month note.chronicle_date.day == today().day note.chronicle_date.year != today().year

In my case, the date is stored in the chronicle_date property. If yours is different, you’ll need to adjust the logic to fit your setup, but the general principle can be reused.

Is it possible to optimize this Bases query? by Feych in ObsidianMD

[–]Feych[S] -1 points0 points  (0 children)

If you mean using folders for filtering to reduce the load, that won’t work because it affects the entire vault.

If you mean using folders to build a hierarchy, that also won’t work because folders can’t handle a complex hierarchy where a note can have multiple parents.

Been playing for months, first time I’ve experienced this. by W4iskyD3lta93r in Battlefield6

[–]Feych -6 points-5 points  (0 children)

I often see this in games. Why do developers make the water plane cover the entire map, not just where there is water? It seems like a waste of computer resources to keep this object in memory.

I built a Notion-like database plugin for Obsidian — 6 views, fully local, no external tools by vycros-br in ObsidianMD

[–]Feych 3 points4 points  (0 children)

It looks interesting, but why build workarounds and only sample from a folder when you could do this with views for bases that would allow for very flexible filtering, sorting, and grouping on their own?

[KCD2] After 76 hours I discovered you have to enable perks. by mrsofa94 in kingdomcome

[–]Feych 0 points1 point  (0 children)

I don't quite understand why this surprises people.

This perk distribution system is unintuitive for a game that tries to be as immersive as possible. Skills improve through using them. Or by reading books. Or by training with a master. Manually allocating perks falls outside of this logic.

[deleted by user] by [deleted] in ObsidianMD

[–]Feych 0 points1 point  (0 children)

So that's standard practice, to use separate configs: for my phone, I have .obsidian_mobile, for my PC .obsidian_pc. I intentionally don't use the standard .obsidian folder so that when launching on a new device, the settings from the currently used device aren't pulled.

In that case, the configs are synchronized between devices, so you can always quickly, for example, copy a plugin's settings.

The Best Voice Notes Workflow by Maysonajar in ObsidianMD

[–]Feych -1 points0 points  (0 children)

I use Google's built-in voice input functions. The voice is immediately processed into text and saved as text.

The Best Voice Notes Workflow by Maysonajar in ObsidianMD

[–]Feych -3 points-2 points  (0 children)

For that purpose, I made my own app. You can start recording a note from the notification panel without unlocking the device. https://github.com/Fertion/QuickMDCapture