🎉 init: first commit

This commit is contained in:
hacker-c 2025-03-16 12:31:44 +08:00
commit c5aec579c6
18 changed files with 1709 additions and 0 deletions

10
.editorconfig Normal file
View 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
View file

@ -0,0 +1,3 @@
node_modules/
main.js

23
.eslintrc Normal file
View 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"
}
}

BIN
.github/img/preview1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

BIN
.github/img/preview2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

22
.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
# 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

1
.npmrc Normal file
View file

@ -0,0 +1 @@
tag-version-prefix=""

5
LICENSE Normal file
View file

@ -0,0 +1,5 @@
Copyright (C) 2020-2025 by Dynalist Inc.
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

22
README.md Normal file
View file

@ -0,0 +1,22 @@
# Open in GitHub Plugin For Obsidian
This is an Obsidian plugin designed to open projects or files directly in GitHub via your browser. Learn more about Obsidian at https://obsidian.md.
![preview1](./.github/img/preview1.png)
![preview2](./.github/img/preview2.png)
## Manually installing the plugin
- Copy over `main.js`,`manifest.json`,`styles.css` to your vault `VaultFolder/.obsidian/plugins/open-in-github`.
## Development
Clone this repo to `VaultFolder/.obsidian/plugins/` and excute the commands below, then open your Obsidian App.
```
pnpm i
pnpm dev
```
Documentation:
- https://docs.obsidian.md
- https://github.com/obsidianmd/obsidian-api

49
esbuild.config.mjs Normal file
View 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();
}

216
main.ts Normal file
View file

