diff --git a/de_DE.json b/de_DE.json
index 653384d..53c0771 100644
--- a/de_DE.json
+++ b/de_DE.json
@@ -14,6 +14,10 @@
"Gitea URL": "Gitea URL",
"Owner": "Eigentümer",
"Repository": "Repository",
- "API Token": "API Token"
+ "API Token": "API Token",
+ "PR Filter": "PR-Filter",
+ "Assigned to me": "Mir zugewiesen",
+ "Review requested": "Review angefordert",
+ "Both": "Beides"
}
}
diff --git a/en.json b/en.json
index dd7b750..93d1479 100644
--- a/en.json
+++ b/en.json
@@ -14,6 +14,10 @@
"Gitea URL": "Gitea URL",
"Owner": "Owner",
"Repository": "Repository",
- "API Token": "API Token"
+ "API Token": "API Token",
+ "PR Filter": "PR Filter",
+ "Assigned to me": "Assigned to me",
+ "Review requested": "Review requested",
+ "Both": "Both"
}
}
diff --git a/manifest.json b/manifest.json
index fbf2c01..448d370 100644
--- a/manifest.json
+++ b/manifest.json
@@ -49,6 +49,19 @@
],
"Tooltip": "Shows the latest Gitea Actions run status for a repository",
"UUID": "dev.mars3142.ulanzideck.collection.gitea"
+ },
+ {
+ "Name": "Gitea Pull Requests",
+ "Icon": "assets/icons/gitea.png",
+ "PropertyInspectorPath": "property-inspector/giteapr/inspector.html",
+ "States": [
+ {
+ "Name": "Default",
+ "Image": "assets/icons/gitea.png"
+ }
+ ],
+ "Tooltip": "Shows open pull requests assigned to you across all repositories",
+ "UUID": "dev.mars3142.ulanzideck.collection.giteapr"
}
],
"OS": [
diff --git a/plugin/actions/GiteaPRAction.js b/plugin/actions/GiteaPRAction.js
new file mode 100644
index 0000000..abc469e
--- /dev/null
+++ b/plugin/actions/GiteaPRAction.js
@@ -0,0 +1,112 @@
+class GiteaPRAction extends ActionBase {
+ constructor($UD, context) {
+ super($UD, context);
+ this.config = {
+ url: '',
+ token: '',
+ filter: 'review_requested',
+ refreshRate: '4',
+ };
+ }
+
+ setActive() {}
+
+ setParams(jsn) {
+ this.config = Object.assign(this.config, (jsn && jsn.param) || {});
+ this.startTimer(() => this.fetchPRs(), this.config.refreshRate);
+ }
+
+ onRun() {
+ if (this.config.url) {
+ this.$UD.openUrl(`${this.config.url}/pulls?type=your_repositories&sort=&state=open&q=`);
+ }
+ }
+
+ fetchPRs() {
+ this.debounce(async () => {
+ if (!this.config.url || !this.config.token) {
+ this.renderButton(null, false);
+ return;
+ }
+ try {
+ const headers = { Authorization: `token ${this.config.token}` };
+ const filter = this.config.filter || 'review_requested';
+ const base = `${this.config.url}/api/v1/repos/issues/search?type=pullrequest&state=open&limit=50`;
+
+ const fetchIds = async (url) => {
+ const r = await fetch(url, { headers, signal: AbortSignal.timeout(8000) });
+ console.log('GiteaPR status:', r.status, url);
+ if (!r.ok) {
+ const body = await r.text();
+ console.log('GiteaPR error body:', body);
+ return null;
+ }
+ const data = await r.json();
+ return Array.isArray(data) ? data.map(i => i.id) : [];
+ };
+
+ let ids;
+ if (filter === 'both') {
+ const [a, b] = await Promise.all([
+ fetchIds(`${base}&assigned=true`),
+ fetchIds(`${base}&review_requested=true`),
+ ]);
+ if (a === null || b === null) { this.renderButton(null, true); return; }
+ ids = [...new Set([...a, ...b])];
+ } else {
+ const param = filter === 'assigned' ? '&assigned=true' : '&review_requested=true';
+ ids = await fetchIds(`${base}${param}`);
+ if (ids === null) { this.renderButton(null, true); return; }
+ }
+
+ this.renderButton(ids.length, false);
+ } catch (e) {
+ // timeout or network error = VPN likely down
+ this.renderButton(null, false);
+ }
+ });
+ }
+
+ renderButton(count, isError) {
+ const { canvas, ctx } = this.createCanvas();
+
+ // Label — top
+ ctx.fillStyle = '#7ec8e3';
+ ctx.font = 'bold 22px "Source Han Sans SC"';
+ ctx.fillText('Pull Requests', 98, 30);
+
+ if (count === null) {
+ // No connection — show plug/disconnected icon
+ ctx.fillStyle = isError ? '#ff6b6b' : '#888888';
+ ctx.font = '70px "Source Han Sans SC"';
+ ctx.textBaseline = 'alphabetic';
+ const bm = ctx.measureText('⚡');
+ ctx.fillText('⚡', 98, 98 + (bm.actualBoundingBoxAscent - bm.actualBoundingBoxDescent) / 2);
+ ctx.textBaseline = 'middle';
+
+ ctx.fillStyle = isError ? '#ff6b6b' : '#888888';
+ ctx.font = 'bold 22px "Source Han Sans SC"';
+ ctx.fillText(isError ? 'API Error' : 'Offline', 98, 166);
+ } else {
+ // Count — large center number (visually centered using actual bounding box)
+ const color = count === 0 ? '#6bff6b' : count < 5 ? '#f0c040' : '#ff6b6b';
+ ctx.fillStyle = color;
+ const fSize = count > 99 ? 60 : count > 9 ? 80 : 96;
+ ctx.font = `bold ${fSize}px "Source Han Sans SC"`;
+ const text = count > 999 ? '999+' : String(count);
+ ctx.textBaseline = 'alphabetic';
+ const m = ctx.measureText(text);
+ ctx.fillText(text, 98, 98 + (m.actualBoundingBoxAscent - m.actualBoundingBoxDescent) / 2);
+ ctx.textBaseline = 'middle';
+
+ // Subtext
+ const filter = this.config.filter || 'review_requested';
+ const label = filter === 'assigned' ? 'assigned' : filter === 'review_requested' ? 'review req.' : 'open';
+ ctx.fillStyle = '#888888';
+ ctx.font = '18px "Source Han Sans SC"';
+ ctx.fillText(label, 98, 166);
+ }
+
+ this.setIcon(canvas);
+ }
+}
diff --git a/plugin/app.html b/plugin/app.html
index 9121a62..91f8782 100644
--- a/plugin/app.html
+++ b/plugin/app.html
@@ -15,6 +15,7 @@
+