vrtmrz_obsidian-tagfolder/v2codebehind.ts
2026-06-30 10:37:36 +00:00

195 lines
7.6 KiB
TypeScript

import type { TREE_TYPE, TagFolderSettings, TagInfoDict, ViewItem } from "./types";
import { V2FI_IDX_CHILDREN, type V2FolderItem, trimTrailingSlash, parseTagName, pathMatch, getExtraTags, getViewItemFromPath, V2FI_IDX_TAG, V2FI_IDX_TAGNAME, V2FI_IDX_TAGDISP, waitForRequestAnimationFrame } from "./util";
export function performSortExactFirst(_items: ViewItem[], children: V2FolderItem[], leftOverItems: ViewItem[]) {
// const m2 = measure("new");
const childrenPathsArr = children.map((e) =>
(e[V2FI_IDX_CHILDREN]).map((ee) => ee.path)).flat()
const childrenPaths = new Set(
childrenPathsArr
);
const exactHerePaths = new Set(_items.map((e) => e.path));
childrenPaths.forEach((path) => exactHerePaths.delete(path));
//const isHerePaths =
const wk2 = [...leftOverItems].sort((a, b) => {
const aIsInChildren = exactHerePaths.has(a.path);
const bIsInChildren = exactHerePaths.has(b.path);
return (aIsInChildren ? -1 : 0) + (bIsInChildren ? 1 : 0);
});
// m2();
return [...wk2];
}
function delay() {
return new Promise<void>(res => window.setTimeout(() => res(), 5));
}
function nextTick() {
return new Promise<void>(res => window.setTimeout(() => res(), 0));
}
const delays = [nextTick, delay, nextTick, waitForRequestAnimationFrame];
let delayIdx = 0;
export async function collectChildren(previousTrail: string, tags: string[], _tagInfo: TagInfoDict, _items: ViewItem[]) {
const previousTrailLC = previousTrail.toLowerCase();
const children: V2FolderItem[] = [];
const tagPerItem = new Map<string, ViewItem[]>();
const lowercaseMap = new Map<string, string>();
for (const item of _items) {
const itemTags = item.tags;
itemTags.forEach(itemTag => {
const tagLc = lowercaseMap.get(itemTag) ?? lowercaseMap.set(itemTag, itemTag.toLowerCase()).get(itemTag)!;
if (!tagPerItem.has(tagLc)) tagPerItem.set(tagLc, []);
tagPerItem.get(tagLc)!.push(item);
})
}
for (const tag of tags) {
const tagLC = tag.toLowerCase();
const tagNestedLC = previousTrailLC && !tagLC.startsWith(previousTrailLC)
? previousTrailLC + tagLC
: "";
const items: ViewItem[] = [];
for (const [itemTag, tempItems] of tagPerItem) {
if (pathMatch(itemTag, tagLC)) {
items.push(...tempItems);
} else if (tagNestedLC && pathMatch(itemTag, tagNestedLC)) {
items.push(...tempItems);
}
}
children.push(
[
tag,
...parseTagName(tag, _tagInfo),
[...new Set(items)]
]
)
// Prevent UI freezing.
delayIdx++; delayIdx %= 4;
await (delays[delayIdx])();
}
return children;
}
export async function collectTreeChildren(
{ key,
expandLimit, depth, tags, trailLower, _setting, isMainTree,
isSuppressibleLevel, viewType, previousTrail, _tagInfo, _items, linkedItems, isRoot,
sortFunc }
:
{
key: string,
expandLimit: number, depth: number, tags: string[], trailLower: string[], _setting: TagFolderSettings,
isMainTree: boolean, isSuppressibleLevel: boolean, viewType: TREE_TYPE, previousTrail: string,
_tagInfo: TagInfoDict, _items: ViewItem[], linkedItems: Map<string, ViewItem[]>, isRoot: boolean,
sortFunc: (a: V2FolderItem, b: V2FolderItem) => number
}
): Promise<{ suppressLevels: string[], children: V2FolderItem[] }> {
let suppressLevels: string[] = []; // This will be shown as chip.
let children: V2FolderItem[] = [];
if (expandLimit && depth >= expandLimit) {
// If expand limit had been configured and we have reached it,
// suppress sub-folders and show that information as extraTags.
children = [];
suppressLevels = getExtraTags(
tags,
trailLower,
_setting.reduceNestedParent
);
} else if (!isMainTree) {
// If not in main tree, suppress sub-folders.
children = [];
} else if (isSuppressibleLevel) {
// If we determined it was a suppressible,
// suppress sub-folders and show that information as extraTags.
children = [];
suppressLevels = getExtraTags(
tags,
trailLower,
_setting.reduceNestedParent
);
} else {
let wChildren = [] as V2FolderItem[];
if (viewType == "tags") {
wChildren = await collectChildren(
previousTrail,
tags,
_tagInfo,
_items
);
} else if (viewType == "links") {
// We made the list in the previous step.
wChildren = tags.map((tag) => {
const selfInfo = getViewItemFromPath(tag);
const dispName = !selfInfo ? tag : selfInfo.displayName;
const children = linkedItems.get(tag) ?? [];
return [
tag,
dispName,
[dispName],
children,
] as V2FolderItem;
});
}
if (viewType == "tags") {
// -- Check redundant combination if configured.
if (_setting.mergeRedundantCombination) {
const out = [] as typeof wChildren;
const isShown = new Set<string>();
for (const [tag, tagName, tagsDisp, items] of wChildren) {
const list = [] as ViewItem[];
for (const v of items) {
if (!isShown.has(v.path)) {
list.push(v);
isShown.add(v.path);
}
}
if (list.length != 0)
out.push([tag, tagName, tagsDisp, list]);
}
wChildren = out;
}
// -- MainTree and Root specific structure modification.
if (isMainTree && isRoot) {
// Remove all items which have been already archived except is on the root.
const archiveTags = _setting.archiveTags
.toLowerCase()
.replace(/[\n ]/g, "")
.split(",");
wChildren = wChildren
.map((e) => {
const childTagPath = `${trimTrailingSlash(e[V2FI_IDX_TAG].toLowerCase())}/`;
return archiveTags.some((aTag) =>
`${trimTrailingSlash(aTag)}/`.startsWith(childTagPath)
)
? e
: ([
e[V2FI_IDX_TAG],
e[V2FI_IDX_TAGNAME],
e[V2FI_IDX_TAGDISP],
e[V2FI_IDX_CHILDREN].filter(
(items) =>
!items.tags.some((e) =>
archiveTags.contains(
e.toLowerCase()
)
)
),
] as V2FolderItem)
})
.filter(
(child) => child[V2FI_IDX_CHILDREN].length != 0
);
}
}
wChildren = wChildren.sort(sortFunc);
children = wChildren;
}
return { suppressLevels, children }
}