-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFoldableLink.ts
121 lines (98 loc) · 3.41 KB
/
FoldableLink.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import {App, setIcon, TFile} from "obsidian";
import CollapsibleList from "./CollapsibleList";
import LinkInfo from "./LinkInfo";
import {uniqBy} from "lodash";
export class FoldableLink {
private app: App;
private currentFile: TFile | null;
private wrapper: HTMLElement;
private collapseIcon: HTMLElement;
private subLinks: LinkInfo[];
private list: CollapsibleList | null = null;
public get element() {
return this.wrapper
}
public static async createFoldableLink(app: App, linkInfo: LinkInfo, file: TFile | null, parent: HTMLElement, path: string[]) {
const subLinks = await FoldableLink.searchLinks(app, file);
const parentLinksFiltered = subLinks.filter(link => !path.includes(link.path));
return new FoldableLink(app, linkInfo, file, parent, parentLinksFiltered, path);
}
private constructor(app: App, linkInfo: LinkInfo, file: TFile | null, parent: HTMLElement, subLinks: LinkInfo[], path: string[]) {
this.app = app;
this.currentFile = file;
this.subLinks = subLinks.filter(link => !path.includes(link.path));
this.collapseIcon = this.createCollapseIconElement();
this.wrapper = parent;
FoldableLink.styleWrapper(this.wrapper);
this.addOnClickListener(path);
const linkElement = this.createLinkElement(linkInfo);
parent.prepend(this.collapseIcon, linkElement);
}
private hasSublinks() {
return this.subLinks.length > 0;
}
private static async searchLinks(app: App, currentFile: TFile | null): Promise<LinkInfo[]> {
if (!currentFile)
return [];
const content = await app.vault.cachedRead(currentFile);
const matches = Array.from(content.matchAll(/\[\[(.+?)]]/g));
const links = matches.map(match => LinkInfo.fromRawText(match[1]));
return uniqBy(links, link => link.name)
}
private static styleWrapper(wrapper: HTMLElement) {
wrapper.addClass("link-wrapper", "is-collapsed");
}
private createCollapseIconElement() {
const collapseElement = createDiv({
cls: `collapse-icon ${!this.hasSublinks() ? "no-links" : ""}`,
});
const iconId = this.hasSublinks() ? "right-triangle" : "circle";
setIcon(collapseElement, iconId, 8)
return collapseElement;
}
private addOnClickListener(path: string[]) {
if (!this.hasSublinks() || !this.currentFile)
return;
this.collapseIcon.onClickEvent(async () => {
if (!this.list) {
this.list = await this.createList(path);
this.list.element.insertAfter(this.wrapper)
}
this.toggleList(this.list);
})
}
private createLinkElement(link: LinkInfo) {
return createEl("a", {
text: link.name,
cls: `internal-link ${!this.currentFile ? "is-unresolved" : ""}`,
href: link.path,
attr: {
"data-href": link.path,
target: "_blank",
rel: "noopener",
}
});
}
async createList(path: string[]) {
const subList = new CollapsibleList({
cls: "link-list"
});
const lis = await Promise.all(this.subLinks.map(async linkInfo => {
const li = createEl("li");
const subFile = this.currentFile ? this.app.metadataCache.getFirstLinkpathDest(linkInfo.path, this.currentFile.path) : null;
const foldableLink = await FoldableLink.createFoldableLink(this.app, linkInfo, subFile, li, [...path, linkInfo.path]);
return foldableLink.element;
}))
subList.addItems(lis);
return subList;
}
private toggleList(list: CollapsibleList) {
if (list.isCollapsed()) {
list.unfold();
this.wrapper.removeClass("is-collapsed");
} else {
list.collapse();
this.wrapper.addClass("is-collapsed");
}
}
}