Skip to content

Commit f01d012

Browse files
Merge pull request #7 from browneyedsoul/develop
feat: add update-checker widget and auto-release CI
2 parents ba77fab + 5d395a4 commit f01d012

10 files changed

Lines changed: 268 additions & 2 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Release Plugins
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
release:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
with:
16+
fetch-depth: 0
17+
18+
- name: Tag and release plugins
19+
env:
20+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21+
run: |
22+
for pkg in plugins/*/package.json; do
23+
plugin=$(echo "$pkg" | cut -d'/' -f2)
24+
version=$(jq -r '.version' "$pkg")
25+
tag="${plugin}@${version}"
26+
27+
if git tag -l "$tag" | grep -q .; then
28+
echo "⏭ $tag already exists, skipping"
29+
continue
30+
fi
31+
32+
echo "🏷 Creating release $tag"
33+
git tag "$tag"
34+
git push origin "$tag"
35+
36+
gh release create "$tag" \
37+
--title "$plugin v$version" \
38+
--notes "Release $plugin v$version" \
39+
--target main
40+
done

.github/workflows/sync-develop.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Sync develop with main
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
sync:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
with:
16+
ref: develop
17+
fetch-depth: 0
18+
19+
- name: Merge main into develop
20+
run: |
21+
git config user.name "github-actions[bot]"
22+
git config user.email "github-actions[bot]@users.noreply.github.com"
23+
git merge origin/main --no-edit
24+
git push origin develop

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,25 @@
55
| \ | Name | Portal |
66
|---|------|--------|
77
| <img src="https://raw.githubusercontent.com/browneyedsoul/trilium-plugins/main/plugins/outliner/public/logo.svg" width="75" height="75" /> | Trilium Outliner | [Link](https://github.com/browneyedsoul/trilium-plugins/tree/main/plugins/outliner) |
8-
| Soon | It will be added more soon | Link |
8+
| <img src="https://raw.githubusercontent.com/browneyedsoul/trilium-plugins/main/plugins/update-checker/public/logo.svg" width="75" height="75" /> | Trilium Update Checker | [Link](https://github.com/browneyedsoul/trilium-plugins/tree/main/plugins/update-checker) |
9+
10+
## Update Checker
11+
12+
### How to Install
13+
14+
To receive update notifications for installed plugins:
15+
16+
1. Create a new **Code Note** in Trilium.
17+
2. Set the note type to **JSX**.
18+
3. Paste the [update-checker code](https://github.com/browneyedsoul/trilium-plugins/tree/main/plugins/update-checker/trilium-update-checker.jsx) into the note.
19+
4. Add the label `#widget` to that note.
20+
5. Refresh Trilium.
21+
22+
Installed plugins automatically register their versions. When a newer version is released, a toast notification will appear in the bottom-right corner with a link to the release page.
23+
24+
### How to Update a Plugin
25+
26+
1. Click the **Release** link in the toast notification.
27+
2. Copy the latest plugin code from the release page.
28+
3. Paste it into the existing Code Note in Trilium, replacing the old code.
29+
4. Refresh Trilium.

plugins/outliner/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "trilium-outliner",
3+
"version": "1.0.0",
4+
"description": "A Plugin which automatically indents content based on heading level",
5+
"author": "browneyedsoul",
6+
"license": "MIT"
7+
}

plugins/outliner/trilium-outliner.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ class OutlineByHeadingWidget extends api.NoteContextAwareWidget {
66
return 100
77
}
88

9+
static get VERSION() { return '1.0.0' }
10+
static get PLUGIN_NAME() { return 'outliner' }
11+
912
constructor() {
1013
super()
1114
this.contentSized()
15+
localStorage.setItem(`trilium-plugin-${OutlineByHeadingWidget.PLUGIN_NAME}`, OutlineByHeadingWidget.VERSION)
1216

1317
// ====== 설정 ======
1418
this.ACTIVATE_LABEL = null // (null 은 어디든 / "outline" 은 #outline 일 때만)

plugins/soon/soon.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

plugins/update-checker/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Trilium Update Checker
2+
3+
A widget that checks for plugin updates by comparing installed versions against GitHub release tags.
4+
5+
## How It Works
6+
7+
1. On load, it fetches tags from the GitHub repository.
8+
2. Compares each plugin's installed version against the latest tag (e.g. `outliner@1.1.0`).
9+
3. If a newer version exists, a toast notification appears in the bottom-right corner.
10+
4. Checks once every 24 hours (cached via localStorage).
11+
12+
## How to Use
13+
14+
1. Create a new **Code Note** in Trilium.
15+
2. Set the note type to `JSX`.
16+
3. Paste the plugin code into the note.
17+
4. Add the label `#widget` to that note.
18+
5. Refresh Trilium.
19+
20+
## How It Detects Installed Plugins
21+
22+
Each plugin registers its name and version to localStorage on load (e.g. `trilium-plugin-outliner: "1.0.0"`). The update checker automatically discovers all registered plugins and compares their versions against GitHub tags. No manual configuration needed.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "trilium-update-checker",
3+
"version": "1.0.0",
4+
"description": "A widget that checks for plugin updates via GitHub tags",
5+
"author": "browneyedsoul",
6+
"license": "MIT"
7+
}
Lines changed: 13 additions & 0 deletions
Loading
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { defineWidget, useState, useEffect } from "trilium:preact";
2+
3+
const REPO = 'browneyedsoul/trilium-plugins'
4+
const CHECK_INTERVAL = 24 * 60 * 60 * 1000
5+
const CACHE_KEY = 'trilium-plugins-update-check'
6+
const PLUGIN_PREFIX = 'trilium-plugin-'
7+
8+
const getInstalledPlugins = () => {
9+
const plugins = []
10+
for (let i = 0; i < localStorage.length; i++) {
11+
const key = localStorage.key(i)
12+
if (key.startsWith(PLUGIN_PREFIX) && key !== CACHE_KEY) {
13+
plugins.push({
14+
name: key.slice(PLUGIN_PREFIX.length),
15+
version: localStorage.getItem(key),
16+
})
17+
}
18+
}
19+
return plugins
20+
}
21+
22+
const compareVersions = (a, b) => {
23+
const pa = a.split('.').map(Number)
24+
const pb = b.split('.').map(Number)
25+
for (let i = 0; i < 3; i++) {
26+
if ((pa[i] || 0) > (pb[i] || 0)) return 1
27+
if ((pa[i] || 0) < (pb[i] || 0)) return -1
28+
}
29+
return 0
30+
}
31+
32+
const checkForUpdates = async () => {
33+
const lastCheck = localStorage.getItem(CACHE_KEY)
34+
if (lastCheck && Date.now() - Number(lastCheck) < CHECK_INTERVAL) return []
35+
36+
localStorage.setItem(CACHE_KEY, String(Date.now()))
37+
38+
try {
39+
const res = await fetch(`https://api.github.com/repos/${REPO}/tags`)
40+
if (!res.ok) return []
41+
42+
const tags = (await res.json()).map(t => t.name)
43+
const updates = []
44+
45+
for (const plugin of getInstalledPlugins()) {
46+
const prefix = `${plugin.name}@`
47+
const versions = tags
48+
.filter(t => t.startsWith(prefix))
49+
.map(t => t.slice(prefix.length))
50+
.sort(compareVersions)
51+
52+
if (versions.length === 0) continue
53+
54+
const latest = versions[versions.length - 1]
55+
if (compareVersions(latest, plugin.version) > 0) {
56+
updates.push({
57+
name: plugin.name,
58+
current: plugin.version,
59+
latest,
60+
tag: `${prefix}${latest}`,
61+
})
62+
}
63+
}
64+
65+
return updates
66+
} catch (_) {
67+
return []
68+
}
69+
}
70+
71+
const toastStyle = {
72+
position: 'fixed',
73+
bottom: '20px',
74+
right: '20px',
75+
zIndex: 99999,
76+
background: '#1e1e1e',
77+
color: '#ccc',
78+
border: '1px solid #444',
79+
borderRadius: '8px',
80+
padding: '14px 18px',
81+
fontSize: '13px',
82+
maxWidth: '360px',
83+
boxShadow: '0 4px 12px rgba(0,0,0,0.4)',
84+
}
85+
86+
const UpdateToast = () => {
87+
const [updates, setUpdates] = useState([])
88+
const [visible, setVisible] = useState(true)
89+
90+
useEffect(() => {
91+
checkForUpdates().then(setUpdates)
92+
}, [])
93+
94+
if (updates.length === 0 || !visible) return null
95+
96+
return (
97+
<div style={toastStyle}>
98+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
99+
<strong style={{ color: '#e6e6e6' }}>Plugin updates available</strong>
100+
<button
101+
onClick={() => setVisible(false)}
102+
style={{ background: 'none', border: 'none', color: '#aaa', cursor: 'pointer', fontSize: '16px' }}
103+
>
104+
105+
</button>
106+
</div>
107+
<ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
108+
{updates.map(u => (
109+
<li key={u.name} style={{ margin: '4px 0' }}>
110+
<strong>{u.name}</strong> v{u.current} → v{u.latest}
111+
<a
112+
href={`https://github.com/${REPO}/releases/tag/${u.tag}`}
113+
target="_blank"
114+
style={{ color: '#58a6ff', marginLeft: '6px' }}
115+
>
116+
Release
117+
</a>
118+
</li>
119+
))}
120+
</ul>
121+
</div>
122+
)
123+
}
124+
125+
export default defineWidget({
126+
parent: "center-pane",
127+
position: 9999,
128+
render: () => <UpdateToast />,
129+
})

0 commit comments

Comments
 (0)