fix: address Obsidian plugin review feedback

- Use sentence case for UI text
- Fix async/await and unhandled promises
- Remove style element from export, use CSS classes
- Replace inline styles with CSS classes
- Fix any types with proper D3 generics
- Remove plugin prefix from command ID/name
- Remove detachLeavesOfType from onunload
- Use arrow functions to avoid this scoping issues

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yashmurty 2026-04-13 14:57:23 +09:00
parent 47fbafcf11
commit a065c7f7d3
5 changed files with 45 additions and 38 deletions

View file

@ -19,15 +19,15 @@ export class PeopleGraphView extends ItemView {
}
getDisplayText(): string {
return "People Graph";
return "People graph";
}
getIcon(): string {
return "users";
}
async onOpen() {
this.render();
async onOpen(): Promise<void> {
await this.render();
// Export button in view header
this.addAction("download", "Export as PNG", async () => {
@ -49,18 +49,18 @@ export class PeopleGraphView extends ItemView {
// Re-render when a note's frontmatter changes
this.registerEvent(
this.app.metadataCache.on("changed", () => {
this.render();
void this.render();
}),
);
}
async render() {
async render(): Promise<void> {
const container = this.containerEl.children[1] as HTMLElement;
const people = await indexPeople(this.app, this.plugin.settings);
renderGraph(container, people, this.plugin.settings, this.app);
}
async onClose() {
async onClose(): Promise<void> {
const container = this.containerEl.children[1] as HTMLElement;
container.empty();
}

View file

@ -13,12 +13,10 @@ export async function exportGraphAsPng(container: HTMLElement): Promise<Blob | n
clone.setAttribute("height", String(height));
clone.setAttribute("viewBox", svgEl.getAttribute("viewBox") ?? `0 0 ${bbox.width} ${bbox.height}`);
// Inline computed styles so they survive serialization
const styles = document.createElement("style");
styles.textContent = `
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
`;
clone.prepend(styles);
// Set font-family directly on text elements instead of using a style element
clone.querySelectorAll("text").forEach((textEl) => {
textEl.setAttribute("font-family", "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif");
});
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(clone);

View file

@ -1,6 +1,6 @@
import * as d3 from "d3";
import { Notice } from "obsidian";
import type { App } from "obsidian";
import type { App, TFile } from "obsidian";
import type { PersonNode, PeopleGraphSettings } from "../types";
interface SimNode extends d3.SimulationNodeDatum {
@ -15,7 +15,8 @@ interface SimLink extends d3.SimulationLinkDatum<SimNode> {
const NODE_RADIUS = 24;
function appendAvatarSilhouette(el: d3.Selection<any, any, any, any>) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function appendAvatarSilhouette(el: d3.Selection<SVGGElement, any, any, any>) {
// Head
el.append("circle")
.attr("cy", -4)
@ -125,8 +126,7 @@ export function renderGraph(
}
// Create SVG — fills container and resizes with it
container.style.position = "relative";
container.style.overflow = "hidden";
container.addClass("people-graph-container");
const svg = d3
.select(container)
@ -205,18 +205,19 @@ export function renderGraph(
// Free tier: dim nodes beyond limit (skip center node)
const maxFree = settings.maxFreeNodes;
const nodeElements = nodeSelection.nodes();
let personIndex = 0;
nodeSelection.each(function (d) {
nodeSelection.each((d, i) => {
if (d.isCenter) return;
if (personIndex >= maxFree) {
d3.select(this).attr("opacity", 0.3);
d3.select(nodeElements[i]).attr("opacity", 0.3);
}
personIndex++;
});
// Render node visuals
nodeSelection.each(function (d, i) {
const el = d3.select(this);
nodeSelection.each((d, i) => {
const el = d3.select(nodeElements[i]);
if (d.isCenter) {
const centerR = NODE_RADIUS + 4;
@ -331,17 +332,7 @@ export function renderGraph(
const tooltip = d3
.select(container)
.append("div")
.attr("class", "people-graph-tooltip")
.style("position", "absolute")
.style("display", "none")
.style("background", "#ffffff")
.style("border", "1px solid #e0e0e0")
.style("border-radius", "6px")
.style("padding", "8px 12px")
.style("font-size", "12px")
.style("pointer-events", "none")
.style("z-index", "100")
.style("box-shadow", "0 2px 8px rgba(0,0,0,0.15)");
.attr("class", "people-graph-tooltip");
nodeSelection
.on("mouseenter", (event, d) => {
@ -381,7 +372,7 @@ export function renderGraph(
}
const file = app.vault.getAbstractFileByPath(d.person.id);
if (file) {
app.workspace.getLeaf("tab").openFile(file as any);
void app.workspace.getLeaf("tab").openFile(file as TFile);
}
});

View file

@ -14,23 +14,23 @@ export default class PeopleGraphPlugin extends Plugin {
(leaf) => new PeopleGraphView(leaf, this)
);
this.addRibbonIcon("users", "Open People Graph", () => {
this.activateView();
this.addRibbonIcon("users", "Open people graph", () => {
void this.activateView();
});
this.addSettingTab(new PeopleGraphSettingTab(this.app, this));
this.addCommand({
id: "open-people-graph",
name: "Open People Graph",
id: "open",
name: "Open graph",
callback: () => {
this.activateView();
void this.activateView();
},
});
}
onunload() {
this.app.workspace.detachLeavesOfType(VIEW_TYPE_PEOPLE_GRAPH);
// no-op: don't detach leaves so user layout is preserved
}
async loadSettings() {

View file

@ -1 +1,19 @@
/* People Graph plugin styles */
.people-graph-container {
position: relative;
overflow: hidden;
}
.people-graph-tooltip {
position: absolute;
display: none;
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 8px 12px;
font-size: 12px;
pointer-events: none;
z-index: 100;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}