mirror of
https://github.com/yashmurty/obsidian-people-graph.git
synced 2026-07-22 06:08:45 +00:00
Fix Obsidian review issues and ship fully free
- Bump minAppVersion to 1.7.2 (covers addAction/revealLeaf) + add versions.json - Obsidian API hygiene: createEl/createDiv, typed d3 callbacks, drop builtin-modules - Add release workflow with GitHub artifact attestation - Remove node-count gate and all Pro/Locked/Upgrade wording - Bump version to 0.1.2
This commit is contained in:
parent
7540b03186
commit
5c7c0bef20
13 changed files with 82 additions and 79 deletions
45
.github/workflows/release.yml
vendored
Normal file
45
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: Release Obsidian plugin
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
permissions:
|
||||
contents: write # create the GitHub release
|
||||
id-token: write # required for attestation signing
|
||||
attestations: write # write artifact attestations
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Generate artifact attestations
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: |
|
||||
main.js
|
||||
manifest.json
|
||||
styles.css
|
||||
|
||||
- name: Create release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
gh release create "$TAG" \
|
||||
--title="$TAG" \
|
||||
--generate-notes \
|
||||
main.js manifest.json styles.css
|
||||
|
|
@ -88,10 +88,11 @@ photo: attachments/me.jpg
|
|||
| Edge opacity | Opacity of relationship lines | 0.4 |
|
||||
| Show closeness ring | Color node ring by closeness score | On |
|
||||
|
||||
## Pro (Coming Soon)
|
||||
## Roadmap
|
||||
|
||||
- Unlimited people (free tier: 20 interactive nodes)
|
||||
- Export graph as PNG/SVG
|
||||
Ideas planned for future releases:
|
||||
|
||||
- Export graph as SVG (PNG export is already available from the view toolbar)
|
||||
- Filter view by tag or company
|
||||
- Color themes
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import { builtinModules } from "node:module";
|
||||
|
||||
const banner = `/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
|
|
@ -30,7 +30,8 @@ const context = await esbuild.context({
|
|||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins,
|
||||
...builtinModules,
|
||||
...builtinModules.map((m) => `node:${m}`),
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "people-graph",
|
||||
"name": "People Graph",
|
||||
"version": "0.1.1",
|
||||
"minAppVersion": "1.0.0",
|
||||
"version": "0.1.2",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "A visual force-directed graph of people and friends in your vault with photo nodes, closeness-based positioning, and company clustering.",
|
||||
"author": "yashmurty",
|
||||
"isDesktopOnly": false
|
||||
|
|
|
|||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-people-graph",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-people-graph",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d3": "^7.9.0"
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
"devDependencies": {
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/node": "^22.0.0",
|
||||
"builtin-modules": "^4.0.0",
|
||||
"esbuild": "^0.24.0",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.8.1",
|
||||
|
|
@ -807,19 +806,6 @@
|
|||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/builtin-modules": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-4.0.0.tgz",
|
||||
"integrity": "sha512-p1n8zyCkt1BVrKNFymOHjcDSAl7oq/gUvfgULv2EblgpPVQlQr9yHnWjg9IJ2MhfwPqiYqMMrr01OY7yQoK2yA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-people-graph",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"description": "A visual force-directed graph of people and friends in your vault",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -13,7 +13,6 @@
|
|||
"devDependencies": {
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/node": "^22.0.0",
|
||||
"builtin-modules": "^4.0.0",
|
||||
"esbuild": "^0.24.0",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.8.1",
|
||||
|
|
|
|||
|
|
@ -38,8 +38,7 @@ export class PeopleGraphView extends ItemView {
|
|||
return;
|
||||
}
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
const a = createEl("a", { href: url });
|
||||
a.download = "people-graph.png";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export async function exportGraphAsPng(container: HTMLElement): Promise<Blob | n
|
|||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const canvas = createEl("canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as d3 from "d3";
|
||||
import { Notice, TFile } from "obsidian";
|
||||
import { TFile } from "obsidian";
|
||||
import type { App } from "obsidian";
|
||||
import type { PersonNode, PeopleGraphSettings } from "../types";
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ export function renderGraph(
|
|||
container.empty();
|
||||
|
||||
if (people.length === 0) {
|
||||
const msg = container.createEl("div", { cls: "people-graph-empty" });
|
||||
const msg = container.createDiv({ cls: "people-graph-empty" });
|
||||
msg.createEl("h4", { text: "No people found" });
|
||||
msg.createEl("p", {
|
||||
text: `Looking for notes where frontmatter field "${settings.personField}" = "${settings.personValue}".`,
|
||||
|
|
@ -141,8 +141,8 @@ export function renderGraph(
|
|||
const zoom = d3
|
||||
.zoom<SVGSVGElement, unknown>()
|
||||
.scaleExtent([0.2, 5])
|
||||
.on("zoom", (event) => {
|
||||
g.attr("transform", event.transform);
|
||||
.on("zoom", (event: d3.D3ZoomEvent<SVGSVGElement, unknown>) => {
|
||||
g.attr("transform", event.transform.toString());
|
||||
});
|
||||
|
||||
svg.call(zoom);
|
||||
|
|
@ -184,18 +184,18 @@ export function renderGraph(
|
|||
.call(
|
||||
d3
|
||||
.drag<SVGGElement, SimNode>()
|
||||
.on("start", (event, d) => {
|
||||
.on("start", (event: d3.D3DragEvent<SVGGElement, SimNode, SimNode>, d) => {
|
||||
if (d.isCenter) return;
|
||||
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
})
|
||||
.on("drag", (event, d) => {
|
||||
.on("drag", (event: d3.D3DragEvent<SVGGElement, SimNode, SimNode>, d) => {
|
||||
if (d.isCenter) return;
|
||||
d.fx = event.x;
|
||||
d.fy = event.y;
|
||||
})
|
||||
.on("end", (event, d) => {
|
||||
.on("end", (event: d3.D3DragEvent<SVGGElement, SimNode, SimNode>, d) => {
|
||||
if (d.isCenter) return;
|
||||
if (!event.active) simulation.alphaTarget(0);
|
||||
d.fx = null;
|
||||
|
|
@ -203,17 +203,7 @@ 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((d, i) => {
|
||||
if (d.isCenter) return;
|
||||
if (personIndex >= maxFree) {
|
||||
d3.select(nodeElements[i]).attr("opacity", 0.3);
|
||||
}
|
||||
personIndex++;
|
||||
});
|
||||
|
||||
// Render node visuals
|
||||
nodeSelection.each((d, i) => {
|
||||
|
|
@ -299,30 +289,22 @@ export function renderGraph(
|
|||
});
|
||||
|
||||
// Labels (skip center — it has its own)
|
||||
let labelIndex = 0;
|
||||
const nonCenterNodes = nodeSelection.filter((d) => !d.isCenter);
|
||||
|
||||
// Name
|
||||
nonCenterNodes
|
||||
.append("text")
|
||||
.text((d) => {
|
||||
const i = labelIndex++;
|
||||
return i >= maxFree ? "Locked" : d.person.name;
|
||||
})
|
||||
.text((d) => d.person.name)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dy", NODE_RADIUS + 14)
|
||||
.attr("font-size", "11px")
|
||||
.attr("fill", "#333333");
|
||||
|
||||
// Company
|
||||
let companyIndex = 0;
|
||||
nonCenterNodes
|
||||
.filter((d) => !!(d.person.company))
|
||||
.append("text")
|
||||
.text((d) => {
|
||||
const i = companyIndex++;
|
||||
return i >= maxFree ? "" : d.person.company!;
|
||||
})
|
||||
.text((d) => d.person.company!)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dy", NODE_RADIUS + 27)
|
||||
.attr("font-size", "9px")
|
||||
|
|
@ -335,22 +317,16 @@ export function renderGraph(
|
|||
.attr("class", "people-graph-tooltip");
|
||||
|
||||
nodeSelection
|
||||
.on("mouseenter", (event, d) => {
|
||||
.on("mouseenter", (_event, d) => {
|
||||
if (d.isCenter) return;
|
||||
const nodeIndex = nodes.indexOf(d) - 1;
|
||||
let lines: string[];
|
||||
if (nodeIndex >= maxFree) {
|
||||
lines = ["Upgrade to Pro to unlock this node"];
|
||||
} else {
|
||||
lines = [d.person.name];
|
||||
if (d.person.company) lines.push(`Company: ${d.person.company}`);
|
||||
if (d.person.role) lines.push(`Role: ${d.person.role}`);
|
||||
lines.push(`Closeness: ${d.person.closeness}/10`);
|
||||
}
|
||||
const lines = [d.person.name];
|
||||
if (d.person.company) lines.push(`Company: ${d.person.company}`);
|
||||
if (d.person.role) lines.push(`Role: ${d.person.role}`);
|
||||
lines.push(`Closeness: ${d.person.closeness}/10`);
|
||||
|
||||
tooltip.html(lines.join("<br>")).style("display", "block");
|
||||
})
|
||||
.on("mousemove", (event) => {
|
||||
.on("mousemove", (event: MouseEvent) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
tooltip
|
||||
.style("left", `${event.clientX - rect.left + 12}px`)
|
||||
|
|
@ -360,16 +336,9 @@ export function renderGraph(
|
|||
tooltip.style("display", "none");
|
||||
});
|
||||
|
||||
// Click to open note (or show upgrade prompt for locked nodes)
|
||||
// Click to open the person's note
|
||||
nodeSelection.on("click", (_event, d) => {
|
||||
if (d.isCenter) return;
|
||||
const nodeIndex = nodes.indexOf(d) - 1; // -1 to skip center node
|
||||
if (nodeIndex >= maxFree) {
|
||||
new Notice(
|
||||
`Upgrade to Pro to unlock all people nodes (currently limited to ${maxFree}).`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const file = app.vault.getAbstractFileByPath(d.person.id);
|
||||
if (file instanceof TFile) {
|
||||
void app.workspace.getLeaf("tab").openFile(file);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export async function indexPeople(app: App, settings: PeopleGraphSettings): Prom
|
|||
const cache = app.metadataCache.getFileCache(file);
|
||||
if (!cache?.frontmatter) continue;
|
||||
|
||||
const fm = cache.frontmatter;
|
||||
const fm = cache.frontmatter as Record<string, unknown>;
|
||||
if (!matchesPersonFilter(fm, settings.personField, settings.personValue)) continue;
|
||||
|
||||
const name = fm.name ?? file.basename;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ export default class PeopleGraphPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
const data = (await this.loadData()) as Partial<PeopleGraphSettings> | null;
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export interface PeopleGraphSettings {
|
|||
edgeOpacity: number;
|
||||
showClosenessRing: boolean;
|
||||
centerLabel: string;
|
||||
maxFreeNodes: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: PeopleGraphSettings = {
|
||||
|
|
@ -36,5 +35,4 @@ export const DEFAULT_SETTINGS: PeopleGraphSettings = {
|
|||
edgeOpacity: 0.4,
|
||||
showClosenessRing: true,
|
||||
centerLabel: "You",
|
||||
maxFreeNodes: 20,
|
||||
};
|
||||
|
|
|
|||
4
versions.json
Normal file
4
versions.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"0.1.1": "1.0.0",
|
||||
"0.1.2": "1.7.2"
|
||||
}
|
||||
Loading…
Reference in a new issue