@ -0,0 +1,216 @@
import { Notice, Plugin, addIcon } from 'obsidian'
import * as path from 'path'
import * as fs from 'fs'
const customIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/>
<path d="M9 18c-4.51 2-5-2-7-2"/>
<g transform="translate(12 12) scale(0.5)" stroke="currentColor">
<path d="M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"/>
<path d="m21 3-9 9"/>
<path d="M15 3h6v6"/>
</g>
</svg>
`
const CONSTS = {
TITLE_ID: 'open-in-github',
TITLE: 'Open in GitHub'
}
interface OpenInGitHubPluginSettings {
mySetting: string
}
const DEFAULT_SETTINGS: OpenInGitHubPluginSettings = {
mySetting: 'default'
}
export default class OpenInGitHubPlugin extends Plugin {
settings: OpenInGitHubPluginSettings
async onload() {
await this.loadSettings()
// add icon
addIcon(CONSTS.TITLE_ID, customIcon)
// Left open-in-github button
const ribbonIconEl = this.addRibbonIcon(CONSTS.TITLE_ID, CONSTS.TITLE, this.openGitHub)
// add file menu
this.registerEvent(
this.app.workspace.on('file-menu', (menu, file) => {
menu.addItem((item) => {
item
.setTitle(CONSTS.TITLE)
.setIcon(CONSTS.TITLE_ID)
.onClick((e) => this.openGitHub(e as MouseEvent, true));
});
})
);
// add editormenu
this.registerEvent(
this.app.workspace.on("editor-menu", (menu, editor, view) => {
menu.addItem((item) => {
item
.setTitle(CONSTS.TITLE)
.setIcon(CONSTS.TITLE_ID)
.onClick((e) => this.openGitHub(e as MouseEvent, true));
});
})
);
// Perform additional things with the ribbon
ribbonIconEl.addClass('my-plugin-ribbon-class')
// Bottom right open-file-in-github button
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
// const statusBarItemEl = this.addStatusBarItem();
// const parser = new DOMParser()
// const svgDoc = parser.parseFromString(customIcon(24, 80), 'image/svg+xml')
// const svgElement = svgDoc.documentElement as unknown as HTMLElement
// svgElement.setAttribute('width', '16');
// svgElement.setAttribute('height', '16');
// statusBarItemEl.appendChild(svgElement);
// statusBarItemEl.addEventListener('click', e => openGitHub(e, true))
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000))
}
onunload() { }
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
}
async saveSettings() {
await this.saveData(this.settings)
}
/**
* open github in browser
* @param evt
* @param openFile
*/
openGitHub = async (evt: MouseEvent, openFile?: boolean) => {
try {
const repoUrl = await this.getGitHubRepoUrl()
if (repoUrl) {
const fileRelativePath = this.app.workspace.getActiveFile()
if (openFile) {
if (fileRelativePath?.path) {
let branch = await this.getCurrentBranch(fileRelativePath?.path)
if (!branch) {
branch = 'main'
}
const fileUrl = `${repoUrl}/blob/${branch}/${fileRelativePath?.path}`
window.open(fileUrl, '_blank')
} else {
new Notice('Not found file relative path!')
}
} else {
window.open(repoUrl, '_blank')
}
} else {
new Notice('Could not determine GitHub repository URL.')
}
} catch (err) {
new Notice('Failed to open GitHub repository.')
console.error(err)
}
}
/**
* Get the GitHub repository URL for the current vault.
*/
async getGitHubRepoUrl(): Promise<string | null> {
const vaultPath = this.getVaultBasePath()
if (!vaultPath) {
new Notice('This feature is only supported in the desktop app.')
return null
}
const gitDir = path.join(vaultPath, '.git')
if (!fs.existsSync(gitDir)) {
new Notice('No .git directory found in the vault.')
return null
}
const configPath = path.join(gitDir, 'config')
if (!fs.existsSync(configPath)) {
new Notice('No .git/config file found.')
return null
}
const configContent = fs.readFileSync(configPath, 'utf-8')
const remoteMatch = configContent.match(/\[remote "origin"\][\s\S]*?url\s*=\s*(.*)/)
if (!remoteMatch) {
new Notice('No remote "origin" found in .git/config.')
return null
}
const remoteUrl = remoteMatch[1].trim()
return this.convertToGitHubUrl(remoteUrl)
}
/**
* Convert a Git remote URL to a GitHub URL.
*/
convertToGitHubUrl(remoteUrl: string): string | null {
// Handle HTTPS URLs (e.g., https://github.com/user/repo.git)
if (remoteUrl.startsWith('https://')) {
return remoteUrl.replace(/\.git$/, '')
}
// Handle SSH URLs (e.g., git@github.com:user/repo.git)
if (remoteUrl.startsWith('git@')) {
return remoteUrl.replace(':', '/').replace('git@', 'https://').replace(/\.git$/, '')
}
return null
}
/**
* Get the base path of the vault (only works in desktop app).
*/
getVaultBasePath(): string | null {
// @ts-ignore
if (this.app.vault.adapter?.getBasePath) {
// @ts-ignore
return this.app.vault.adapter.getBasePath()
}
return null
}
/**
* Get the current branch of the Git repository.
*/
async getCurrentBranch(filePath: string): Promise<string | null> {
const vaultPath = this.getVaultBasePath()
if (!vaultPath) {
new Notice('This feature is only supported in the desktop app.')
return null
}
const gitDir = path.join(vaultPath, '.git')
if (!fs.existsSync(gitDir)) {
new Notice('No .git directory found in the vault.')
return null
}
const headPath = path.join(gitDir, 'HEAD');
const headContent = fs.readFileSync(headPath, 'utf-8').trim();
// Example: ref: refs/heads/main
const branchMatch = headContent.match(/ref: refs\/heads\/(.*)/);
if (branchMatch) {
return branchMatch[1];
}
return null;
}
}

10
manifest.json Normal file
View file

@ -0,0 +1,10 @@
{
"id": "open-in-github",
"name": "Open in GitHub",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "Open the current project or file in github.com.",
"author": "Murphy Chen",
"authorUrl": "https://github.com/Hacker-C",
"isDesktopOnly": false
}

24
package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "obsidian-sample-plugin",
"version": "1.0.0",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"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": [],
"author": "",
"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"
}
}

1275
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

8
styles.css Normal file
View file

@ -0,0 +1,8 @@
/*
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.
*/

24
tsconfig.json Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
{
"1.0.0": "0.15.0"
}