mirror of
https://github.com/flyingnobita/obsidian-github-stars.git
synced 2026-07-22 05:42:52 +00:00
Initial commit
This commit is contained in:
commit
3e33be66e7
18 changed files with 3155 additions and 0 deletions
10
.editorconfig
Normal file
10
.editorconfig
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
|
||||
main.js
|
||||
23
.eslintrc
Normal file
23
.eslintrc
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": { "node": true },
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
}
|
||||
}
|
||||
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# vscode
|
||||
.vscode
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# npm
|
||||
node_modules
|
||||
|
||||
# Don't include the compiled main.js file in the repo.
|
||||
# They should be uploaded to GitHub releases instead.
|
||||
main.js
|
||||
|
||||
# Exclude sourcemaps
|
||||
*.map
|
||||
|
||||
# obsidian
|
||||
data.json
|
||||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
# Devenv
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
|
||||
# direnv
|
||||
.direnv
|
||||
|
||||
# pre-commit
|
||||
.pre-commit-config.yaml
|
||||
0
.hotreload
Normal file
0
.hotreload
Normal file
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
tag-version-prefix=""
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Flying Nobita
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
61
README.md
Normal file
61
README.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# Obsidian GitHub Stars Plugin
|
||||
|
||||
Display the number of stars next to GitHub repository links.
|
||||
|
||||
_Note: This plugin currently only display the stars in **Reading View**. In the future I hope to support it in Live Preview as well._
|
||||
|
||||
## Features
|
||||
|
||||
- Automatically detects GitHub repository URLs in your notes
|
||||
- Displays the star count next to each GitHub repository link
|
||||
- Caches star counts to minimize API requests
|
||||
- Configurable display format for star counts
|
||||
- Optional GitHub API token support for higher rate limits
|
||||
- Supports abbreviated number formatting (e.g., 1.2k instead of 1,234)
|
||||
- Command to refresh star counts for the current note
|
||||
|
||||
## Examples
|
||||
|
||||
When you include a GitHub repository URL in your notes, the plugin will automatically enhance it to show the star count:
|
||||
|
||||

