Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
terrychenzw committed May 10, 2024
1 parent 75e7b93 commit 731bd20
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 137 deletions.
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,42 @@
# [Obsidian Zettelkasten Navigation](https://github.com/terrychenzw/obsidian-zettelkasten-navigation)
This plugin provides 2 customViews for navigating a zettelkasten using Luhmann-style IDs and key word indexes.

[中文说明](https://pkmer.cn/show/20240506222202)

![Demo](https://github.com/terrychenzw/obsidian-zettelkasten-navigation/blob/35981ab539718b3248afd5d7c9e844d987dfa209/attachments/Demo.gif)

## Main functionalities
1. zk-index-graph-view
2. zk-local-graph-view

## Why I created this plugin?
> [!important]
> **What kind of graph view can be generated basing on Luhmann-style IDs and his keyword index**
>
> Many note-taking apps like Obsidian provide the graph view functionality to visualize the relation of notes. But this kind of graph view only bases on linkages/references between notes. It is hard to recognize a specific long COT(chain of thoughts)——the starting point, the path and the end. Different COTs crossing in the graph view makes it more chaotic.
>
> Luhmann's zettelkasten is a
>
> > "combination of disorder and order, of clustering and unpredictable combinations emerging from ad hoc selection."
> >
> > Johannes F.K. Schmidt, [Niklas Luhmann’s Card Index: The Fabrication of Serendipity](https://sociologica.unibo.it/article/view/8350/8270)
>
> The graph view, basing on linkages/references between notes, in some ways can represent the aspect of disorder of a zettelkasten. But what is the aspect of order?
>
> > "The absence of a fixed system of order and, in consequence, a table of contents turned the index into the key tool for using the file – how else should one be able to find certain notes again and thus gain access to the system of references? Not wanting to rely on pure chance requires being able to identify at least one point from which the respective web of references can be accessed. This is the purpose of the keyword index."
> >
> > Johannes F.K. Schmidt, [Niklas Luhmann’s Card Index: The Fabrication of Serendipity](https://sociologica.unibo.it/article/view/8350/8270)
>
> Base on my understanding, the aspect of order in Luhmann's zettelkasten is composed of his note IDs(folgezettel) and keyword index(register).
>
> As so far, I don't find any note-taking apps or plugins provide the graph view functionality basing on Luhmann-style IDs and his keyword index——And this is the reason why I created this plugin.
>
> This plugin provides a different graph view to visualize and navigate a zettelkasten with Luhmann-style IDs and his keyword index. I think this is the real Luhmann way to retrive thoughts and navigate notes in a digital zettelkasten.
## Why Mermaid?
Because Obsidian supports Mermaid.js natively.

This plugin uses [Obsidian API: loadMermaid()](https://docs.obsidian.md/Reference/TypeScript+API/loadMermaid) to generate flowcharts and uses [d3.js](https://github.com/d3/d3) for zooming mermaid flowcharts.
This plugin uses [Obsidian API: loadMermaid()](https://docs.obsidian.md/Reference/TypeScript+API/loadMermaid) to generate graphs and uses [svg-pan-zoom](https://github.com/bumbu/svg-pan-zoom) for panning and zooming mermaid graphs.

## Prerequisites
1. **Luhmann-style IDs**. below style IDs are supported by this plugin:
Expand All @@ -36,4 +62,4 @@ Below ID field options are supported by this plugin. You have to choose 1 option
4. others settings(optional)

## Installation
`Settings > Community plugins > Community Plugins > Browse` and search for `Zettelkasten Navigation`.
`Settings > Community plugins > Community Plugins > Browse` and search for `Zettelkasten Navigation`.
16 changes: 12 additions & 4 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { Plugin } from "obsidian";
import { Notice, Plugin } from "obsidian";
import { ZKNavigationSettngTab } from "src/settings/settings";
import { ZKGraphView, ZK_GRAPH_TYPE } from "src/view/graphView";
import { ZKIndexView, ZK_INDEX_TYPE, ZK_NAVIGATION } from "src/view/indexView";

export interface FoldNode{
graphID: string;
nodeIDstr: string;
position: number;
}

//settings fields
interface ZKNavigationSettings {
FolderOfMainNotes: string;
Expand All @@ -22,8 +28,9 @@ interface ZKNavigationSettings {
IndexButtonText: string;
SuggestMode: string;
FoldToggle: boolean;
FoldNodeArr: string[];
FoldNodeArr: FoldNode[];
RedDashLine: boolean;
FixedHeight:number;
}

//Default value for setting field
Expand All @@ -47,6 +54,7 @@ const DEFAULT_SETTINGS: ZKNavigationSettings = {
FoldToggle: false,
FoldNodeArr: [],
RedDashLine:false,
FixedHeight: 510,
}

export default class ZKNavigationPlugin extends Plugin {
Expand All @@ -58,7 +66,7 @@ export default class ZKNavigationPlugin extends Plugin {
{},
DEFAULT_SETTINGS,
await this.loadData()
)
)
}

async onload() {
Expand Down Expand Up @@ -88,7 +96,7 @@ export default class ZKNavigationPlugin extends Plugin {
{
defaultMod:true,
display:"ZK Navigation"
});
});

}

Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "zettelkasten-navigation",
"name": "zettelkasten navigation",
"version": "0.0.31",
"version": "0.0.32",
"minAppVersion": "1.5.7",
"description": "Visualize a Luhmann-style zettelkasten.",
"author": "terrychenzw",
Expand Down
18 changes: 16 additions & 2 deletions src/settings/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ZKNavigationPlugin from "main";
import { App, PluginSettingTab, Setting } from "obsidian";
import { App, Notice, PluginSettingTab, Setting } from "obsidian";
import { FolderSuggest } from "../suggester/FolderSuggester";
import { TagSuggest } from "src/suggester/TagSuggester";

Expand Down Expand Up @@ -83,7 +83,7 @@ export class ZKNavigationSettngTab extends PluginSettingTab {
break;
case "3":
new Setting(this.containerEl)
.setName("Specify a separator for splitting ID and title")
.setName("Specify a separator between ID and title")
.addDropdown(options => options
.addOption(" ", `" "(blank)`)
.addOption("-", `"-"(hyphen)`)
Expand Down Expand Up @@ -136,6 +136,20 @@ export class ZKNavigationSettngTab extends PluginSettingTab {
this.plugin.saveData(this.plugin.settings);
})
)

new Setting(this.containerEl)
.setName("Fixed Height")
.addText((cb) => {
cb.setValue(this.plugin.settings.FixedHeight.toString())
.setPlaceholder("510 (px)")
.onChange((value) => {

this.plugin.settings.FixedHeight = Number(value);

this.plugin.saveData(this.plugin.settings);
})
}
);

new Setting(this.containerEl)
.setName("Set red dash line for nodes with ID ends with letter")
Expand Down
95 changes: 52 additions & 43 deletions src/view/graphView.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ZKNavigationPlugin from "main";
import { ItemView, Notice, TFile, WorkspaceLeaf, debounce, loadMermaid } from "obsidian";
import { ZKNode, ZK_NAVIGATION } from "./indexView";
import * as d3 from "d3";

export const ZK_GRAPH_TYPE: string = "zk-graph-type"
export const ZK_GRAPH_VIEW: string = "zk-local-graph"
Expand Down Expand Up @@ -44,14 +43,13 @@ export class ZKGraphView extends ItemView {

if (currentFile !== null) {

let mermaid = await loadMermaid();

const mermaid = await loadMermaid();
const svgPanZoom = require("svg-pan-zoom");
if (this.plugin.settings.FamilyGraphToggle == true) {

let familyNodeArr: ZKNode[] = await this.getFamilyNodes(currentFile);
let familyMermaidStr: string = await this.genericFamilyMermaidStr(currentFile, familyNodeArr);


const familyGraphContainer = graphMermaidDiv.createDiv("zk-family-graph-container");
const familyGraphTextDiv = familyGraphContainer.createDiv("zk-graph-link");

Expand All @@ -65,18 +63,17 @@ export class ZKGraphView extends ItemView {
let { svg } = await mermaid.render(`${familyTreeDiv.id}-svg`, `${familyMermaidStr}`);
familyTreeDiv.insertAdjacentHTML('beforeend', svg);
graphMermaidDiv.appendChild(familyTreeDiv);

let svgs = d3.select("[id=zk-family-tree] svg");

svgs.each(function () {
var svg = d3.select(this);
svg.html("<g>" + svg.html() + "</g>");
var inner = svg.select("g");
var zoom = d3.zoom().on("zoom", function (event) {
inner.attr("transform", event.transform);
});
svg.call(zoom);
});

let panZoomTiger = svgPanZoom(`#${familyTreeDiv.id}-svg`, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: false,
center: true,
minZoom: 0.001,
maxZoom: 1000,
dblClickZoomEnabled: false,
zoomScaleSensitivity: 0.3,
})

let nodeGArr = familyTreeDiv.querySelectorAll("[id^='flowchart-']");
let nodeArr = familyTreeDiv.getElementsByClassName("nodeLabel");
Expand All @@ -89,8 +86,12 @@ export class ZKGraphView extends ItemView {
link.textContent = nodeArr[i].getText();
nodeArr[i].textContent = "";
nodeArr[i].appendChild(link);
nodeArr[i].addEventListener("click", () => {
this.app.workspace.openLinkText("", node.file.path, 'tab');
nodeArr[i].addEventListener("click", (event: MouseEvent) => {
if(event.ctrlKey){
this.app.workspace.openLinkText("", node.file.path, 'tab');
}else{
this.app.workspace.openLinkText("",node.file.path)
}
})

nodeArr[i].addEventListener(`mouseover`, (event: MouseEvent) => {
Expand Down Expand Up @@ -124,16 +125,16 @@ export class ZKGraphView extends ItemView {
inlinksDiv.insertAdjacentHTML('beforeend', svg);
graphMermaidDiv.appendChild(inlinksDiv);

let svgs = d3.select("[id=zk-inlinks] svg");
svgs.each(function () {
var svg = d3.select(this);
svg.html("<g>" + svg.html() + "</g>");
var inner = svg.select("g");
var zoom = d3.zoom().on("zoom", function (event) {
inner.attr("transform", event.transform);
});
svg.call(zoom);
});
let panZoomTiger = svgPanZoom(`#${inlinksDiv.id}-svg`, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: false,
center: true,
minZoom: 0.001,
maxZoom: 1000,
dblClickZoomEnabled: false,
zoomScaleSensitivity: 0.3,
})


let nodeGArr = inlinksDiv.querySelectorAll("[id^='flowchart-']");
Expand All @@ -148,8 +149,12 @@ export class ZKGraphView extends ItemView {
link.textContent = nodeArr[i].getText();
nodeArr[i].textContent = "";
nodeArr[i].appendChild(link);
nodeArr[i].addEventListener("click", () => {
this.app.workspace.openLinkText("", node.path, 'tab');
nodeArr[i].addEventListener("click", (event: MouseEvent) => {
if(event.ctrlKey){
this.app.workspace.openLinkText("", node.path, 'tab');
}else{
this.app.workspace.openLinkText("", node.path);
}
})

nodeArr[i].addEventListener(`mouseover`, (event: MouseEvent) => {
Expand Down Expand Up @@ -187,16 +192,16 @@ export class ZKGraphView extends ItemView {
outlinksDiv.insertAdjacentHTML('beforeend', svg);
graphMermaidDiv.appendChild(outlinksDiv);

let svgs = d3.select("[id=zk-outlinks] svg");
svgs.each(function () {
var svg = d3.select(this);
svg.html("<g>" + svg.html() + "</g>");
var inner = svg.select("g");
var zoom = d3.zoom().on("zoom", function (event) {
inner.attr("transform", event.transform);
});
svg.call(zoom);
});
let panZoomTiger = svgPanZoom(`#${outlinksDiv.id}-svg`, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: false,
center: true,
minZoom: 0.001,
maxZoom: 1000,
dblClickZoomEnabled: false,
zoomScaleSensitivity: 0.3,
})

let nodeGArr = outlinksDiv.querySelectorAll("[id^='flowchart-']");
let nodeArr = outlinksDiv.getElementsByClassName("nodeLabel");
Expand All @@ -209,8 +214,12 @@ export class ZKGraphView extends ItemView {
link.textContent = nodeArr[i].getText();
nodeArr[i].textContent = "";
nodeArr[i].appendChild(link);
nodeArr[i].addEventListener("click", () => {
this.app.workspace.openLinkText("", node.path, 'tab');
nodeArr[i].addEventListener("click", (event: MouseEvent) => {
if(event.ctrlKey){
this.app.workspace.openLinkText("", node.path, 'tab');
}else{
this.app.workspace.openLinkText("", node.path);
}
})

nodeArr[i].addEventListener(`mouseover`, (event: MouseEvent) => {
Expand Down Expand Up @@ -287,7 +296,7 @@ export class ZKGraphView extends ItemView {
node.IDStr = IDArr.toString();
if (nodeCache !== null) {
if (typeof nodeCache.frontmatter !== 'undefined' && this.plugin.settings.TitleField !== "") {
let title = nodeCache.frontmatter[this.plugin.settings.TitleField];
let title = nodeCache.frontmatter[this.plugin.settings.TitleField].toString();
if (typeof title == "string" && title.length > 0) {
node.title = title;
}
Expand Down
Loading

0 comments on commit 731bd20

Please sign in to comment.