Main plugin class extending Obsidian's Plugin.
#### Properties
class GitHubProjectsPlugin extends Plugin {
settings: PluginSettings;
projectState: ProjectState;
}
#### Methods
##### onload(): Promise
Called when the plugin is loaded.
Returns: Promise
Example:
async onload() {
await this.loadSettings();
this.registerView(VIEW_TYPE_PROJECT_BOARD, (leaf) => new ProjectBoardView(leaf, this));
this.addRibbonIcon('layout-dashboard', 'Open GitHub Project', () => this.activateView());
}
##### onunload(): void
Called when the plugin is unloaded.
Returns: void
##### loadSettings(): Promise
Load plugin settings from storage.
Returns: Promise
##### saveSettings(): Promise
Save plugin settings to storage.
Returns: Promise
##### activateView(): Promise
Open or focus the project board view.
Returns: Promise
Example:
await this.plugin.activateView();
Centralized state management using event-driven pattern.
#### Constructor
constructor(plugin: GitHubProjectsPlugin)
Parameters:
plugin: Reference to the main plugin instance#### Properties
class ProjectState {
private project: Project | null;
private loading: boolean;
private error: string | null;
private events: Events;
}
#### Methods
##### loadProject(): Promise
Fetch project data from GitHub.
Returns: Promise
Emits: project-loading, project-loaded, project-error
Example:
await projectState.loadProject();
##### refresh(): Promise
Refresh project data from GitHub.
Returns: Promise
Emits: project-refreshing, project-loaded, project-error
##### moveItem(itemId: string, statusId: string): Promise
Move an item to a different status (optimistic update).
Parameters:
itemId: The ID of the item to movestatusId: The ID of the target statusReturns: Promise
Emits: item-moved, item-move-error
Example:
await projectState.moveItem('ITEM_123', 'STATUS_456');
##### getProject(): Project | null
Get the current project data.
Returns: Project | null
##### getColumns(): Column[]
Get project columns based on status field.
Returns: Column[]
##### getItemsByColumn(columnId: string): ProjectItem[]
Get all items in a specific column.
Parameters:
columnId: The column ID (status option ID)Returns: ProjectItem[]
##### on(event: string, callback: Function): void
Subscribe to state events.
Parameters:
event: Event namecallback: Function to call when event is triggeredExample:
projectState.on('project-loaded', (project) => {
console.log('Project loaded:', project.title);
});
##### off(event: string, callback: Function): void
Unsubscribe from state events.
Parameters:
event: Event namecallback: Previously registered callback functionGraphQL client for GitHub Projects V2 API.
#### Constructor
constructor(token: string)
Parameters:
token: GitHub Personal Access Token#### Methods
##### fetchProject(organization: string, projectNumber: number): Promise
Fetch project data from GitHub.
Parameters:
organization: GitHub organization nameprojectNumber: Project number from URLReturns: Promise
Throws:
Error if authentication failsError if project not foundError if network errorExample:
const client = new GitHubAPIClient(token);
const project = await client.fetchProject('my-org', 5);
##### updateItemStatus(projectId: string, itemId: string, fieldId: string, statusId: string): Promise
Update an item's status field.
Parameters:
projectId: Project V2 IDitemId: Item IDfieldId: Status field IDstatusId: Target status option IDReturns: Promise
Throws: Error if update fails
Example:
await client.updateItemStatus(
'PROJECT_ID',
'ITEM_ID',
'FIELD_ID',
'STATUS_ID'
);
##### testConnection(organization: string, projectNumber: number): Promise
Test if the API connection works.
Parameters:
organization: GitHub organization nameprojectNumber: Project numberReturns: Promise - true if connection successful
Example:
const isValid = await client.testConnection('my-org', 5);
interface PluginSettings {
githubToken: string; // GitHub PAT (stored in localStorage)
organization: string; // GitHub organization name
projectNumber: number; // Project number from URL
autoRefreshInterval: number; // Auto-refresh interval in minutes
}
interface Project {
id: string; // Project V2 ID
title: string; // Project title
description?: string; // Project description
fields: ProjectField[]; // All project fields
items: ProjectItem[]; // All project items
statusField?: StatusField; // The "Status" field used for columns
}
interface ProjectField {
id: string; // Field ID
name: string; // Field name
type: FieldType; // Field type
options?: FieldOption[]; // Options for select fields
}type FieldType =
| 'TEXT'
| 'NUMBER'
| 'DATE'
| 'SINGLE_SELECT'
| 'ITERATION';
interface StatusField extends ProjectField {
type: 'SINGLE_SELECT';
options: StatusOption[];
}interface StatusOption {
id: string; // Option ID
name: string; // Display name
color?: string; // Optional color
}
interface ProjectItem {
id: string; // Item ID
type: ItemType; // Item type
content: ItemContent; // Issue or PR content
fieldValues: FieldValue[]; // All field values
statusId?: string; // Current status option ID
}type ItemType = 'ISSUE' | 'PULL_REQUEST' | 'DRAFT_ISSUE';
interface ItemContent {
number?: number; // Issue/PR number
title: string; // Title
body?: string; // Description
state: ItemState; // Open/Closed/Merged
url: string; // GitHub URL
assignees: Assignee[]; // Assigned users
labels: Label[]; // Labels
createdAt: string; // Creation timestamp
updatedAt: string; // Last update timestamp
}type ItemState = 'OPEN' | 'CLOSED' | 'MERGED';
interface Assignee {
id: string; // User ID
login: string; // GitHub username
name?: string; // Display name
avatarUrl: string; // Avatar image URL
}
interface Label {
id: string; // Label ID
name: string; // Label name
color: string; // Hex color code
description?: string; // Label description
}
interface Column {
id: string; // Status option ID
name: string; // Column name
items: ProjectItem[]; // Items in this column
}
The ProjectState class emits the following events:
#### project-loading
Emitted when project data fetch starts.
Payload: None
Example:
projectState.on('project-loading', () => {
console.log('Loading project...');
});
#### project-loaded
Emitted when project data is successfully loaded.
Payload: Project
Example:
projectState.on('project-loaded', (project: Project) => {
console.log('Project loaded:', project.title);
});
#### project-error
Emitted when project fetch fails.
Payload: Error
Example:
projectState.on('project-error', (error: Error) => {
console.error('Failed to load project:', error.message);
});
#### project-refreshing
Emitted when project refresh starts.
Payload: None
#### item-moved
Emitted when an item is moved (optimistically).
Payload: { itemId: string, statusId: string }
Example:
projectState.on('item-moved', ({ itemId, statusId }) => {
console.log(Item ${itemId} moved to ${statusId});
});
#### item-move-error
Emitted when item move fails.
Payload: { itemId: string, error: Error }
Located in src/utils/storage.ts:
#### getToken(): string | null
Get GitHub token from localStorage.
Returns: string | null
Example:
const token = getToken();
#### setToken(token: string): void
Save GitHub token to localStorage.
Parameters:
token: GitHub Personal Access TokenExample:
setToken('ghp_...');
#### clearToken(): void
Remove GitHub token from localStorage.
Example:
clearToken();
Located in src/utils/github.ts:
#### parseProjectUrl(url: string): { org: string, number: number } | null
Parse organization and project number from GitHub URL.
Parameters:
url: GitHub project URLReturns: { org: string, number: number } | null
Example:
const parsed = parseProjectUrl('https://github.com/orgs/my-org/projects/5');
// Returns: { org: 'my-org', number: 5 }
#### formatItemNumber(item: ProjectItem): string
Format item number for display (e.g., "#123").
Parameters:
item: Project itemReturns: string
Example:
const formatted = formatItemNumber(item);
// Returns: "#123"
#### getItemStateColor(state: ItemState): string
Get CSS color for item state.
Parameters:
state: Item state (OPEN/CLOSED/MERGED)Returns: string (CSS color value)
Example:
const color = getItemStateColor('OPEN');
// Returns: 'var(--color-green)'
#### formatRelativeTime(timestamp: string): string
Format timestamp as relative time (e.g., "2 hours ago").
Parameters:
timestamp: ISO 8601 timestampReturns: string
Example:
const relative = formatRelativeTime('2024-01-15T10:00:00Z');
// Returns: "2 hours ago"
Register custom views that integrate with the plugin:
this.registerView(
'custom-view-type',
(leaf) => new CustomView(leaf, this.plugin.projectState)
);
Add commands that interact with project state:
this.addCommand({
id: 'custom-command',
name: 'Custom Command',
callback: async () => {
const project = this.plugin.projectState.getProject();
// Do something with project data
}
});
Subscribe to plugin events in other plugins or scripts:
// Access plugin instance
const plugin = app.plugins.plugins['github-projects'];// Subscribe to events
plugin.projectState.on('project-loaded', (project) => {
// React to project updates
});
#### AuthenticationError
Thrown when GitHub authentication fails.
catch (error) {
if (error.message.includes('authentication')) {
// Handle authentication error
}
}
#### NotFoundError
Thrown when project is not found.
catch (error) {
if (error.message.includes('not found')) {
// Handle not found error
}
}
#### NetworkError
Thrown when network request fails.
catch (error) {
if (error.message.includes('network')) {
// Handle network error
}
}
#### RateLimitError
Thrown when GitHub API rate limit is exceeded.
catch (error) {
if (error.message.includes('rate limit')) {
// Handle rate limit error
}
}
const debouncedRefresh = debounce(() => state.refresh(), 1000);
const columns = useMemo(() => groupByStatus(items), [items]);
// Bad
console.log('Token:', token); // Good
console.log('Token:', token ? '[REDACTED]' : 'None');
try {
await state.loadProject();
} catch (error) {
new Notice('Failed to load project: ' + error.message);
}
All APIs are fully typed. Import types:
import type {
Project,
ProjectItem,
PluginSettings,
Column
} from 'obsidian-github-projects';
Enable strict TypeScript for best experience:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}