|
||||
|
||||
## Configuration
|
||||
|
||||
The plugin can be configured in the Settings tab:
|
||||
|
||||
- **Cache Expiry**: Time in minutes before the GitHub star count cache expires (default: 60 minutes)
|
||||
- **Display Format**: Format for displaying star counts. Use `{stars}` as a placeholder for the number (default: `⭐ {stars}`)
|
||||
- **Number Format**: Choose between full numbers (e.g., 1,234) or abbreviated format (e.g., 1.2k)
|
||||
- **GitHub API Token**: Optional personal access token for GitHub API to increase rate limits
|
||||
|
||||
## Commands
|
||||
|
||||
The plugin adds the following commands:
|
||||
|
||||
- **Refresh GitHub Stars for Current Note**: Refreshes all GitHub star counts in the current note
|
||||
- **Clear GitHub Stars Cache**: Clears the cached star counts
|
||||
|
||||
## GitHub API Rate Limits
|
||||
|
||||
The GitHub API has rate limits for unauthenticated requests (60 requests per hour). If you use GitHub extensively in your notes, you might want to add a GitHub personal access token in the plugin settings to increase this limit (5,000 requests per hour for authenticated requests).
|
||||
|
||||
To create a GitHub personal access token:
|
||||
|
||||
1. Go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens)
|
||||
2. Click "Generate new token"
|
||||
3. Give it a name and select the "public_repo" scope
|
||||
4. Click "Generate token"
|
||||
5. Copy the token and paste it in the plugin settings
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] Support for Live Preview mode
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
49
esbuild.config.mjs
Normal file
49
esbuild.config.mjs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
`;
|
||||
|
||||
const prod = (process.argv[2] === "production");
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
minify: prod,
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
} else {
|
||||
await context.watch();
|
||||
}
|
||||
430
main.ts
Normal file
430
main.ts
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
import { App, MarkdownView, Notice, Plugin, PluginSettingTab, Setting, MarkdownPostProcessorContext } from 'obsidian';
|
||||
|
||||
|
||||
interface GitHubStarsSettings {
|
||||
cacheExpiry: number; // Time in minutes before cache expires
|
||||
displayFormat: string; // Format for displaying stars (e.g., "⭐ {stars}")
|
||||
apiToken: string; // Optional GitHub API token for higher rate limits
|
||||
numberFormat: 'full' | 'abbreviated'; // Number formatting style
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: GitHubStarsSettings = {
|
||||
cacheExpiry: 60, // Default cache expiry: 60 minutes
|
||||
displayFormat: "⭐ {stars}",
|
||||
apiToken: "",
|
||||
numberFormat: 'abbreviated'
|
||||
}
|
||||
|
||||
// Interface for cache entries
|
||||
interface CacheEntry {
|
||||
stars: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export default class GitHubStarsPlugin extends Plugin {
|
||||
settings: GitHubStarsSettings;
|
||||
cache: Record<string, CacheEntry> = {};
|
||||
|
||||
async onload() {
|
||||
// Load settings
|
||||
await this.loadSettings();
|
||||
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon('star', 'Github Stars', (evt: MouseEvent) => {
|
||||
// Called when the user clicks the icon.
|
||||
new Notice('Processing GitHub Stars...');
|
||||
|
||||
// Get the active markdown view
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView) {
|
||||
// Force refresh by triggering the markdown processor
|
||||
activeView.previewMode.rerender(true);
|
||||
new Notice('GitHub star counts refreshed!');
|
||||
} else {
|
||||
new Notice('No active markdown view found');
|
||||
}
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('github-stars-ribbon-class');
|
||||
|
||||
// Load cache from data
|
||||
try {
|
||||
|
||||
const loadedCache = await this.loadData();
|
||||
|
||||
// Check if we have a cache property in the loaded data
|
||||
if (loadedCache && loadedCache.cache) {
|
||||
this.cache = loadedCache.cache;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading cache:', error);
|
||||
}
|
||||
|
||||
// Register the markdown post processor to find and enhance GitHub links
|
||||
this.registerMarkdownPostProcessor(this.processMarkdown.bind(this));
|
||||
|
||||
// Add settings tab
|
||||
this.addSettingTab(new GitHubStarsSettingTab(this.app, this));
|
||||
|
||||
// Add a command to clear the cache
|
||||
this.addCommand({
|
||||
id: 'clear-github-stars-cache',
|
||||
name: 'Clear GitHub Stars Cache',
|
||||
callback: () => {
|
||||
this.cache = {};
|
||||
this.saveSettings();
|
||||
new Notice('GitHub Stars cache cleared');
|
||||
}
|
||||
});
|
||||
|
||||
// Add a command to refresh star counts for the current note
|
||||
this.addCommand({
|
||||
id: 'refresh-github-stars',
|
||||
name: 'Refresh GitHub Stars for Current Note',
|
||||
checkCallback: (checking: boolean) => {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView) {
|
||||
if (!checking) {
|
||||
// Force refresh by triggering the markdown processor
|
||||
activeView.previewMode.rerender(true);
|
||||
new Notice('Refreshing GitHub star counts...');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onunload() {
|
||||
// Save settings when plugin is unloaded
|
||||
this.saveSettings();
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
const data = await this.loadData();
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
|
||||
// Store cache separately if it exists in the loaded data
|
||||
if (data && data.cache) {
|
||||
this.cache = data.cache;
|
||||
}
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
// Save both settings and cache in the same data object
|
||||
const dataToSave = {
|
||||
...this.settings,
|
||||
cache: this.cache
|
||||
};
|
||||
await this.saveData(dataToSave);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process markdown content to find and enhance GitHub links
|
||||
*/
|
||||
async processMarkdown(el: HTMLElement, ctx: MarkdownPostProcessorContext) {
|
||||
// Find all links in the document
|
||||
const links = el.querySelectorAll('a');
|
||||
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
const link = links[i];
|
||||
const url = link.getAttribute('href');
|
||||
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the link is a GitHub repository URL
|
||||
const repoInfo = this.extractRepoInfo(url);
|
||||
if (repoInfo) {
|
||||
// Create a span to hold the star count with loading indicator
|
||||
const starSpan = document.createElement('span');
|
||||
starSpan.addClass('github-stars-count');
|
||||
starSpan.addClass('github-stars-loading');
|
||||
starSpan.setText('⭐ ...');
|
||||
|
||||
// Insert the star count after the link
|
||||
link.after(starSpan);
|
||||
|
||||
// Get star count and update the span using async/await
|
||||
try {
|
||||
const stars = await this.getStarCount(repoInfo.owner, repoInfo.repo);
|
||||
starSpan.removeClass('github-stars-loading');
|
||||
|
||||
if (stars !== null) {
|
||||
const formattedStars = this.formatStarCount(stars);
|
||||
starSpan.setText(formattedStars);
|
||||
} else {
|
||||
starSpan.setText('⭐ ?');
|
||||
starSpan.addClass('github-stars-error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error getting star count for ${repoInfo.owner}/${repoInfo.repo}:`, error);
|
||||
starSpan.removeClass('github-stars-loading');
|
||||
starSpan.setText('⭐ ?');
|
||||
starSpan.addClass('github-stars-error');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract repository owner and name from a GitHub URL
|
||||
* Returns null if the URL is not a valid GitHub repository URL
|
||||
*/
|
||||
extractRepoInfo(url: string): { owner: string, repo: string } | null {
|
||||
// Match GitHub repository URLs
|
||||
// Examples:
|
||||
// - https://github.com/owner/repo
|
||||
// - https://github.com/owner/repo/
|
||||
// - https://github.com/owner/repo/tree/master
|
||||
// - https://github.com/owner/repo/blob/master/README.md
|
||||
// - https://www.github.com/owner/repo
|
||||
// - http://github.com/owner/repo
|
||||
|
||||
// First, normalize the URL
|
||||
const normalizedUrl = url.trim().toLowerCase();
|
||||
|
||||
// Check if it's a GitHub URL
|
||||
if (!normalizedUrl.includes('github.com')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract owner and repo using regex
|
||||
const githubRegex = /https?:\/\/(www\.)?github\.com\/([^/\s]+)\/([^/\s#?]+)(\/.*)?$/;
|
||||
const match = url.match(githubRegex);
|
||||
|
||||
if (match && match[2] && match[3]) {
|
||||
const owner = match[2];
|
||||
let repo = match[3];
|
||||
|
||||
// Remove .git suffix if present
|
||||
if (repo.endsWith('.git')) {
|
||||
repo = repo.slice(0, -4);
|
||||
}
|
||||
|
||||
return { owner, repo };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get star count for a GitHub repository
|
||||
* Uses cache if available and not expired
|
||||
*/
|
||||
async getStarCount(owner: string, repo: string): Promise<number | null> {
|
||||
const cacheKey = `${owner}/${repo}`;
|
||||
|
||||
// Check if we have a valid cache entry
|
||||
if (this.cache[cacheKey]) {
|
||||
const entry = this.cache[cacheKey];
|
||||
const now = Date.now();
|
||||
const expiryTime = this.settings.cacheExpiry * 60 * 1000; // Convert minutes to milliseconds
|
||||
|
||||
// If cache entry is still valid, use it
|
||||
if (now - entry.timestamp < expiryTime) {
|
||||
return entry.stars;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch star count from GitHub API
|
||||
const headers: Record<string, string> = {
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'Obsidian-GitHub-Stars-Plugin'
|
||||
};
|
||||
|
||||
// Add API token if available
|
||||
if (this.settings.apiToken && this.settings.apiToken.trim() !== '') {
|
||||
headers['Authorization'] = `token ${this.settings.apiToken.trim()}`;
|
||||
}
|
||||
|
||||
const apiUrl = `https://api.github.com/repos/${owner}/${repo}`;
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
headers,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
// Handle rate limiting
|
||||
const rateLimitRemaining = response.headers.get('X-RateLimit-Remaining');
|
||||
const rateLimitReset = response.headers.get('X-RateLimit-Reset');
|
||||
|
||||
if (rateLimitRemaining === '0' && rateLimitReset) {
|
||||
const resetTime = new Date(parseInt(rateLimitReset) * 1000);
|
||||
const now = new Date();
|
||||
const minutesUntilReset = Math.ceil((resetTime.getTime() - now.getTime()) / (60 * 1000));
|
||||
|
||||
console.warn(`GitHub API rate limit exceeded. Resets in ${minutesUntilReset} minutes.`);
|
||||
|
||||
// If we have a cached value, use it even if expired
|
||||
if (this.cache[cacheKey]) {
|
||||
return this.cache[cacheKey].stars;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
console.error(`Repository ${owner}/${repo} not found`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch star count for ${owner}/${repo}: ${response.status} ${response.statusText}`);
|
||||
|
||||
// If we have a cached value, use it even if expired
|
||||
if (this.cache[cacheKey]) {
|
||||
return this.cache[cacheKey].stars;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data || typeof data.stargazers_count !== 'number') {
|
||||
console.error(`Invalid response data for ${owner}/${repo}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const stars = data.stargazers_count;
|
||||
|
||||
// Update cache
|
||||
this.cache[cacheKey] = {
|
||||
stars,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Save cache to disk
|
||||
this.saveSettings();
|
||||
|
||||
return stars;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching star count for ${owner}/${repo}:`, error);
|
||||
|
||||
// If we have a cached value, use it even if expired
|
||||
if (this.cache[cacheKey]) {
|
||||
return this.cache[cacheKey].stars;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format star count according to settings
|
||||
*/
|
||||
formatStarCount(stars: number): string {
|
||||
const formatted = this.settings.displayFormat.replace('{stars}', this.formatNumber(stars));
|
||||
return formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format number with thousands separators or abbreviate for large numbers
|
||||
*/
|
||||
formatNumber(num: number): string {
|
||||
// Use full format if configured
|
||||
if (this.settings.numberFormat === 'full') {
|
||||
return num.toLocaleString();
|
||||
}
|
||||
|
||||
// Otherwise use abbreviated format
|
||||
// For numbers less than 1000, just use locale string
|
||||
if (num < 1000) {
|
||||
return num.toLocaleString();
|
||||
}
|
||||
|
||||
// For numbers 1000 or greater, use abbreviations
|
||||
if (num < 10000) {
|
||||
// 1.2k format for 1000-9999
|
||||
return (num / 1000).toFixed(1) + 'k';
|
||||
} else if (num < 1000000) {
|
||||
// 12k format for 10000-999999
|
||||
return Math.round(num / 1000) + 'k';
|
||||
} else {
|
||||
// 1.2M format for 1000000+
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GitHubStarsSettingTab extends PluginSettingTab {
|
||||
plugin: GitHubStarsPlugin;
|
||||
|
||||
constructor(app: App, plugin: GitHubStarsPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Cache expiry')
|
||||
.setDesc('Time in minutes before the GitHub star count cache expires')
|
||||
.addText(text => text
|
||||
.setPlaceholder('60')
|
||||
.setValue(this.plugin.settings.cacheExpiry.toString())
|
||||
.onChange(async (value) => {
|
||||
const numValue = parseInt(value);
|
||||
if (!isNaN(numValue) && numValue > 0) {
|
||||
this.plugin.settings.cacheExpiry = numValue;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Display format')
|
||||
.setDesc('Format for displaying star counts. Use {stars} as a placeholder for the number.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('⭐ {stars}')
|
||||
.setValue(this.plugin.settings.displayFormat)
|
||||
.onChange(async (value) => {
|
||||
if (value.includes('{stars}')) {
|
||||
this.plugin.settings.displayFormat = value;
|
||||
await this.plugin.saveSettings();
|
||||
} else {
|
||||
new Notice('Display format must include {stars} placeholder');
|
||||
}
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Number format')
|
||||
.setDesc('How to format star counts')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('full', 'Full numbers (e.g., 1,234)')
|
||||
.addOption('abbreviated', 'Abbreviated (e.g., 1.2k)')
|
||||
.setValue(this.plugin.settings.numberFormat)
|
||||
.onChange(async (value: 'full' | 'abbreviated') => {
|
||||
this.plugin.settings.numberFormat = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('GitHub API token (optional)')
|
||||
.setDesc('Personal access token for GitHub API to increase rate limits')
|
||||
.addText(text => text
|
||||
.setPlaceholder('ghp_xxxxxxxxxxxxxxxxxxxx')
|
||||
.setValue(this.plugin.settings.apiToken)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.apiToken = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
// Add a button to clear the cache
|
||||
new Setting(containerEl)
|
||||
.setName('Clear cache')
|
||||
.setDesc('Clear the GitHub stars cache')
|
||||
.addButton(button => button
|
||||
.setButtonText('Clear')
|
||||
.onClick(async () => {
|
||||
this.plugin.cache = {};
|
||||
await this.plugin.saveSettings();
|
||||
new Notice('GitHub Stars cache cleared');
|
||||
}));
|
||||
}
|
||||
}
|
||||
10
manifest.json
Normal file
10
manifest.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "obsidian-github-stars",
|
||||
"name": "GitHub Stars",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "1.8.9",
|
||||
"description": "Displays the number of stars for GitHub repositories mentioned in notes.",
|
||||
"author": "Flying Nobita",
|
||||
"authorUrl": "https://github.com/flyingnobita",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
BIN
obsidian-github-stars-screenshot.png
Normal file
BIN
obsidian-github-stars-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
2397
package-lock.json
generated
Normal file
2397
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
29
package.json
Normal file
29
package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "obsidian-github-stars",
|
||||
"version": "1.0.0",
|
||||
"description": "Obsidian plugin that displays the number of stars for GitHub repositories mentioned in notes",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [
|
||||
"obsidian",
|
||||
"plugin",
|
||||
"github",
|
||||
"stars"
|
||||
],
|
||||
"author": "Your Name",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
48
styles.css
Normal file
48
styles.css
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
|
||||
This CSS file will be included with your plugin, and
|
||||
available in the app when your plugin is enabled.
|
||||
|
||||
If your plugin does not need CSS, delete this file.
|
||||
|
||||
*/
|
||||
|
||||
/* GitHub Stars Plugin Styles */
|
||||
|
||||
/* Style for the stars count span */
|
||||
.github-stars-count {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.85em;
|
||||
background-color: var(--background-secondary);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
/* Loading state */
|
||||
.github-stars-loading {
|
||||
animation: github-stars-pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes github-stars-pulse {
|
||||
0% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* Error state */
|
||||
.github-stars-error {
|
||||
color: var(--text-error);
|
||||
}
|
||||
|
||||
/* Hover effect */
|
||||
a:hover + .github-stars-count {
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
14
version-bump.mjs
Normal file
14
version-bump.mjs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { readFileSync, writeFileSync } from "fs";
|
||||
|
||||
const targetVersion = process.env.npm_package_version;
|
||||
|
||||
// read minAppVersion from manifest.json and bump version to target version
|
||||
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
|
||||
const { minAppVersion } = manifest;
|
||||
manifest.version = targetVersion;
|
||||
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
|
||||
|
||||
// update versions.json with target version and minAppVersion from manifest.json
|
||||
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
|
||||
versions[targetVersion] = minAppVersion;
|
||||
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
|
||||
3
versions.json
Normal file
3
versions.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"1.0.0": "0.15.0"
|
||||
}
|
||||
Loading…
Reference in a new issue